This commit is contained in:
Zhe Fang
2025-07-07 17:30:44 -04:00
parent 78a6ba8e1f
commit a29e5c98f8
44 changed files with 2043 additions and 725 deletions

View File

@@ -5,52 +5,62 @@
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
xmlns:uap18="http://schemas.microsoft.com/appx/manifest/uap/windows10/18"
IgnorableNamespaces="uap rescap uap18">
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.8.0" />
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.9.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>BetterLyrics</DisplayName>
<PublisherDisplayName>founchoo</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Properties>
<DisplayName>BetterLyrics</DisplayName>
<PublisherDisplayName>founchoo</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
</Dependencies>
<Resources>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
<Resource Language="zh-TW"/>
<Resource Language="ja-JP"/>
<Resource Language="ko-KR"/>
</Resources>
<Resources>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
<Resource Language="zh-TW"/>
<Resource Language="ja-JP"/>
<Resource Language="ko-KR"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="BetterLyrics"
Description="BetterLyrics.WinUI3 (Package)"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="BetterLyrics"
Description="BetterLyrics.WinUI3 (Package)"
BackgroundColor="transparent"
Square150x150Logo="Images\Square150x150Logo.png"
Square44x44Logo="Images\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
<uap:SplashScreen Image="Images\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<uap5:Extension
Category="windows.startupTask">
<uap5:StartupTask
TaskId="AutoStartup"
Enabled="false"
DisplayName="BetterLyrics" />
</uap5:Extension>
</Extensions>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -53,7 +53,7 @@
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
<!-- Style (inc. the correct spacing) of a section header -->
<!-- Style -->
<Style
x:Key="SettingsSectionHeaderTextBlockStyle"
BasedOn="{StaticResource BodyStrongTextBlockStyle}"

View File

@@ -1,9 +1,5 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
@@ -16,6 +12,11 @@ using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using Serilog;
using ShadowViewer.Controls;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3
{
@@ -23,13 +24,15 @@ namespace BetterLyrics.WinUI3
{
private readonly ILogger<App> _logger;
private readonly ISettingsService _settingsService;
public static new App Current => (App)Application.Current;
public static DispatcherQueue? DispatcherQueue { get; private set; }
public static DispatcherQueueTimer? DispatcherQueueTimer { get; private set; }
public static ResourceLoader? ResourceLoader { get; private set; }
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
public App()
{
this.InitializeComponent();
@@ -43,7 +46,6 @@ namespace BetterLyrics.WinUI3
ConfigureServices();
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
_settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
UnhandledException += App_UnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
@@ -90,6 +92,7 @@ namespace BetterLyrics.WinUI3
.AddSingleton<IPlaybackService, PlaybackService>()
.AddSingleton<IMusicSearchService, MusicSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ILibreTranslateService, LibreTranslateService>()
// ViewModels
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()

View File

@@ -41,7 +41,6 @@
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="Fluent.LibreTranslate" Version="1.0.6" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="iTunesSearch" Version="1.0.44" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
@@ -52,6 +51,7 @@
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />

View File

@@ -29,6 +29,7 @@ namespace BetterLyrics.WinUI3.Helper
public bool IsTransitioning => _isTransitioning;
public T Value => _currentValue;
public T TargetValue => _targetValue;
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
{

View File

@@ -2,7 +2,9 @@
namespace BetterLyrics.WinUI3.Helper
{
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
@@ -26,31 +28,40 @@ namespace BetterLyrics.WinUI3.Helper
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
public const string UnlockWindowTag = "UnlockWindow";
public static string AmllTtmlDbIndexPath => Path.Combine(CacheFolder, "amll-ttml-db-index.json");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
public static string LrcLibLyricsCacheDirectory => Path.Combine(CacheFolder, "lrclib-lyrics");
public static string NeteaseLyricsCacheDirectory => Path.Combine(CacheFolder, "netease-lyrics");
public static string QQLyricsCacheDirectory => Path.Combine(CacheFolder, "qq-lyrics");
public static string KugouLyricsCacheDirectory => Path.Combine(CacheFolder, "kugou-lyrics");
public static string AmllTtmlDbLyricsCacheDirectory => Path.Combine(CacheFolder, "amll-ttml-db-lyrics");
public static string iTunesAlbumArtCacheDirectory => Path.Combine(CacheFolder, "itunes-album-art");
private static string LocalFolder => ApplicationData.Current.LocalFolder.Path;
public static void EnsureDirectories()
{
Directory.CreateDirectory(LocalFolder);
Directory.CreateDirectory(LogDirectory);
Directory.CreateDirectory(LrcLibLyricsCacheDirectory);
Directory.CreateDirectory(QQLyricsCacheDirectory);
Directory.CreateDirectory(KugouLyricsCacheDirectory);
Directory.CreateDirectory(NeteaseLyricsCacheDirectory);
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
}
public static async Task<DateTime> GetBuildDate()
@@ -66,5 +77,39 @@ namespace BetterLyrics.WinUI3.Helper
// 返回修改日期
return props.DateModified.DateTime;
}
public static List<LanguageInfo> GetAllTranslationLanguagesInfo() =>
[
new LanguageInfo("ar", "العربية"),
new LanguageInfo("az", "Azərbaycan dili"),
new LanguageInfo("zh", "中文"),
new LanguageInfo("cs", "Čeština"),
new LanguageInfo("da", "Dansk"),
new LanguageInfo("nl", "Nederlands"),
new LanguageInfo("en", "English"),
new LanguageInfo("eo", "Esperanto"),
new LanguageInfo("fi", "Suomi"),
new LanguageInfo("fr", "Français"),
new LanguageInfo("de", "Deutsch"),
new LanguageInfo("el", "Ελληνικά"),
new LanguageInfo("he", "עברית"),
new LanguageInfo("hi", "हिन्दी"),
new LanguageInfo("hu", "Magyar"),
new LanguageInfo("id", "Bahasa Indonesia"),
new LanguageInfo("ga", "Gaeilge"),
new LanguageInfo("it", "Italiano"),
new LanguageInfo("ja", "日本語"),
new LanguageInfo("ko", "한국어"),
new LanguageInfo("fa", "فارسی"),
new LanguageInfo("pl", "Polski"),
new LanguageInfo("pt", "Português"),
new LanguageInfo("ru", "Русский"),
new LanguageInfo("sk", "Slovenčina"),
new LanguageInfo("es", "Español"),
new LanguageInfo("sv", "Svenska"),
new LanguageInfo("tr", "Türkçe"),
new LanguageInfo("uk", "Українська"),
new LanguageInfo("vi", "Tiếng Việt"),
];
}
}

View File

@@ -86,5 +86,17 @@ namespace BetterLyrics.WinUI3.Helper
{
return Color.FromArgb(alpha, color.R, color.G, color.B);
}
public static Color WithBrightness(this Color color, double brightness)
{
// 确保亮度因子在合理范围内
brightness = Math.Max(0, Math.Min(1, brightness));
var hsl = CommunityToolkit.WinUI.Helpers.ColorHelper.ToHsl(color);
double h = hsl.H;
double s = hsl.S;
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
}
}
}

View File

@@ -34,10 +34,9 @@ namespace BetterLyrics.WinUI3.Helper
}
// <20>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
var windowManager = WindowManager.Get(window);
if (_originalWindowBounds.TryGetValue(hwnd, out var bounds))
{
windowManager.AppWindow.MoveAndResize(
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(
(int)bounds.X,
(int)bounds.Y,
@@ -63,14 +62,13 @@ namespace BetterLyrics.WinUI3.Helper
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// <20><>¼ԭʼ<D4AD><CABC><EFBFBD><EFBFBD>λ<EFBFBD>úʹ<C3BA>С
var windowManager = WindowManager.Get(window);
if (!_originalWindowBounds.ContainsKey(hwnd))
{
_originalWindowBounds[hwnd] = (
windowManager.AppWindow.Position.X,
windowManager.AppWindow.Position.Y,
windowManager.Width,
windowManager.Height
window.AppWindow.Position.X,
window.AppWindow.Position.Y,
window.AppWindow.Size.Width,
window.AppWindow.Size.Height
);
}
@@ -81,7 +79,7 @@ namespace BetterLyrics.WinUI3.Helper
int targetY = _settingsService.DesktopWindowTop;
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
windowManager.AppWindow.MoveAndResize(
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
);
@@ -102,7 +100,7 @@ namespace BetterLyrics.WinUI3.Helper
public static void Lock(Window window)
{
window.SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
// <20><><EFBFBD><EFBFBD><EFBFBD>ޱ߿<DEB1><DFBF><EFBFBD>͸<EFBFBD><CDB8>
window.ToggleWindowStyle(true, WindowStyle.Popup | WindowStyle.Visible);
window.ExtendsContentIntoTitleBar = false;

View File

@@ -171,5 +171,22 @@ namespace BetterLyrics.WinUI3.Helper
await stream.AsStreamForRead().CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
public static float GetAverageLuminance(CanvasBitmap bitmap)
{
var pixels = bitmap.GetPixelBytes();
double sum = 0;
for (int i = 0; i < pixels.Length; i += 4)
{
// BGRA
byte b = pixels[i];
byte g = pixels[i + 1];
byte r = pixels[i + 2];
// 忽略A
double y = 0.299 * r + 0.587 * g + 0.114 * b;
sum += y / 255.0;
}
return (float)(sum / (pixels.Length / 4));
}
}
}

View File

@@ -2,7 +2,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using Fluent.LibreTranslate;
using Lyricify.Lyrics.Models;
using System;
using System.Collections.Generic;

View File

@@ -1,9 +0,0 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace BetterLyrics.WinUI3.Messages
{
public class ShowNotificatonMessage(Notification value) : ValueChangedMessage<Notification>(value) { }
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class DetectLanguageResult
{
[JsonPropertyName("confidence")]
public double Confidence { get; set; }
[JsonPropertyName("language")]
public string Language { get; set; }
}
}

View File

@@ -0,0 +1,23 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public partial class LanguageInfo : ObservableObject
{
[ObservableProperty]
public partial string Code { get; set; }
[ObservableProperty]
public partial string Name { get; set; }
public LanguageInfo(string code, string name)
{
Code = code;
Name = name;
}
}
}

View File

@@ -9,7 +9,7 @@ namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
private const float _animationDuration = 0.5f;
private const float _animationDuration = 0.3f;
public ValueTransition<float> AngleTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class TranslateResponse
{
[JsonPropertyName("translatedText")]
public string TranslatedText { get; set; }
}
}

View File

@@ -5,6 +5,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Renderer"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
@@ -14,5 +15,15 @@
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
<Grid
Margin="36"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Visibility="{x:Bind ViewModel.IsTranslating, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<FontIcon
x:Name="RotatingIcon"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE8C1;" />
</Grid>
</Grid>
</UserControl>

View File

@@ -3,6 +3,7 @@
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
namespace BetterLyrics.WinUI3.Renderer
{

View File

@@ -12,6 +12,8 @@ namespace BetterLyrics.WinUI3.Serialization
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<DetectLanguageResult>))]
[JsonSerializable(typeof(TranslateResponse))]
[JsonSerializable(typeof(JsonElement))]
[JsonSourceGenerationOptions(WriteIndented = true)]
internal partial class SourceGenerationContext : JsonSerializerContext { }

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
public interface ILibreTranslateService
{
Task<string> TranslateAsync(string text, CancellationToken? token);
}
}

View File

@@ -9,7 +9,7 @@ namespace BetterLyrics.WinUI3.Services
{
public interface IMusicSearchService
{
Task <byte[]> SearchAlbumArtAsync(string title, string artist, string album);
Task <byte[]?> SearchAlbumArtAsync(string title, string artist, string album);
Task<string?> SearchLyricsAsync(
string title,

View File

@@ -37,6 +37,8 @@ namespace BetterLyrics.WinUI3.Services
bool AutoLockOnDesktopMode { get; set; }
string LibreTranslateServer { get; set; }
int SelectedTargetLanguageIndex { get; set; }
// Lyrics lib
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
@@ -48,12 +50,20 @@ namespace BetterLyrics.WinUI3.Services
int LyricsBlurAmount { get; set; }
Color LyricsCustomFontColor { get; set; }
Color LyricsCustomBgFontColor { get; set; }
Color LyricsCustomFgFontColor { get; set; }
Color LyricsCustomStrokeFontColor { get; set; }
LyricsFontColorType LyricsFontColorType { get; set; }
LyricsFontColorType LyricsBgFontColorType { get; set; }
LyricsFontColorType LyricsFgFontColorType { get; set; }
LyricsFontColorType LyricsStrokeFontColorType { get; set; }
int LyricsFontSize { get; set; }
ElementTheme LyricsBackgroundTheme { get; set; }
int LyricsFontStrokeWidth { get; set; }
LyricsFontWeight LyricsFontWeight { get; set; }
LineRenderingType LyricsGlowEffectScope { get; set; }
@@ -65,5 +75,11 @@ namespace BetterLyrics.WinUI3.Services
List<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
int LyricsVerticalEdgeOpacity { get; set; }
bool IgnoreFullscreenWindow { get; set; }
bool IsTranslationEnabled { get; set; }
LyricsDisplayType PreferredDisplayType { get; set; }
}
}

View File

@@ -0,0 +1,78 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
{
public class LibreTranslateService : ILibreTranslateService
{
private readonly ISettingsService _settingsService;
private readonly HttpClient _httpClient;
public LibreTranslateService(ISettingsService settingsService)
{
_settingsService = settingsService;
_httpClient = new HttpClient();
}
public async Task<string> TranslateAsync(string text, CancellationToken? token)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new ArgumentException("Text and target language must be provided.");
}
string targetLangCode = AppInfo.GetAllTranslationLanguagesInfo()[_settingsService.SelectedTargetLanguageIndex].Code;
string originalLangCode = await DetectLanguageCode(text);
token?.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(originalLangCode) || originalLangCode == targetLangCode)
{
return text; // No translation needed
}
var url = $"{_settingsService.LibreTranslateServer}/translate";
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
[
new("q", text),
new("source", originalLangCode),
new("target", targetLangCode),
]));
token?.ThrowIfCancellationRequested();
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
token?.ThrowIfCancellationRequested();
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
return result?.TranslatedText ?? string.Empty;
}
private async Task<string> DetectLanguageCode(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new ArgumentException("Text must be provided.");
}
var url = $"{_settingsService.LibreTranslateServer}/detect";
var response = await _httpClient.PostAsync(url, new FormUrlEncodedContent(
[
new("q", text),
]));
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var resultList = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.ListDetectLanguageResult);
return resultList?.OrderByDescending(x => x.Confidence).FirstOrDefault()?.Language ?? string.Empty;
}
}
}

View File

@@ -22,13 +22,9 @@ namespace BetterLyrics.WinUI3.Services
public class MusicSearchService : IMusicSearchService
{
private readonly HttpClient _amllTtmlDbHttpClient;
private readonly HttpClient _lrcLibHttpClient;
private readonly HttpClient _iTunesHttpClinet;
private readonly iTunesSearchManager _iTunesSearchManager;
private readonly ISettingsService _settingsService;
private readonly ILogger _logger;
@@ -44,12 +40,11 @@ namespace BetterLyrics.WinUI3.Services
);
_amllTtmlDbHttpClient = new();
_iTunesHttpClinet = new();
_iTunesSearchManager = new();
}
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
{
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-img-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
@@ -87,7 +82,7 @@ namespace BetterLyrics.WinUI3.Services
return "us";
}
public async Task<byte[]> SearchAlbumArtAsync(string title, string artist, string album)
public async Task<byte[]?> SearchAlbumArtAsync(string title, string artist, string album)
{
foreach (var folder in _settingsService.LocalLyricsFolders)
{
@@ -108,20 +103,62 @@ namespace BetterLyrics.WinUI3.Services
}
}
var resultItems = await _iTunesSearchManager.GetAlbumsAsync(album, 1, countryCode: GuessCountryCode(album, artist));
var url = resultItems.Albums.Where(al => Normalize(al.ArtistName).Contains(Normalize(artist)))
.FirstOrDefault()?.ArtworkUrl100.Replace("100x100bb.jpg", "100000x100000-999.jpg");
if (url != null)
{
return await _iTunesHttpClinet.GetByteArrayAsync(url);
}
return await SearchiTunesAlbumArtAsync(artist, album);
}
return await ImageHelper.CreateTextPlaceholderBytesAsync($"{artist} - {title}", 400, 400);
private async Task<byte[]?> SearchiTunesAlbumArtAsync(string artist, string album)
{
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
try
{
string format = ".jpg";
var cachedAlbumArt = ReadAlbumArtCache(artist, album, format, AppInfo.iTunesAlbumArtCacheDirectory);
if (cachedAlbumArt != null)
{
return cachedAlbumArt;
}
// Build the iTunes API URL
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + GuessCountryCode(album, artist) + "&entity=album";
url.Replace(" ", "-");
// Make a request to the API
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
// Parse the JSON response
var data = JsonSerializer.Deserialize(responseBody, Serialization.SourceGenerationContext.Default.JsonElement);
if (data.TryGetProperty("results", out var results) && results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
{
// Get the first result
var result = results[0];
if (result.TryGetProperty("artworkUrl100", out var artworkUrlProp))
{
string artworkUrl = artworkUrlProp.GetString()?.Replace("100x100bb.jpg", "1200x1200bb.jpg") ?? string.Empty;
var fetched = await _iTunesHttpClinet.GetByteArrayAsync(artworkUrl);
if (fetched != null && fetched.Length > 0)
{
// Write to cache
WriteAlbumArtCache(artist, album, fetched, format, AppInfo.iTunesAlbumArtCacheDirectory);
return fetched;
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error searching iTunes album art for {Artist} - {Album}", artist, album);
}
return null;
}
public async Task<string?> SearchLyricsAsync(string title, string artist, string album, double durationMs, CancellationToken token)
{
_logger.LogInformation("Searching lyrics for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
{
@@ -136,7 +173,7 @@ namespace BetterLyrics.WinUI3.Services
// Check cache first
if (provider.Provider.IsRemote())
{
cachedLyrics = ReadCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
cachedLyrics = ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
if (!string.IsNullOrWhiteSpace(cachedLyrics))
{
return cachedLyrics;
@@ -186,7 +223,7 @@ namespace BetterLyrics.WinUI3.Services
{
if (provider.Provider.IsRemote())
{
WriteCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
}
return searchedLyrics;
@@ -274,7 +311,7 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
private string? ReadCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
private string? ReadLyricsCache(string title, string artist, LyricsFormat format, string cacheFolderPath)
{
var safeArtist = SanitizeFileName(artist);
var safeTitle = SanitizeFileName(title);
@@ -286,6 +323,18 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
private byte[]? ReadAlbumArtCache(string album, string artist, string format, string cacheFolderPath)
{
var safeArtist = SanitizeFileName(artist);
var safeAlbum = SanitizeFileName(album);
var cacheFilePath = Path.Combine(cacheFolderPath, $"{safeArtist} - {safeAlbum}{format}");
if (File.Exists(cacheFilePath))
{
return File.ReadAllBytes(cacheFilePath);
}
return null;
}
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
{
// 检索本地 JSONL 索引文件,查找 rawLyricFile
@@ -339,7 +388,7 @@ namespace BetterLyrics.WinUI3.Services
return null;
// 下载歌词内容
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-img/{rawLyricFile}";
try
{
var response = await _amllTtmlDbHttpClient.GetAsync(url);
@@ -431,7 +480,7 @@ namespace BetterLyrics.WinUI3.Services
return null;
}
private void WriteCache(
private void WriteLyricsCache(
string title,
string artist,
string lyrics,
@@ -447,5 +496,22 @@ namespace BetterLyrics.WinUI3.Services
);
File.WriteAllText(cacheFilePath, lyrics);
}
private void WriteAlbumArtCache(
string album,
string artist,
byte[] img,
string format,
string cacheFolderPath
)
{
var safeArtist = SanitizeFileName(artist);
var safeAlbum = SanitizeFileName(album);
var cacheFilePath = Path.Combine(
cacheFolderPath,
$"{safeArtist} - {safeAlbum}{format}"
);
File.WriteAllBytes(cacheFilePath, img);
}
}
}

View File

@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using Windows.Graphics.Imaging;
using Windows.Media.Control;
@@ -65,7 +66,7 @@ namespace BetterLyrics.WinUI3.Services
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
}
private async void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession)
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession)
{
if (mediaSession == null || !IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId))
{
@@ -73,8 +74,20 @@ namespace BetterLyrics.WinUI3.Services
}
else
{
MediaManager_OnAnyMediaPropertyChanged(mediaSession, await mediaSession.ControlSession.TryGetMediaPropertiesAsync());
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
_dispatcherQueue.TryEnqueue(async () =>
{
try
{
var props = await mediaSession.ControlSession.TryGetMediaPropertiesAsync();
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
}
catch (Exception ex)
{
_logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed");
SendNullMessages();
}
});
}
}
@@ -137,37 +150,49 @@ namespace BetterLyrics.WinUI3.Services
SourceAppUserModelId = id,
};
byte[] bytes;
byte[]? bytes;
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{
bytes = await ImageHelper.ToByteArrayAsync(
streamReference
);
token.ThrowIfCancellationRequested();
}
else
{
bytes = await _musicSearchService.SearchAlbumArtAsync(
songInfo.Title,
songInfo.Artist,
songInfo.Album
);
token.ThrowIfCancellationRequested();
}
var decoder = await ImageHelper.GetDecoderFromByte(bytes);
bytes = await _musicSearchService.SearchAlbumArtAsync(
songInfo.Title,
songInfo.Artist,
songInfo.Album
);
token.ThrowIfCancellationRequested();
if (bytes == null)
{
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{
bytes = await ImageHelper.ToByteArrayAsync(streamReference);
token.ThrowIfCancellationRequested();
}
else
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{songInfo.Artist} - {songInfo.Title}", 400, 400);
token.ThrowIfCancellationRequested();
}
}
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
songInfo.AlbumArtSwBitmap?.Dispose();
songInfo.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
songInfo.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
if (!token.IsCancellationRequested)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(songInfo));
});
() =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(songInfo));
});
}
}
catch (OperationCanceledException) { }

View File

@@ -1,8 +1,5 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Linq;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
@@ -10,6 +7,9 @@ using BetterLyrics.WinUI3.Serialization;
using CommunityToolkit.WinUI.Helpers;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Storage;
using Windows.UI;
@@ -17,7 +17,9 @@ namespace BetterLyrics.WinUI3.Services
{
public class SettingsService : ISettingsService
{
public const string LyricsCustomFontColorKey = "LyricsCustomFontColor";
public const string LyricsCustomBgFontColorKey = "LyricsCustomBgFontColor";
public const string LyricsCustomFgFontColorKey = "LyricsCustomFgFontColor";
public const string LyricsCustomStrokeFontColorKey = "LyricsCustomStrokeFontColor";
// App behavior
@@ -50,7 +52,13 @@ namespace BetterLyrics.WinUI3.Services
private const string LyricsAlignmentTypeKey = "TextAlignmentType";
private const string SongInfoAlignmentTypeKey = "SongInfoAlignmentType";
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
private const string LyricsFontColorTypeKey = "LyricsFontColorType";
private const string LyricsBgFontColorTypeKey = "_lyricsBgFontColorType";
private const string LyricsFgFontColorTypeKey = "LyricsFgFontColorType";
private const string LyricsStrokeFontColorTypeKey = "LyricsStrokeFontColorType";
private const string LyricsFontStrokeWidthKey = "LyricsFontStrokeWidth";
private const string LyricsFontSizeKey = "LyricsFontSize";
private const string LyricsFontWeightKey = "LyricsFontWeightKey";
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
@@ -60,6 +68,16 @@ namespace BetterLyrics.WinUI3.Services
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
private const string LibreTranslateServerKey = "LibreTranslateServer";
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
private readonly ApplicationDataContainer _localSettings;
public SettingsService()
@@ -94,15 +112,15 @@ namespace BetterLyrics.WinUI3.Services
// App appearance
SetDefault(LanguageKey, (int)Language.FollowSystem);
SetDefault(DesktopWindowHeightKey, 500);
SetDefault(DesktopWindowHeightKey, 600);
SetDefault(DesktopWindowLeftKey, 200);
SetDefault(DesktopWindowTopKey, 200);
SetDefault(DesktopWindowWidthKey, 800);
SetDefault(DesktopWindowWidthKey, 1200);
SetDefault(StandardWindowHeightKey, 800);
SetDefault(StandardWindowLeftKey, 200);
SetDefault(StandardWindowTopKey, 200);
SetDefault(StandardWindowWidthKey, 1000);
SetDefault(StandardWindowWidthKey, 1600);
SetDefault(AutoLockOnDesktopModeKey, false);
// App behavior
@@ -110,22 +128,53 @@ namespace BetterLyrics.WinUI3.Services
// Album art
SetDefault(IsCoverOverlayEnabledKey, true);
SetDefault(IsDynamicCoverOverlayEnabledKey, true);
SetDefault(CoverOverlayOpacityKey, 75); // 100 % = 1.0
SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.0
SetDefault(CoverOverlayBlurAmountKey, 200);
SetDefault(CoverImageRadiusKey, 24); // 24 %
SetDefault(CoverImageRadiusKey, 12); // 12 %
// Lyrics
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
SetDefault(LyricsBlurAmountKey, 5);
SetDefault(LyricsFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsCustomFontColorKey, Colors.White.ToInt());
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Default);
SetDefault(LyricsBgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsFgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsStrokeFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsCustomBgFontColorKey, Colors.White.ToInt());
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
SetDefault(LyricsFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
SetDefault(LyricsVerticalEdgeOpacityKey, 0);
SetDefault(IsLyricsGlowEffectEnabledKey, true);
SetDefault(LyricsGlowEffectScopeKey, (int)LineRenderingType.CurrentCharOnly);
SetDefault(IsFanLyricsEnabledKey, false);
SetDefault(LibreTranslateServerKey, "");
SetDefault(IsTranslationEnabledKey, false);
SetDefault(SelectedTargetLanguageIndexKey, 6);
SetDefault(LyricsFontStrokeWidthKey, 3);
SetDefault(IgnoreFullscreenWindowKey, false);
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
}
public LyricsDisplayType PreferredDisplayType
{
get => (LyricsDisplayType)GetValue<int>(PreferredDisplayTypeKey);
set => SetValue(PreferredDisplayTypeKey, (int)value);
}
public ElementTheme LyricsBackgroundTheme
{
get => (ElementTheme)GetValue<int>(LyricsBackgroundThemeKey);
set => SetValue(LyricsBackgroundThemeKey, (int)value);
}
public AutoStartWindowType AutoStartWindowType
@@ -271,16 +320,46 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(LyricsBlurAmountKey, value);
}
public Color LyricsCustomFontColor
public Color LyricsCustomBgFontColor
{
get => GetValue<int>(LyricsCustomFontColorKey)!.ToColor();
set => SetValue(LyricsCustomFontColorKey, value.ToInt());
get => GetValue<int>(LyricsCustomBgFontColorKey)!.ToColor();
set => SetValue(LyricsCustomBgFontColorKey, value.ToInt());
}
public LyricsFontColorType LyricsFontColorType
public Color LyricsCustomFgFontColor
{
get => (LyricsFontColorType)GetValue<int>(LyricsFontColorTypeKey);
set => SetValue(LyricsFontColorTypeKey, (int)value);
get => GetValue<int>(LyricsCustomFgFontColorKey)!.ToColor();
set => SetValue(LyricsCustomFgFontColorKey, value.ToInt());
}
public Color LyricsCustomStrokeFontColor
{
get => GetValue<int>(LyricsCustomStrokeFontColorKey)!.ToColor();
set => SetValue(LyricsCustomStrokeFontColorKey, value.ToInt());
}
public LyricsFontColorType LyricsBgFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsBgFontColorTypeKey);
set => SetValue(LyricsBgFontColorTypeKey, (int)value);
}
public LyricsFontColorType LyricsFgFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsFgFontColorTypeKey);
set => SetValue(LyricsFgFontColorTypeKey, (int)value);
}
public LyricsFontColorType LyricsStrokeFontColorType
{
get => (LyricsFontColorType)GetValue<int>(LyricsStrokeFontColorTypeKey);
set => SetValue(LyricsStrokeFontColorTypeKey, (int)value);
}
public int LyricsFontStrokeWidth
{
get => GetValue<int>(LyricsFontStrokeWidthKey);
set => SetValue(LyricsFontStrokeWidthKey, value);
}
public int LyricsFontSize
@@ -347,6 +426,30 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(LyricsVerticalEdgeOpacityKey, value);
}
public string LibreTranslateServer
{
get => GetValue<string>(LibreTranslateServerKey)!;
set => SetValue(LibreTranslateServerKey, value);
}
public bool IsTranslationEnabled
{
get => GetValue<bool>(IsTranslationEnabledKey);
set => SetValue(IsTranslationEnabledKey, value);
}
public int SelectedTargetLanguageIndex
{
get => GetValue<int>(SelectedTargetLanguageIndexKey);
set => SetValue(SelectedTargetLanguageIndexKey, value);
}
public bool IgnoreFullscreenWindow
{
get => GetValue<bool>(IgnoreFullscreenWindowKey);
set => SetValue(IgnoreFullscreenWindowKey, value);
}
private T? GetValue<T>(string key)
{
if (_localSettings.Values.TryGetValue(key, out object? value))

View File

@@ -139,7 +139,7 @@
<value>Add a folder</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>Lyrics window theme</value>
<value>Lyrics background theme</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>Language</value>
@@ -147,6 +147,9 @@
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Colored)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Colored)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>Light</value>
</data>
@@ -301,7 +304,7 @@
<value>Welcome to BetterLyrics</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>Let's setup lyrics database now</value>
<value>Hover the mouse over the top or bottom area of the app to display more function options</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>No music playing now</value>
@@ -321,12 +324,18 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Including log files, network lyrics cache</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>Font color</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>Font color (Non-current playback area)</value>
</data>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>Font color (Current playback area)</value>
</data>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Grayed)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>Adaptive to lyrics background (Grayed)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>Album art area style</value>
</data>
@@ -531,6 +540,9 @@
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>Custom</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>Custom</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>Lyrics style and effect</value>
</data>
@@ -579,4 +591,67 @@
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>Lyrics timeline offset (ms)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>Configure translation services</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>Server address</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>Test server</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>Target language</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>Translation service powered by LibreTranslate</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>Visit https://github.com/LibreTranslate/LibreTranslate for installation instructions and more information (this software is not affiliated with this translation service in any way)</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>Server test successful</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>Server test failed</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>Lyrics stroke width (Desktop mode only)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>Follow system</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>Automatic startup</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>Lyrics stroke color (Desktop mode only)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>Always stay on top of fullscreen applications</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>Force this app to appear on top of full-screen apps when docked or desktop mode is enabled</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>More</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>Lyrics timeline offset</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>Translate</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>Display type</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>Settings</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>Translate server is not set, please configure it in settings first</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>Will automatically reset to 0 when switching songs</value>
</data>
</root>

View File

@@ -139,7 +139,7 @@
<value>フォルダーを追加します</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>歌詞ウィンドウのテーマ</value>
<value>歌詞の背景テーマ</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>言語</value>
@@ -147,6 +147,9 @@
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>歌詞の背景に適応する(色付き)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>歌詞の背景に適応する(色付き)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>ライト</value>
</data>
@@ -301,7 +304,7 @@
<value>BetterLyrics へようこそ</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>今すぐ歌詞データベースをセットアップしましょう</value>
<value>マウスをアプリの上または下部の領域にホバリングして、より多くの機能オプションを表示します</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>今は音楽が再生されていません</value>
@@ -321,12 +324,18 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>ログファイル、ネットワーク歌詞キャッシュを含む</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>フォントカラー</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>フォントカラー(非電流再生エリア)</value>
</data>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>フォントカラー(現在の再生エリア)</value>
</data>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>歌詞の背景に適応する(灰色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>歌詞の背景に適応する(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>アルバムエリアスタイル</value>
</data>
@@ -531,6 +540,9 @@
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>カスタマイズ</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>カスタマイズ</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌詞のスタイルと効果</value>
</data>
@@ -579,4 +591,67 @@
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌詞タイムラインオフセット(ミリ秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>翻訳サービスを構成します</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>サーバーアドレス</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>テストサーバー</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>ターゲット言語</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>LibreTranslate を搭載した翻訳サービス</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>https://github.com/LibreTranslate/LibreTranslate にアクセスしてください。</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>サーバーテストが成功しました</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>サーバーテストに失敗しました</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>歌詞ストローク幅(デスクトップモードのみ)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>システムをフォローします</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自動起動</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌詞ストロークカラー(デスクトップモードのみ)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>常にフルスクリーンアプリケーションを常に把握してください</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>このアプリは、ドッキングまたはデスクトップモードが有効になっているときにフルスクリーンアプリの上に表示されます</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>もっと</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>歌詞タイムラインオフセット</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>翻訳する</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>表示タイプ</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>曲を切り替えると、0 に自動的にリセットされます</value>
</data>
</root>

View File

@@ -139,7 +139,7 @@
<value>폴더를 추가하십시오</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>가사 테마</value>
<value>가사 배경 테마</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>언어</value>
@@ -147,6 +147,9 @@
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>가사 배경 (색상)에 적응</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>가사 배경 (색상)에 적응</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>빛</value>
</data>
@@ -301,7 +304,7 @@
<value>Betterlyrics에 오신 것을 환영합니다</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>지금 가사 데이터베이스를 설정합시다</value>
<value>더 많은 기능 옵션을 표시하려면 앱의 상단 또는 하단 영역 위로 마우스를 가져옵니다.</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>지금 음악이 재생되지 않습니다</value>
@@ -321,12 +324,18 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>로그 파일, 네트워크 가사 캐시 포함</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>글꼴 색상</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>글꼴 색상 (비 전류 재생 영역)</value>
</data>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>글꼴 색상 (현재 재생 영역)</value>
</data>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>가사 배경 (회색)에 적응</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>가사 배경 (회색)에 적응</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>앨범 영역 스타일</value>
</data>
@@ -531,6 +540,9 @@
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>사용자 정의하십시오</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>사용자 정의하십시오</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>가사 스타일과 효과</value>
</data>
@@ -579,4 +591,67 @@
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>가사 타임 라인 오프셋 (밀리초)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>번역 서비스를 구성하십시오</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>서버 주소</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>테스트 서버</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>대상 언어</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>LibreTranslate 가 구동하는 번역 서비스</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>설치 지침 및 자세한 정보는 https://github.com/LibreTranslate/LibreTranslate 를 방문하십시오 (이 소프트웨어는이 번역 서비스와 제휴하지 않습니다).</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>서버 테스트 성공</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>서버 테스트가 실패했습니다</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>가사 뇌졸중 너비 (데스크탑 모드 만)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>시스템을 따르십시오</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>자동 시작</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>가사 스트로크 컬러 (데스크탑 모드 만)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>항상 전체 화면 응용 프로그램 위에 머물러 있습니다</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>도킹 또는 데스크탑 모드가 활성화 될 때이 앱이 전체 화면 앱 위에 나타나도록 강요</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>더</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>가사 타임 라인 오프셋</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>번역하다</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>디스플레이 유형</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>설정</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>노래를 전환 할 때 자동으로 0 으로 재설정됩니다</value>
</data>
</root>

View File

@@ -139,7 +139,7 @@
<value>添加文件夹</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>歌词窗口主题</value>
<value>歌词背景主题</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>语言</value>
@@ -147,6 +147,9 @@
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>适应歌词背景(彩色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>适应歌词背景(彩色)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>浅色</value>
</data>
@@ -301,7 +304,7 @@
<value>欢迎使用 BetterLyrics</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>现在就来初始化歌词数据库吧</value>
<value>将鼠标悬停在应用程序的顶部或底部区域以显示更多功能选项</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>当前没有正在播放的音乐</value>
@@ -321,12 +324,18 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日志文件,网络歌词缓存</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字体颜色</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>字体颜色(非当前播放区域)</value>
</data>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>字体颜色(当前播放区域)</value>
</data>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>适应歌词背景(灰色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>适应歌词背景(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>专辑区域样式</value>
</data>
@@ -531,6 +540,9 @@
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>自定义</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>自定义</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌词样式与动效</value>
</data>
@@ -579,4 +591,67 @@
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌词时间轴偏移(毫秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>配置翻译服务</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>服务器地址</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>测试服务器</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>目标语言</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>翻译服务由 LibreTranslate 驱动</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>访问 https://github.com/LibreTranslate/LibreTranslate 获取安装教程及更多信息(本软件与该翻译服务无任何联系)</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>服务器测试成功</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>服务器测试失败</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>歌词描边宽度(仅桌面模式)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自动启动</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌词描边颜色(仅桌面模式)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>始终显示在全屏应用上方</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>当启用停靠模式或桌面模式时强制将本应用显示在全屏应用的上方</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>更多</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>歌词时间偏移</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>翻译</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>显示类型</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>设置</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>未设置翻译服务器,请先在设置中进行配置</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>切换歌曲时将自动重置为 0</value>
</data>
</root>

View File

@@ -139,7 +139,7 @@
<value>新增資料夾</value>
</data>
<data name="SettingsPageTheme.Header" xml:space="preserve">
<value>歌詞窗口主題</value>
<value>歌詞背景主題</value>
</data>
<data name="SettingsPageLanguage.Header" xml:space="preserve">
<value>語言</value>
@@ -147,6 +147,9 @@
<data name="SettingsPageLyricsFontColorAdaptiveColored.Content" xml:space="preserve">
<value>適應歌詞背景(彩色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveColored.Content" xml:space="preserve">
<value>適應歌詞背景(彩色)</value>
</data>
<data name="SettingsPageLight.Content" xml:space="preserve">
<value>淺色</value>
</data>
@@ -301,7 +304,7 @@
<value>歡迎使用 BetterLyrics</value>
</data>
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
<value>現在就來初始化歌詞資料庫吧</value>
<value>將鼠標懸停在應用程序的頂部或底部區域以顯示更多功能選項</value>
</data>
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
<value>目前沒有正在播放的音樂</value>
@@ -321,12 +324,18 @@
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>包括日誌文件,網絡歌詞緩存</value>
</data>
<data name="SettingsPageLyricsFontColor.Header" xml:space="preserve">
<value>字體顏色</value>
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
<value>字體顏色(非當前播放區域)</value>
</data>
<data name="SettingsPageLyricsFgFontColor.Header" xml:space="preserve">
<value>字體顏色(當前播放區域)</value>
</data>
<data name="SettingsPageLyricsFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>適應歌詞背景(灰色)</value>
</data>
<data name="SettingsPageLyricsFgFontColorAdaptiveGrayed.Content" xml:space="preserve">
<value>適應歌詞背景(灰色)</value>
</data>
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
<value>专辑区域样式</value>
</data>
@@ -531,6 +540,9 @@
<data name="SettingsPageLyricsFontColorCustom.Content" xml:space="preserve">
<value>自定義</value>
</data>
<data name="SettingsPageLyricsFgFontColorCustom.Content" xml:space="preserve">
<value>自定義</value>
</data>
<data name="SettingsPageLyrics.Content" xml:space="preserve">
<value>歌詞樣式與動效</value>
</data>
@@ -579,4 +591,67 @@
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌詞時間軸偏移(毫秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>配置翻譯服務</value>
</data>
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
<value>服務器地址</value>
</data>
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
<value>測試服務器</value>
</data>
<data name="SettingsPageTargetLanguage.Header" xml:space="preserve">
<value>目標語言</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>翻译服务由 LibreTranslate 驱动</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>造訪 https://github.com/LibreTranslate/LibreTranslate 以取得安裝教學及更多資訊(本軟體與此翻譯服務無任何關聯)</value>
</data>
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
<value>服務器測試成功</value>
</data>
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
<value>服務器測試失敗</value>
</data>
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
<value>歌詞描邊寬度(僅桌面模式)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自動啟動</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌词描边颜色(仅桌面模式)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Header" xml:space="preserve">
<value>始終顯示在全螢幕應用程式上方</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.Description" xml:space="preserve">
<value>啟用停靠模式或桌面模式時強制將本應用程式顯示在全螢幕應用程式的上方</value>
</data>
<data name="HostWindowMoreButtonToolTip.Content" xml:space="preserve">
<value>更多</value>
</data>
<data name="LyricsPageTimelineOffsetButtonToolTip.Content" xml:space="preserve">
<value>歌詞時間偏移</value>
</data>
<data name="LyricsPageTranslationButtonToolTip.Content" xml:space="preserve">
<value>翻譯</value>
</data>
<data name="LyricsPageDisplayTypeButtonToolTip.Content" xml:space="preserve">
<value>顯示類型</value>
</data>
<data name="LyricsPageSettingsButtonToolTip.Content" xml:space="preserve">
<value>設定</value>
</data>
<data name="TranslateServerNotSet" xml:space="preserve">
<value>未設定翻譯伺服器,請先在設定中進行配置</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<value>將在切換歌曲時自動重設為 0</value>
</data>
</root>

View File

@@ -24,6 +24,8 @@ namespace BetterLyrics.WinUI3.ViewModels
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
{
LyricsFontSize = _settingsService.LyricsFontSize;
IsTranslationEnabled = _settingsService.IsTranslationEnabled;
PreferredDisplayType = _settingsService.PreferredDisplayType;
_playbackService = playbackService;
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
@@ -56,7 +58,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial int LyricsFontSize { get; set; }
[ObservableProperty]
public partial LyricsDisplayType? PreferredDisplayType { get; set; } = LyricsDisplayType.SplitView;
public partial LyricsDisplayType PreferredDisplayType { get; set; }
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; } = null;
@@ -69,6 +71,16 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial bool IsTranslationEnabled { get; set; } = false;
partial void OnIsTranslationEnabledChanged(bool value)
{
_settingsService.IsTranslationEnabled = value;
}
partial void OnPreferredDisplayTypeChanged(LyricsDisplayType value)
{
_settingsService.PreferredDisplayType = value;
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is LyricsWindowViewModel)
@@ -112,7 +124,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode;
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode ?? LyricsDisplayType.SplitView;
}
}

View File

@@ -9,6 +9,7 @@ using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
@@ -32,16 +33,22 @@ namespace BetterLyrics.WinUI3.ViewModels
DrawBlurredLyrics(control, blurredLyricsDs);
}
if (_lastAlbumArtSwBitmap != null && _lastAlbumArtCanvasBitmap == null)
{
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
}
if (_albumArtSwBitmap != null && _albumArtCanvasBitmap == null)
{
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
}
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
DrawAlbumArtBackground(control, combinedDs);
if (_isDockMode)
{
DrawImmersiveBackground(control, combinedDs);
}
combinedDs.DrawImage(blurredLyrics);
if (_isDesktopMode)
@@ -54,15 +61,13 @@ namespace BetterLyrics.WinUI3.ViewModels
}
DrawAlbumArt(control, ds);
DrawTitleAndArtist(control, ds);
if (_isDebugOverlayEnabled)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
?.SafeGet(_playingLineIndex);
if (currentPlayingLine != null)
{
@@ -73,36 +78,35 @@ namespace BetterLyrics.WinUI3.ViewModels
out float charProgress
);
ds.DrawText(
$"[DEBUG]\n" +
$"Cur playing {currentPlayingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Cur time {_totalTime + _positionOffset}\n" +
$"Lang size {_multiLangLyrics.Count}\n" +
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
new Vector2(10, 10),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
);
//ds.DrawText(
// $"[DEBUG]\n" +
// $"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
// $"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
// $"Cur time {_totalTime + _positionOffset}\n" +
// $"Lang size {_multiLangLyrics.Count}\n" +
// $"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
// new Vector2(10, 10),
// ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
//);
//for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
//{
// LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
// if (line != null)
// {
// ds.DrawText(
// $"[{i}] {line.Text} {line.ScaleTransition.Value}",
// new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
// ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
// );
// }
//}
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line != null)
{
ds.DrawText(
$"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
);
}
}
}
}
}
private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, SoftwareBitmap swBitmap, float opacity)
private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
{
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, swBitmap);
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
@@ -111,26 +115,40 @@ namespace BetterLyrics.WinUI3.ViewModels
float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
// Source: https://zhuanlan.zhihu.com/p/37178216
float bright = _lyricsBgBrightnessTransition.Value / 1f * 2f; // 明度参数范围在0.0f到2.0f之间
float whiteX = Math.Min(2 - bright, 1);
float whiteY = 1f;
float blackX = Math.Max(1 - bright, 0);
float blackY = 0f;
ds.DrawImage(new OpacityEffect
{
Source = new ScaleEffect
Source = new BrightnessEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
WhitePoint = new Vector2(whiteX, whiteY),
BlackPoint = new Vector2(blackX, blackY),
},
Opacity = opacity,
}, new Vector2(x, y)
);
}
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, SoftwareBitmap swBitmap, float opacity)
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
{
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, swBitmap);
if (opacity == 0) return;
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.1f) return;
if (scaleFactor < 0.01f) return;
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
@@ -161,16 +179,16 @@ namespace BetterLyrics.WinUI3.ViewModels
{
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_lastAlbumArtSwBitmap != null)
if (_lastAlbumArtCanvasBitmap != null)
{
DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtSwBitmap, 1 - _albumArtBgTransition.Value);
DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
if (_albumArtSwBitmap != null)
if (_albumArtCanvasBitmap != null)
{
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtSwBitmap, _albumArtBgTransition.Value);
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
using var coverOverlayEffect = new OpacityEffect
@@ -191,13 +209,13 @@ namespace BetterLyrics.WinUI3.ViewModels
{
using var albumArt = new CanvasCommandList(control.Device);
using var albumArtDs = albumArt.CreateDrawingSession();
if (_albumArtSwBitmap != null)
if (_albumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _albumArtSwBitmap, _albumArtBgTransition.Value);
DrawForegroundImgae(control, albumArtDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
if (_lastAlbumArtSwBitmap != null)
if (_lastAlbumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _lastAlbumArtSwBitmap, 1 - _albumArtBgTransition.Value);
DrawForegroundImgae(control, albumArtDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
using var opacity = new CanvasCommandList(control.Device);
@@ -242,20 +260,18 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.DrawTextLayout(
titleLayout,
new Vector2(_albumArtXTransition.Value, _titleY),
_fontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
ds.DrawTextLayout(
artistLayout,
new Vector2(_albumArtXTransition.Value, _titleY + (float)titleLayout.LayoutBounds.Height),
_fontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
}
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
?.SafeGet(_playingLineIndex);
if (currentPlayingLine == null)
{
@@ -314,157 +330,176 @@ namespace BetterLyrics.WinUI3.ViewModels
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, _canvasYScrollTransition.Value + _canvasHeight / 2);
// Create the original lyrics line
using var lyrics = new CanvasCommandList(control.Device);
using var lyricsDs = lyrics.CreateDrawingSession();
lyricsDs.DrawTextLayout(textLayout, position, _fontColor);
// Create the background lyrics line with stroke and fill
using var bgLyrics = new CanvasCommandList(control.Device);
using var bgLyricsDs = bgLyrics.CreateDrawingSession();
// Create the foreground lyrics line with stroke and fill
using var fgLyrics = new CanvasCommandList(control.Device);
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
// 创建文字几何体
using (var textGeometry = CanvasGeometry.CreateText(textLayout))
{
if (_isDesktopMode)
{
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
}
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
}
// Mock gradient blurred lyrics layer
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
// Current line will not be blurred
ds.DrawImage(
new GaussianBlurEffect
{
Source = new OpacityEffect { Source = lyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
BlurAmount = line.BlurAmountTransition.Value,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
}
);
// 再叠加当前行歌词层
using var mask = new CanvasCommandList(control.Device);
using var maskDs = mask.CreateDrawingSession();
using var highlightMask = new CanvasCommandList(control.Device);
using var highlightMaskDs = highlightMask.CreateDrawingSession();
if (i == currentPlayingLineIndex)
if (line.HighlightOpacityTransition.Value != 0)
{
GetLinePlayingProgress(
line,
out int charStartIndex,
out int charLength,
out float charProgress
);
var regions = textLayout.GetCharacterRegions(0, charStartIndex);
var highlightRegion = textLayout
.GetCharacterRegions(charStartIndex, charLength)
.FirstOrDefault();
if (regions.Length > 0)
// 再叠加高亮行歌词层(前景歌词层)
using var mask = new CanvasCommandList(control.Device);
using var maskDs = mask.CreateDrawingSession();
using var highlightMask = new CanvasCommandList(control.Device);
using var highlightMaskDs = highlightMask.CreateDrawingSession();
if (i == _playingLineIndex)
{
// Draw the mask for the current line
for (int j = 0; j < regions.Length; j++)
GetLinePlayingProgress(
line,
out int charStartIndex,
out int charLength,
out float charProgress
);
var regions = textLayout.GetCharacterRegions(0, charStartIndex);
var highlightRegion = textLayout
.GetCharacterRegions(charStartIndex, charLength)
.FirstOrDefault();
if (regions.Length > 0)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
maskDs.FillRectangle(rect, Colors.Black);
}
}
float highlightTotalWidth = (float)highlightRegion.LayoutBounds.Width;
// Draw the highlight for the current character
float highlightWidth = highlightTotalWidth * charProgress;
float fadingWidth = (float)highlightRegion.LayoutBounds.Height / 2;
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// Brushes
using var fadeInBrush = GetHorizontalFillBrush(
control,
[(0f, 0f), (1f, 1f)],
(float)highlightRect.Right - fadingWidth,
fadingWidth
);
using var fadeOutBrush = GetHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(float)highlightRect.Right,
fadingWidth
);
maskDs.FillRectangle(highlightRect, Colors.White);
maskDs.FillRectangle(fadeOutRect, fadeOutBrush);
highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
highlightMaskDs.FillRectangle(fadeOutRect, fadeOutBrush);
}
else
{
float height = 0f;
var regions = textLayout.GetCharacterRegions(0, string.Join("", line.CharTimings.Select(x => x.Text)).Length);
if (regions.Length > 0)
{
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
}
maskDs.FillRectangle(
new Rect(
textLayout.LayoutBounds.X,
position.Y,
textLayout.LayoutBounds.Width,
height
),
Colors.White
);
}
ds.DrawImage(
new OpacityEffect
{
Source = new BlendEffect
{
Background = IsLyricsGlowEffectEnabled
? new GaussianBlurEffect
{
Source = new AlphaMaskEffect
{
Source = lyrics,
AlphaMask = LyricsGlowEffectScope switch
{
LineRenderingType.UntilCurrentChar => mask,
LineRenderingType.CurrentCharOnly => highlightMask,
_ => mask,
},
},
BlurAmount = _lyricsGlowEffectAmount,
Optimization = EffectOptimization.Quality,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
// Draw the mask for the current line
for (int j = 0; j < regions.Length; j++)
{
Source = lyrics,
AlphaMask = mask,
},
},
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
maskDs.FillRectangle(rect, Colors.Black);
}
}
float highlightTotalWidth = (float)highlightRegion.LayoutBounds.Width;
// Draw the highlight for the current character
float highlightWidth = highlightTotalWidth * charProgress;
float fadingWidth = (float)highlightRegion.LayoutBounds.Height / 2;
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// Brushes
using var fadeInBrush = GetHorizontalFillBrush(
control,
[(0f, 0f), (1f, 1f)],
(float)highlightRect.Right - fadingWidth,
fadingWidth
);
using var fadeOutBrush = GetHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(float)highlightRect.Right,
fadingWidth
);
maskDs.FillRectangle(highlightRect, Colors.White);
maskDs.FillRectangle(fadeOutRect, fadeOutBrush);
highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
highlightMaskDs.FillRectangle(fadeOutRect, fadeOutBrush);
}
);
else
{
float height = 0f;
var regions = textLayout.GetCharacterRegions(0, string.Join("", line.CharTimings.Select(x => x.Text)).Length);
if (regions.Length > 0)
{
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
}
maskDs.FillRectangle(
new Rect(
textLayout.LayoutBounds.X,
position.Y,
textLayout.LayoutBounds.Width,
height
),
Colors.White
);
}
ds.DrawImage(
new OpacityEffect
{
Source = new BlendEffect
{
Background = IsLyricsGlowEffectEnabled
? new GaussianBlurEffect
{
Source = new AlphaMaskEffect
{
Source = fgLyrics,
AlphaMask = LyricsGlowEffectScope switch
{
LineRenderingType.UntilCurrentChar => mask,
LineRenderingType.CurrentCharOnly => highlightMask,
_ => mask,
},
},
BlurAmount = _lyricsGlowEffectAmount,
Optimization = EffectOptimization.Quality,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
{
Source = fgLyrics,
AlphaMask = mask,
},
},
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
}
);
}
// Reset scale
ds.Transform = Matrix3x2.Identity;

View File

@@ -2,6 +2,8 @@
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using System;
using System.Collections.ObjectModel;
using Windows.UI;
@@ -18,6 +20,7 @@ namespace BetterLyrics.WinUI3.ViewModels
IRecipient<PropertyChangedMessage<TextAlignmentType>>,
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
IRecipient<PropertyChangedMessage<LineRenderingType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
{
@@ -28,6 +31,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
RefreshLyricsAsync();
}
}
@@ -40,6 +44,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsSearchProvidersInfo))
{
// Lyrics search providers info changed, re-fetch lyrics
_logger.LogInformation("Lyrics search providers info changed, refreshing lyrics.");
RefreshLyricsAsync();
}
}
@@ -66,6 +71,7 @@ namespace BetterLyrics.WinUI3.ViewModels
else if (message.PropertyName == nameof(SettingsPageViewModel.IsFanLyricsEnabled))
{
_isFanLyricsEnabled = message.NewValue;
_isLayoutChanged = true;
}
}
else if (message.Sender is LyricsWindowViewModel)
@@ -84,6 +90,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (message.PropertyName == nameof(LyricsPageViewModel.IsTranslationEnabled))
{
_isTranslationEnabled = message.NewValue;
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _isTranslationEnabled);
UpdateTranslationsAsync();
}
}
@@ -96,16 +103,25 @@ namespace BetterLyrics.WinUI3.ViewModels
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
{
_immersiveBgTransition.StartTransition(message.NewValue);
_lyricsWindowBgColor = message.NewValue;
_adaptiveFontColor = Helper.ColorHelper.GetForegroundColor(_lyricsWindowBgColor);
_environmentalColor = message.NewValue;
UpdateFontColor();
}
}
else if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomFontColor))
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomBgFontColor))
{
_customFontColor = message.NewValue;
_customBgFontColor = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomFgFontColor))
{
_customFgFontColor = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomStrokeFontColor))
{
_customStrokeFontColor = message.NewValue;
UpdateFontColor();
}
}
@@ -141,15 +157,27 @@ namespace BetterLyrics.WinUI3.ViewModels
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsVerticalEdgeOpacity))
{
LyricsVerticalEdgeOpacity = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBlurAmount))
{
LyricsBlurAmount = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
{
LyricsFontSize = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SelectedTargetLanguageIndex))
{
_targetLanguageIndex = message.NewValue;
_logger.LogInformation("Target language index changed: {Index}", _targetLanguageIndex);
UpdateTranslationsAsync();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontStrokeWidth))
{
_lyricsFontStrokeWidth = message.NewValue;
}
}
else if (message.Sender is LyricsPageViewModel)
{
@@ -196,9 +224,20 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontColorType))
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontColorType))
{
LyricsFontColorType = message.NewValue;
_lyricsBgFontColorType = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFgFontColorType))
{
_lyricsFgFontColorType = message.NewValue;
UpdateFontColor();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStrokeFontColorType))
{
_lyricsStrokeFontColorType = message.NewValue;
UpdateFontColor();
}
}
}
@@ -214,25 +253,32 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
UpdateFontColor();
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBackgroundTheme))
{
_lyricsBgTheme = message.NewValue;
UpdateFontColor();
}
}
}
partial void OnLyricsFontSizeChanged(int value)
{
_isRelayoutNeeded = true;
_isLayoutChanged = true;
}
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
{
_lyricsTextFormat.FontWeight = value.ToFontWeight();
_isRelayoutNeeded = true;
_isLayoutChanged = true;
}
partial void OnLyricsLineSpacingFactorChanged(float value)
{
_isRelayoutNeeded = true;
_isLayoutChanged = true;
}
}
}

View File

@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ValueTransition<Color> _immersiveBgTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 1f,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
@@ -29,7 +29,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ValueTransition<float> _lyricsOpacityTransition = new(
initialValue: 0f,
durationSeconds: 1f
durationSeconds: 0.3f
);
private readonly ValueTransition<float> _albumArtBgTransition = new(
@@ -52,5 +52,10 @@ namespace BetterLyrics.WinUI3.ViewModels
durationSeconds: 1f
);
private readonly ValueTransition<float> _lyricsBgBrightnessTransition = new(
initialValue: 0f,
durationSeconds: 1f
);
}
}

View File

@@ -14,28 +14,39 @@ namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel
{
private bool _isCanvasWidthChanged = false;
private bool _isCanvasHeightChanged = false;
private bool _isDisplayTypeChanged = false;
private bool _isPlayingLineChanged = false;
private bool _isVisibleLinesBoundaryChanged = false;
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
bool isCanvasWidthChanged = _canvasWidth != control.Size.Width;
bool isDisplayTypeChanged = _displayType != _displayTypeReceived;
_canvasWidth = (float)control.Size.Width;
_canvasHeight = (float)control.Size.Height;
_albumArtY = 36 + (_canvasHeight - 36 * 2) * 3 / 16f;
_displayType = _displayTypeReceived;
_elapsedTime = args.Timing.ElapsedTime;
if (_isPlaying)
{
_totalTime += args.Timing.ElapsedTime;
_totalTime += _elapsedTime;
}
ElapsedTime = args.Timing.ElapsedTime;
var playingLineIndex = GetCurrentPlayingLineIndex();
_immersiveBgTransition.Update(ElapsedTime);
_albumArtBgTransition.Update(ElapsedTime);
_songInfoOpacityTransition.Update(ElapsedTime);
_isCanvasWidthChanged = _canvasWidth != control.Size.Width;
_isCanvasHeightChanged = _canvasHeight != control.Size.Height;
_isDisplayTypeChanged = _displayType != _displayTypeReceived;
_isPlayingLineChanged = _playingLineIndex != playingLineIndex;
_canvasWidth = (float)control.Size.Width;
_canvasHeight = (float)control.Size.Height;
_displayType = _displayTypeReceived;
_playingLineIndex = playingLineIndex;
_immersiveBgTransition.Update(_elapsedTime);
_albumArtBgTransition.Update(_elapsedTime);
_lyricsBgBrightnessTransition.Update(_elapsedTime);
_songInfoOpacityTransition.Update(_elapsedTime);
if (IsDynamicCoverOverlayEnabled)
{
@@ -43,16 +54,24 @@ namespace BetterLyrics.WinUI3.ViewModels
_rotateAngle %= MathF.PI * 2;
}
_albumArtSize = MathF.Min(
(_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16,
(_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
_albumArtSize = MathF.Max(0, _albumArtSize);
_titleY = _albumArtY + _albumArtSize * 1.05f;
if (isDisplayTypeChanged || isCanvasWidthChanged)
if (_isCanvasHeightChanged)
{
bool jumpTo = !isDisplayTypeChanged && isCanvasWidthChanged;
_albumArtY = 36 + (_canvasHeight - 36 * 2) * 3 / 16f;
}
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
{
_albumArtSize = MathF.Min(
(_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16,
(_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
_albumArtSize = MathF.Max(0, _albumArtSize);
_titleY = _albumArtY + _albumArtSize * 1.05f;
}
if (_isDisplayTypeChanged || _isCanvasWidthChanged)
{
bool jumpTo = !_isDisplayTypeChanged && _isCanvasWidthChanged;
switch (_displayType)
{
case LyricsDisplayType.AlbumArtOnly:
@@ -64,14 +83,12 @@ namespace BetterLyrics.WinUI3.ViewModels
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
_lyricsXTransition.StartTransition(_leftMargin, jumpTo);
_isRelayoutNeeded = true;
break;
case LyricsDisplayType.SplitView:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
_isRelayoutNeeded = true;
break;
case LyricsDisplayType.PlaceholderOnly:
break;
@@ -80,31 +97,31 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
_lyricsXTransition.Update(ElapsedTime);
_albumArtXTransition.Update(ElapsedTime);
_lyricsOpacityTransition.Update(ElapsedTime);
_albumArtOpacityTransition.Update(ElapsedTime);
_lyricsXTransition.Update(_elapsedTime);
_albumArtXTransition.Update(_elapsedTime);
_lyricsOpacityTransition.Update(_elapsedTime);
_albumArtOpacityTransition.Update(_elapsedTime);
if (_lyricsXTransition.IsTransitioning)
if (_isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
{
_isRelayoutNeeded = true;
_maxLyricsWidth = _canvasWidth - _lyricsXTransition.Value - _rightMargin;
_maxLyricsWidth = Math.Max(_maxLyricsWidth, 0);
_isLayoutChanged = true;
}
_maxLyricsWidth = _canvasWidth - _lyricsXTransition.Value - _rightMargin;
_maxLyricsWidth = Math.Max(_maxLyricsWidth, 0);
if (_isRelayoutNeeded)
if (_isLayoutChanged)
{
ReLayout(control);
_isRelayoutNeeded = false;
UpdateCanvasYScrollOffset(control, false);
UpdateCanvasYScrollOffset(control, true, false);
}
else
{
UpdateCanvasYScrollOffset(control, true);
UpdateCanvasYScrollOffset(control, false, true);
}
UpdateLinesProps();
_isLayoutChanged = false;
}
private void ReLayout(ICanvasAnimatedControl control)
@@ -150,48 +167,35 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control, bool withAnimation)
private void UpdateCanvasYScrollOffset(ICanvasAnimatedControl control, bool forceScroll, bool withAnimation)
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
if (startLineIndex < 0 || endLineIndex < 0)
{
return;
}
if (startLineIndex < 0 || endLineIndex < 0) return;
// Set _scrollOffsetY
LyricsLine? currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
var playingTextLayout = currentPlayingLine?.CanvasTextLayout;
if (currentPlayingLine == null || playingTextLayout == null)
if ((!_isPlayingLineChanged && forceScroll) || _isPlayingLineChanged)
{
return;
LyricsLine? currentPlayingLine = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(_playingLineIndex);
if (currentPlayingLine == null) return;
var playingTextLayout = currentPlayingLine?.CanvasTextLayout;
if (playingTextLayout == null) return;
float? targetYScrollOffset = (float?)(-currentPlayingLine!.Position.Y + _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2);
if (!targetYScrollOffset.HasValue) return;
_canvasYScrollTransition.StartTransition(targetYScrollOffset.Value, !withAnimation);
}
float targetYScrollOffset =
(float?)(
-currentPlayingLine.Position.Y
+ _multiLangLyrics.SafeGet(_langIndex)?[0].Position.Y
- playingTextLayout.LayoutBounds.Height / 2
) ?? 0f;
_canvasYScrollTransition.Update(_elapsedTime);
if (withAnimation && !_canvasYScrollTransition.IsTransitioning)
{
_canvasYScrollTransition.StartTransition(targetYScrollOffset);
}
else if (!withAnimation)
{
_canvasYScrollTransition.StartTransition(targetYScrollOffset, true);
}
_canvasYScrollTransition.Update(ElapsedTime);
_startVisibleLineIndex = _endVisibleLineIndex = -1;
int startVisibleLineIndex = -1;
int endVisibleLineIndex = -1;
// Update visible line indices
for (int i = startLineIndex; i <= endLineIndex; i++)
@@ -213,9 +217,9 @@ namespace BetterLyrics.WinUI3.ViewModels
>= 0
)
{
if (_startVisibleLineIndex == -1)
if (startVisibleLineIndex == -1)
{
_startVisibleLineIndex = i;
startVisibleLineIndex = i;
}
}
if (
@@ -226,46 +230,127 @@ namespace BetterLyrics.WinUI3.ViewModels
>= control.Size.Height
)
{
if (_endVisibleLineIndex == -1)
if (endVisibleLineIndex == -1)
{
_endVisibleLineIndex = i;
endVisibleLineIndex = i;
}
}
}
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
if (startVisibleLineIndex != -1 && endVisibleLineIndex == -1)
{
_endVisibleLineIndex = endLineIndex;
endVisibleLineIndex = endLineIndex;
}
_isVisibleLinesBoundaryChanged = _startVisibleLineIndex != startVisibleLineIndex || _endVisibleLineIndex != endVisibleLineIndex;
_startVisibleLineIndex = startVisibleLineIndex;
_endVisibleLineIndex = endVisibleLineIndex;
}
private protected void UpdateFontColor()
private void UpdateFontColor()
{
ThemeTypeSent = Helper.ColorHelper.GetElementThemeFromBackgroundColor(_lyricsWindowBgColor);
if (_isDesktopMode || _isDockMode)
{
ThemeTypeSent = Helper.ColorHelper.GetElementThemeFromBackgroundColor(_environmentalColor);
}
else
{
ThemeTypeSent = _lyricsBgTheme;
}
float brightness = 0f;
Color grayedEnvironmentalColor = Colors.Transparent;
Color fallbackFg = Colors.Transparent;
switch (ThemeTypeSent)
{
case ElementTheme.Default:
switch (Application.Current.RequestedTheme)
{
case ApplicationTheme.Light:
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
break;
case ApplicationTheme.Dark:
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
break;
default:
break;
}
break;
case ElementTheme.Light:
fallbackFg = _darkFontColor;
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
break;
case ElementTheme.Dark:
fallbackFg = _lightFontColor;
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
break;
default:
break;
}
switch (LyricsFontColorType)
if (_adaptiveGrayedFontColor == _lightColor)
{
case Enums.LyricsFontColorType.AdaptiveGrayed:
_fontColor = fallbackFg;
grayedEnvironmentalColor = _darkColor;
} else if (_adaptiveGrayedFontColor == _darkColor)
{
grayedEnvironmentalColor = _lightColor;
}
_lyricsBgBrightnessTransition.StartTransition(brightness);
if (_isDesktopMode || _isDockMode)
{
_adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(_environmentalColor);
}
else
{
_adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(_albumArtAccentColor?.WithBrightness(brightness) ?? Colors.Transparent);
}
switch (_lyricsBgFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
_bgFontColor = _adaptiveGrayedFontColor;
break;
case Enums.LyricsFontColorType.AdaptiveColored:
_fontColor = _adaptiveFontColor ?? fallbackFg;
case LyricsFontColorType.AdaptiveColored:
_bgFontColor = _adaptiveColoredFontColor ?? _adaptiveGrayedFontColor;
break;
case Enums.LyricsFontColorType.Custom:
_fontColor = _customFontColor ?? fallbackFg;
case LyricsFontColorType.Custom:
_bgFontColor = _customBgFontColor ?? _adaptiveGrayedFontColor;
break;
default:
break;
}
switch (_lyricsFgFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
_fgFontColor = _adaptiveGrayedFontColor;
break;
case LyricsFontColorType.AdaptiveColored:
_fgFontColor = _adaptiveColoredFontColor ?? _adaptiveGrayedFontColor;
break;
case LyricsFontColorType.Custom:
_fgFontColor = _customFgFontColor ?? _adaptiveGrayedFontColor;
break;
default:
break;
}
switch (_lyricsStrokeFontColorType)
{
case LyricsFontColorType.AdaptiveGrayed:
_strokeFontColor = grayedEnvironmentalColor.WithBrightness(0.7);
break;
case LyricsFontColorType.AdaptiveColored:
_strokeFontColor = _environmentalColor.WithBrightness(0.7);
break;
case LyricsFontColorType.Custom:
_strokeFontColor = _customStrokeFontColor ?? _environmentalColor;
break;
default:
break;
@@ -274,65 +359,42 @@ namespace BetterLyrics.WinUI3.ViewModels
private void UpdateLinesProps()
{
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
var currentPlayingLine = _multiLangLyrics
.SafeGet(_langIndex)
?.SafeGet(currentPlayingLineIndex);
?.SafeGet(_playingLineIndex);
if (currentPlayingLine == null)
{
return;
}
if (currentPlayingLine == null) return;
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
if (line == null)
if (line == null) continue;
if (_isLayoutChanged || _isVisibleLinesBoundaryChanged || _isPlayingLineChanged)
{
continue;
}
float distanceFromPlayingLine = Math.Abs(line.Position.Y - currentPlayingLine.Position.Y);
float distanceFactor = Math.Clamp(distanceFromPlayingLine / (_canvasHeight / 2), 0, 1);
float distanceFromPlayingLine = Math.Abs(line.Position.Y - currentPlayingLine.Position.Y);
float distanceFactor = Math.Clamp(distanceFromPlayingLine / (_canvasHeight / 2), 0, 1);
if (!line.AngleTransition.IsTransitioning)
line.AngleTransition.StartTransition(
_isFanLyricsEnabled
line.AngleTransition.StartTransition(_isFanLyricsEnabled
? (float)Math.PI
* (30f / 180f)
* distanceFactor
* (i - currentPlayingLineIndex > 0 ? 1 : -1)
* (i > _playingLineIndex ? 1 : -1)
: 0
);
if (!line.BlurAmountTransition.IsTransitioning)
line.BlurAmountTransition.StartTransition(LyricsBlurAmount * distanceFactor);
if (!line.ScaleTransition.IsTransitioning)
line.ScaleTransition.StartTransition(
_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale)
);
if (!line.OpacityTransition.IsTransitioning)
line.ScaleTransition.StartTransition(_highlightedScale - distanceFactor * (_highlightedScale - _defaultScale));
line.OpacityTransition.StartTransition(_defaultOpacity - distanceFactor * _defaultOpacity * (1 - LyricsVerticalEdgeOpacity / 100f));
// Only calculate highlight opacity for the current line and the two lines around it
// to avoid unnecessary calculations
if (!line.HighlightOpacityTransition.IsTransitioning)
{
line.HighlightOpacityTransition.StartTransition(
distanceFromPlayingLine == 0 ? 1f : 0f
);
line.HighlightOpacityTransition.StartTransition(i == _playingLineIndex ? 1f : 0f);
}
line.AngleTransition.Update(ElapsedTime);
line.ScaleTransition.Update(ElapsedTime);
line.BlurAmountTransition.Update(ElapsedTime);
line.OpacityTransition.Update(ElapsedTime);
line.HighlightOpacityTransition.Update(ElapsedTime);
line.AngleTransition.Update(_elapsedTime);
line.ScaleTransition.Update(_elapsedTime);
line.BlurAmountTransition.Update(_elapsedTime);
line.OpacityTransition.Update(_elapsedTime);
line.HighlightOpacityTransition.Update(_elapsedTime);
}
}
}

View File

@@ -8,7 +8,6 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Fluent.LibreTranslate;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
@@ -29,12 +28,16 @@ namespace BetterLyrics.WinUI3.ViewModels
{
public partial class LyricsRendererViewModel : BaseViewModel
{
private TimeSpan _elapsedTime = TimeSpan.Zero;
private TimeSpan _totalTime = TimeSpan.Zero;
private TimeSpan _positionOffset = TimeSpan.Zero;
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
private CanvasBitmap? _lastAlbumArtCanvasBitmap = null;
private CanvasBitmap? _albumArtCanvasBitmap = null;
private float _albumArtSize = 0f;
private int _albumArtCornerRadius = 0;
@@ -67,6 +70,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly IMusicSearchService _musicSearchService;
private readonly ILibWatcherService _libWatcherService;
private readonly IPlaybackService _playbackService;
private readonly ILibreTranslateService _libreTranslateService;
private readonly ILogger _logger;
private readonly float _leftMargin = 36f;
@@ -75,13 +79,32 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly float _topMargin = 36f;
private readonly float _bottomMargin = 36f;
private Color _fontColor;
private Color? _adaptiveFontColor = null;
private Color? _customFontColor;
private Color _lightFontColor = Colors.White;
private Color _darkFontColor = Colors.Black;
private Color _adaptiveGrayedFontColor = Colors.Transparent;
private Color? _adaptiveColoredFontColor = null;
private Color? _albumArtAccentColor = null;
private Color _lyricsWindowBgColor = Colors.Transparent;
private Color _environmentalColor = Colors.Transparent;
private Color _lightColor = Colors.White;
private Color _darkColor = Colors.Black;
private Color _bgFontColor;
private Color _fgFontColor;
private Color _strokeFontColor;
private Color? _customBgFontColor;
private Color? _customFgFontColor;
private Color? _customStrokeFontColor;
private LyricsFontColorType _lyricsBgFontColorType;
private LyricsFontColorType _lyricsFgFontColorType;
private LyricsFontColorType _lyricsStrokeFontColorType;
private ElementTheme _lyricsBgTheme;
private int _lyricsFontStrokeWidth;
private int _playingLineIndex = -1;
private int _startVisibleLineIndex = -1;
private int _endVisibleLineIndex = -1;
@@ -93,13 +116,14 @@ namespace BetterLyrics.WinUI3.ViewModels
private bool _isPlaying = true;
private bool _isRelayoutNeeded = true;
private bool _isLayoutChanged = true;
private int _langIndex = 0;
private List<List<LyricsLine>> _multiLangLyrics = [];
private List<string> _translations = [];
private bool _isTranslationEnabled = false;
private int _targetLanguageIndex = 6;
private CanvasTextFormat _lyricsTextFormat = new()
{
@@ -131,13 +155,13 @@ namespace BetterLyrics.WinUI3.ViewModels
private Task? _showTranslationsTask;
private CancellationTokenSource? _showTranslationsCts;
public LyricsRendererViewModel(
ISettingsService settingsService, IPlaybackService playbackService,
IMusicSearchService musicSearchService, ILibWatcherService libWatcherService) : base(settingsService)
public LyricsRendererViewModel(ISettingsService settingsService, IPlaybackService playbackService, IMusicSearchService musicSearchService, ILibWatcherService libWatcherService, ILibreTranslateService libreTranslateService) : base(settingsService)
{
_musicSearchService = musicSearchService;
_playbackService = playbackService;
_libWatcherService = libWatcherService;
_libreTranslateService = libreTranslateService;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
_albumArtCornerRadius = _settingsService.CoverImageRadius;
@@ -145,7 +169,9 @@ namespace BetterLyrics.WinUI3.ViewModels
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
LyricsFontColorType = _settingsService.LyricsFontColorType;
_lyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
_lyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
LyricsFontWeight = _settingsService.LyricsFontWeight;
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
@@ -154,9 +180,19 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
_customFontColor = _settingsService.LyricsCustomFontColor;
_customBgFontColor = _settingsService.LyricsCustomBgFontColor;
_customFgFontColor = _settingsService.LyricsCustomFgFontColor;
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
_libWatcherService.MusicLibraryFilesChanged +=
@@ -169,6 +205,9 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateFontColor();
}
[ObservableProperty]
public partial bool IsTranslating { get; set; } = false;
public int CoverOverlayBlurAmount { get; set; }
public int CoverOverlayOpacity { get; set; }
@@ -176,8 +215,6 @@ namespace BetterLyrics.WinUI3.ViewModels
private LyricsDisplayType _displayTypeReceived = LyricsDisplayType.PlaceholderOnly;
private LyricsDisplayType _displayType = LyricsDisplayType.PlaceholderOnly;
public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
public bool IsDynamicCoverOverlayEnabled { get; set; }
public bool IsLyricsGlowEffectEnabled { get; set; }
@@ -186,9 +223,6 @@ namespace BetterLyrics.WinUI3.ViewModels
public int LyricsBlurAmount { get; set; }
[ObservableProperty]
public partial LyricsFontColorType LyricsFontColorType { get; set; }
[ObservableProperty]
public partial int LyricsFontSize { get; set; }
@@ -301,6 +335,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
{
_logger.LogInformation("Music library files changed: {ChangeType} {FilePath}, refreshing lyrics...", e.ChangeType, e.FilePath);
RefreshLyricsAsync();
}
@@ -324,15 +359,16 @@ namespace BetterLyrics.WinUI3.ViewModels
if (SongInfo?.AlbumArtSwBitmap != _albumArtSwBitmap)
{
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
_lastAlbumArtCanvasBitmap = null;
_albumArtSwBitmap = SongInfo?.AlbumArtSwBitmap;
_albumArtCanvasBitmap = null;
_albumArtAccentColor = SongInfo?.AlbumArtAccentColor;
_lyricsWindowBgColor = _albumArtAccentColor ?? Colors.Gray;
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
if (!_isDesktopMode && !_isDockMode) _adaptiveFontColor = Helper.ColorHelper.GetForegroundColor(_lyricsWindowBgColor);
UpdateFontColor();
}
@@ -347,6 +383,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
await RefreshLyricsAsync();
_totalTime = TimeSpan.Zero;
@@ -372,6 +409,7 @@ namespace BetterLyrics.WinUI3.ViewModels
private async Task UpdateTranslationsAsync()
{
IsTranslating = true;
if (_isTranslationEnabled)
{
await ShowWithTranslationsAsync();
@@ -380,6 +418,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
ShowOriginalsOnly();
}
IsTranslating = false;
}
private async Task ShowWithTranslationsAsync()
@@ -400,11 +439,23 @@ namespace BetterLyrics.WinUI3.ViewModels
private async Task ShowTranslationsCoreAsync(CancellationToken token)
{
_logger.LogInformation("Showing translations for lyrics...");
try
{
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
{
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.LyricsWindowNotificationPanel?.Notify(
App.ResourceLoader!.GetString("TranslateServerNotSet"),
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
);
});
ShowOriginalsOnly();
return;
}
var text = string.Join("\n", _multiLangLyrics.FirstOrDefault()?.Select(x => x.OriginalText) ?? []);
GlobalLibreTranslateSettings.Server = new LibreTranslateServer("http://localhost:5000");
var translated = await text.TranslateAsync(LanguageCode.Chinese);
var translated = await _libreTranslateService.TranslateAsync(text, token);
token.ThrowIfCancellationRequested();
_translations = translated.Split('\n').ToList();
@@ -424,25 +475,27 @@ namespace BetterLyrics.WinUI3.ViewModels
break;
}
if (totallySame) return; // No need to show translations
foreach (var langLyrics in _multiLangLyrics)
{
int i = 0;
foreach (var line in langLyrics)
{
line.DisplayedText = $"{line.OriginalText}\n{_translations[i]}";
line.DisplayedText = totallySame ? line.OriginalText : $"{line.OriginalText}\n{_translations[i]}";
i++;
}
break;
}
_isRelayoutNeeded = true;
_isLayoutChanged = true;
}
catch (Exception)
{
IsTranslating = false;
}
catch (Exception) { }
}
private void ShowOriginalsOnly()
{
_logger.LogInformation("Showing original lyrics only, translations disabled.");
foreach (var langLyrics in _multiLangLyrics)
{
foreach (var line in langLyrics)
@@ -450,7 +503,7 @@ namespace BetterLyrics.WinUI3.ViewModels
line.DisplayedText = line.OriginalText;
}
}
_isRelayoutNeeded = true;
_isLayoutChanged = true;
}
private async Task RefreshLyricsCoreAsync(CancellationToken token)
@@ -485,8 +538,8 @@ namespace BetterLyrics.WinUI3.ViewModels
(int?)SongInfo?.DurationMs ?? (int)TimeSpan.FromMinutes(99).TotalMilliseconds
);
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _multiLangLyrics.Count);
ShowOriginalsOnly();
await UpdateTranslationsAsync();
token.ThrowIfCancellationRequested();
}
catch (Exception) { }
}
@@ -506,7 +559,7 @@ namespace BetterLyrics.WinUI3.ViewModels
},
]
);
_isRelayoutNeeded = true;
_isLayoutChanged = true;
}
}
}

View File

@@ -3,7 +3,6 @@
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.ViewModels;
@@ -13,6 +12,7 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Windows.UI;
using WinRT.Interop;
@@ -27,23 +27,9 @@ namespace BetterLyrics.WinUI3
{
private ForegroundWindowWatcherHelper? _watcherHelper = null;
public LyricsWindowViewModel(ISettingsService settingsService)
: base(settingsService)
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
{
WeakReferenceMessenger.Default.Register<ShowNotificatonMessage>(
this,
async (r, m) =>
{
Notification = m.Value;
if (!Notification.IsForeverDismissable)
{
Notification.Visibility = Notification.IsForeverDismissable ? Visibility.Visible : Visibility.Collapsed;
ShowInfoBar = true;
await Task.Delay(AnimationHelper.StackedNotificationsShowingDuration);
ShowInfoBar = false;
}
}
);
_ignoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
}
[ObservableProperty]
@@ -77,6 +63,8 @@ namespace BetterLyrics.WinUI3
[ObservableProperty]
public partial double TitleBarHeight { get; set; } = 36;
private bool _ignoreFullscreenWindow = false;
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is SystemTrayViewModel)
@@ -89,6 +77,13 @@ namespace BetterLyrics.WinUI3
}
}
}
else if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.IgnoreFullscreenWindow))
{
_ignoreFullscreenWindow = message.NewValue;
}
}
}
public void Receive(PropertyChangedMessage<ElementTheme> message)
@@ -113,10 +108,7 @@ namespace BetterLyrics.WinUI3
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
DockModeHelper.UpdateAppBarHeight(
WindowNative.GetWindowHandle(window),
message.NewValue * 3
);
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), message.NewValue * 4);
}
}
}
@@ -132,6 +124,10 @@ namespace BetterLyrics.WinUI3
hwnd,
onWindowChanged =>
{
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.IsAlwaysOnTop = true;
}
UpdateAccentColor(hwnd, mode);
}
);
@@ -148,6 +144,8 @@ namespace BetterLyrics.WinUI3
private void LockWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
DesktopModeHelper.Lock(window);
IsLyricsWindowLocked = true;
}
@@ -175,7 +173,6 @@ namespace BetterLyrics.WinUI3
else
{
DesktopModeHelper.Disable(window);
StopWatchWindowColorChange();
}
}
@@ -183,17 +180,18 @@ namespace BetterLyrics.WinUI3
private void ToggleDockMode()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
StopWatchWindowColorChange();
IsDockMode = !IsDockMode;
if (IsDockMode)
{
StartWatchWindowColorChange(WindowColorSampleMode.BelowWindow);
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 3);
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 4);
}
else
{
StartWatchWindowColorChange(WindowColorSampleMode.WindowEdge);
DockModeHelper.Disable(window);
}
}

View File

@@ -1,15 +1,7 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Views;
@@ -17,11 +9,23 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI.Xaml;
using ShadowViewer.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Globalization;
using Windows.Media.Playback;
using Windows.System;
using Windows.UI;
using Windows.UI.Popups;
using WinRT.Interop;
using AppInfo = BetterLyrics.WinUI3.Helper.AppInfo;
namespace BetterLyrics.WinUI3.ViewModels
{
@@ -29,11 +33,18 @@ namespace BetterLyrics.WinUI3.ViewModels
{
private readonly ILibWatcherService _libWatcherService;
private readonly IPlaybackService _playbackService;
private readonly ILibreTranslateService _libreTranslateService;
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService) : base(settingsService)
private readonly string _autoStartupTaskId = "AutoStartup";
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ILibreTranslateService libreTranslateService) : base(settingsService)
{
_libWatcherService = libWatcherService;
_playbackService = playbackService;
_libreTranslateService = libreTranslateService;
LibreTranslateServer = _settingsService.LibreTranslateServer;
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
@@ -58,15 +69,28 @@ namespace BetterLyrics.WinUI3.ViewModels
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
IsFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
LyricsFontColorType = _settingsService.LyricsFontColorType;
LyricsCustomFontColor = _settingsService.LyricsCustomFontColor;
LyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
LyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
LyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
LyricsCustomBgFontColor = _settingsService.LyricsCustomBgFontColor;
LyricsCustomFgFontColor = _settingsService.LyricsCustomFgFontColor;
LyricsCustomStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
LyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
LyricsBackgroundTheme = _settingsService.LyricsBackgroundTheme;
MediaSourceProvidersInfo = [.. _settingsService.MediaSourceProvidersInfo];
IgnoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
Task.Run(async () =>
{
BuildDate = (await AppInfo.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
BuildDate = (await Helper.AppInfo.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
});
}
@@ -75,6 +99,10 @@ namespace BetterLyrics.WinUI3.ViewModels
MediaSourceProvidersInfo = [.. e.MediaSourceProviersInfo];
}
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ElementTheme LyricsBackgroundTheme { get; set; }
[ObservableProperty]
public partial AutoStartWindowType AutoStartWindowType { get; set; }
@@ -140,11 +168,27 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color LyricsCustomFontColor { get; set; }
public partial Color LyricsCustomBgFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsFontColorType { get; set; }
public partial Color LyricsCustomFgFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Color LyricsCustomStrokeFontColor { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsBgFontColorType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsFgFontColorType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsFontColorType LyricsStrokeFontColorType { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
@@ -173,6 +217,34 @@ namespace BetterLyrics.WinUI3.ViewModels
public string BuildDate { get; set; } = string.Empty;
[ObservableProperty]
public partial string LibreTranslateServer { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int SelectedTargetLanguageIndex { get; set; } = 0;
[ObservableProperty]
public partial bool IsLibreTranslateServerTesting { get; set; } = false;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int LyricsFontStrokeWidth { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IgnoreFullscreenWindow { get; set; }
partial void OnLyricsBackgroundThemeChanged(ElementTheme value)
{
_settingsService.LyricsBackgroundTheme = value;
}
partial void OnLyricsFontStrokeWidthChanged(int value)
{
_settingsService.LyricsFontStrokeWidth = value;
}
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
@@ -223,64 +295,22 @@ namespace BetterLyrics.WinUI3.ViewModels
private void AddFolderAsync(string path)
{
var normalizedPath =
Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
if (
LocalLyricsFolders.Any(x =>
Path.GetFullPath(x.Path)
.TrimEnd(Path.DirectorySeparatorChar)
.Equals(
normalizedPath.TrimEnd(Path.DirectorySeparatorChar),
StringComparison.OrdinalIgnoreCase
)
)
)
if (LocalLyricsFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
{
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo")
)
)
);
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
}
else if (
LocalLyricsFolders.Any(item =>
normalizedPath.StartsWith(
Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar)
+ Path.DirectorySeparatorChar,
StringComparison.OrdinalIgnoreCase
)
)
)
else if (LocalLyricsFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
// 添加的文件夹是现有文件夹的子文件夹
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo")
)
)
);
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
}
else if (
LocalLyricsFolders.Any(item =>
Path.GetFullPath(item.Path)
.TrimEnd(Path.DirectorySeparatorChar)
.StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase)
)
else if (LocalLyricsFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
)
{
// 添加的文件夹是现有文件夹的父文件夹
WeakReferenceMessenger.Default.Send(
new ShowNotificatonMessage(
new Notification(
App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo")
)
)
);
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"));
}
else
{
@@ -347,6 +377,79 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
[RelayCommand]
private void LibreTranslateServerTest()
{
IsLibreTranslateServerTesting = true;
Task.Run(async () =>
{
try
{
string result = await _libreTranslateService.TranslateAsync("Hello, world!", null);
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
IsLibreTranslateServerTesting = false;
});
}
catch (Exception)
{
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
IsLibreTranslateServerTesting = false;
});
}
});
}
public async Task<bool> ToggleAutoStartupAsync(bool target)
{
StartupTask startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
if (target)
{
await startupTask.RequestEnableAsync();
}
else
{
startupTask.Disable();
}
return await DetectIsAutoStartupEnabledAsync();
}
public async Task<bool> DetectIsAutoStartupEnabledAsync()
{
bool result = false;
var startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
switch (startupTask.State)
{
case StartupTaskState.Disabled:
case StartupTaskState.DisabledByUser:
case StartupTaskState.DisabledByPolicy:
result = false;
break;
case StartupTaskState.Enabled:
result = true;
break;
}
return result;
}
partial void OnIgnoreFullscreenWindowChanged(bool value)
{
_settingsService.IgnoreFullscreenWindow = value;
}
partial void OnSelectedTargetLanguageIndexChanged(int value)
{
_settingsService.SelectedTargetLanguageIndex = value;
}
partial void OnLibreTranslateServerChanged(string value)
{
_settingsService.LibreTranslateServer = value;
}
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
{
_settingsService.AutoStartWindowType = value;
@@ -430,14 +533,34 @@ namespace BetterLyrics.WinUI3.ViewModels
_settingsService.LyricsBlurAmount = value;
}
partial void OnLyricsCustomFontColorChanged(Color value)
partial void OnLyricsCustomBgFontColorChanged(Color value)
{
_settingsService.LyricsCustomFontColor = value;
_settingsService.LyricsCustomBgFontColor = value;
}
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
partial void OnLyricsCustomFgFontColorChanged(Color value)
{
_settingsService.LyricsFontColorType = value;
_settingsService.LyricsCustomFgFontColor = value;
}
partial void OnLyricsCustomStrokeFontColorChanged(Color value)
{
_settingsService.LyricsCustomStrokeFontColor = value;
}
partial void OnLyricsBgFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsBgFontColorType = value;
}
partial void OnLyricsFgFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsFgFontColorType = value;
}
partial void OnLyricsStrokeFontColorTypeChanged(LyricsFontColorType value)
{
_settingsService.LyricsStrokeFontColorType = value;
}
partial void OnLyricsFontSizeChanged(int value)

View File

@@ -40,24 +40,12 @@
Padding="12"
VerticalAlignment="Bottom"
Background="Transparent"
Opacity="0">
Opacity="0"
PointerEntered="BottomCommandGrid_PointerEntered"
PointerExited="BottomCommandGrid_PointerExited">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction
PropertyName="Opacity"
TargetObject="{x:Bind BottomCommandGrid}"
Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction
PropertyName="Opacity"
TargetObject="{x:Bind BottomCommandGrid}"
Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel
x:Name="BottomLeftCommandStackPanel"
@@ -98,6 +86,9 @@
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
</FontIcon.RenderTransform>
</FontIcon>
<ToolTipService.ToolTip>
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<StackPanel>
@@ -120,8 +111,10 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE777;}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True" />
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource GhostButtonStyle}" />
</RelativePanel>
<TextBlock Opacity="0.5" x:Uid="LyricsPagePositionOffsetHint"/>
</StackPanel>
</Flyout>
</Button.Flyout>
@@ -132,7 +125,11 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8C1;}"
IsChecked="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}"
Style="{StaticResource GhostToggleButtonStyle}" />
Style="{StaticResource GhostToggleButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
</ToolTipService.ToolTip>
</ToggleButton>
<!-- Display type -->
<Button
@@ -141,9 +138,9 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF246;}"
Style="{StaticResource GhostButtonStyle}">
<Button.OpacityTransition>
<ScalarTransition />
</Button.OpacityTransition>
<ToolTipService.ToolTip>
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout>
<Flyout.FlyoutPresenterStyle>
@@ -167,7 +164,11 @@
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE713;}"
Style="{StaticResource GhostButtonStyle}" />
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
@@ -178,7 +179,7 @@
x:Uid="MainPageWelcomeTeachingTip"
Closed="WelcomeTeachingTip_Closed"
IsOpen="{x:Bind ViewModel.IsWelcomeTeachingTipOpen, Mode=OneWay}"
Target="{x:Bind SettingsButton}" />
Target="{x:Bind RootGrid}" />
<uc:SystemTray />

View File

@@ -45,5 +45,15 @@ namespace BetterLyrics.WinUI3.Views
{
ViewModel.PositionOffset = 0;
}
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
BottomCommandGrid.Opacity = 1;
}
private void BottomCommandGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
BottomCommandGrid.Opacity = 0;
}
}
}

View File

@@ -9,6 +9,7 @@
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:media="using:CommunityToolkit.WinUI.Media"
xmlns:scontrols="using:ShadowViewer.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Window.SystemBackdrop>
@@ -24,24 +25,12 @@
x:Name="TopCommandGrid"
VerticalAlignment="Top"
Background="Transparent"
Opacity="0">
Opacity="0"
PointerEntered="TopCommandGrid_PointerEntered"
PointerExited="TopCommandGrid_PointerExited">
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction
PropertyName="Opacity"
TargetObject="{x:Bind TopCommandGrid}"
Value="1" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction
PropertyName="Opacity"
TargetObject="{x:Bind TopCommandGrid}"
Value="0" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel
x:Name="TopLeftCommandStackPanel"
@@ -74,20 +63,6 @@
<ToolTipService.ToolTip>
<ToolTip x:Name="LockToolTip" x:Uid="HostWindowLockToolTip" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<interactivity:EventTriggerBehavior EventName="PointerEntered">
<interactivity:ChangePropertyAction
PropertyName="IsOpen"
TargetObject="{x:Bind LockToolTip}"
Value="True" />
</interactivity:EventTriggerBehavior>
<interactivity:EventTriggerBehavior EventName="PointerExited">
<interactivity:ChangePropertyAction
PropertyName="IsOpen"
TargetObject="{x:Bind LockToolTip}"
Value="False" />
</interactivity:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<!-- More -->
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
@@ -95,6 +70,9 @@
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE712;" />
<ToolTipService.ToolTip>
<ToolTip x:Name="MoreButtonToolTip" x:Uid="HostWindowMoreButtonToolTip" />
</ToolTipService.ToolTip>
<Button.Flyout>
<MenuFlyout>
<ToggleMenuFlyoutItem
@@ -135,7 +113,7 @@
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE629;" />
Glyph="&#xE654;" />
</Button>
<!-- Window Maximise -->
<Button
@@ -145,7 +123,7 @@
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xF16B;" />
Glyph="&#xE655;" />
</Button>
<!-- Window Restore -->
<Button
@@ -156,7 +134,7 @@
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xF5ED;" />
Glyph="&#xE656;" />
</Button>
<!-- Window Close -->
<Button
@@ -166,54 +144,22 @@
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
Glyph="&#xE624;" />
Glyph="&#xE653;" />
</Button>
</StackPanel>
</Grid>
<InfoBar
x:Name="HostInfoBar"
Margin="36"
<scontrols:NotificationPanel
x:Name="TipContainerCenter"
Margin="0,0,0,52"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="{ThemeResource SystemFillColorSolidAttentionBackgroundBrush}"
IsClosable="False"
IsOpen="{x:Bind ViewModel.ShowInfoBar, Mode=OneWay}"
Message="{x:Bind ViewModel.Notification.Message, Mode=OneWay}"
Opacity="0"
Severity="{x:Bind ViewModel.Notification.Severity, Mode=OneWay}">
<InfoBar.RenderTransform>
<TranslateTransform x:Name="HostInfoBarTransform" Y="20" />
</InfoBar.RenderTransform>
<InfoBar.Resources>
<Storyboard x:Key="InfoBarShowAndHideStoryboard">
<!-- Opacity -->
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBar" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="0" />
</DoubleAnimationUsingKeyFrames>
<!-- Y -->
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HostInfoBarTransform" Storyboard.TargetProperty="Y">
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="20" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.6" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:3.9" Value="20" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</InfoBar.Resources>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{Binding ElementName=HostInfoBar, Path=IsOpen, Mode=OneWay}"
ComparisonCondition="Equal"
Value="True">
<interactivity:ControlStoryboardAction Storyboard="{StaticResource InfoBarShowAndHideStoryboard}" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</InfoBar>
Canvas.ZIndex="1"
FlowDirection="RightToLeft"
Loaded="TipContainerCenter_Loaded"
Orientation="Vertical"
Visibility="Collapsed" />
</Grid>
</Window>

View File

@@ -252,5 +252,20 @@ namespace BetterLyrics.WinUI3.Views
presenter.Restore();
}
}
private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
TopCommandGrid.Opacity = 1;
}
private void TopCommandGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
TopCommandGrid.Opacity = 0;
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
{
App.Current.LyricsWindowNotificationPanel = TipContainerCenter;
}
}
}

View File

@@ -9,6 +9,7 @@
xmlns:local="using:BetterLyrics.WinUI3.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:scontrols="using:ShadowViewer.Controls"
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
@@ -111,19 +112,25 @@
<TextBlock x:Uid="SettingsPageAppBehavior" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow">
<ComboBox SelectedIndex="{x:Bind ViewModel.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoStart" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF71C;}">
<ToggleSwitch x:Name="AutoStartupToggleSwitch" Loaded="AutoStartupToggleSwitch_Loaded" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoLock">
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoLockOnDesktopMode, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
<controls:SettingsCard x:Uid="SettingsPageAutoStartWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE736;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.AutoStartWindowType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageAutoLock" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE755;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoLockOnDesktopMode, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE66C;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IgnoreFullscreenWindow, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
</controls:Case>
@@ -132,11 +139,19 @@
<controls:Case Value="Background">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageDynamicLyricsBackground">
<controls:SettingsCard x:Uid="SettingsPageTheme" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE790;}">
<ComboBox x:Name="ThemeComboBox" SelectedIndex="{x:Bind ViewModel.LyricsBackgroundTheme, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageFollowSystem" />
<ComboBoxItem x:Uid="SettingsPageLight" />
<ComboBoxItem x:Uid="SettingsPageDark" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageDynamicLyricsBackground" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE759;}">
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDynamicCoverOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundOpacity">
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF573;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.CoverOverlayOpacity, Mode=OneWay}" />
@@ -155,7 +170,7 @@
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount">
<controls:SettingsCard x:Uid="SettingsPageLyricsBackgroundBlurAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE727;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
@@ -379,8 +394,26 @@
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<controls:SettingsCard x:Uid="SettingsPageLyricsFontStrokeWidth" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEC12;}">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text="{x:Bind ViewModel.LyricsFontStrokeWidth, Mode=OneWay}" />
<Slider
Maximum="5"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="1"
TickFrequency="1"
TickPlacement="Outside"
Value="{x:Bind ViewModel.LyricsFontStrokeWidth, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsStrokeFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsStrokeFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
@@ -396,16 +429,84 @@
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind ViewModel.LyricsCustomFontColor, Mode=TwoWay}">
Color="{x:Bind ViewModel.LyricsCustomStrokeFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
Binding="{x:Bind ViewModel.LyricsStrokeFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
Binding="{x:Bind ViewModel.LyricsStrokeFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsBgFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsBgFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
</ComboBox>
</controls:SettingsCard>
<ColorPicker
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsAlphaSliderVisible="True"
IsAlphaTextInputVisible="True"
IsColorChannelTextInputVisible="True"
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind ViewModel.LyricsCustomBgFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsBgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsBgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ColorPicker>
<controls:SettingsCard x:Uid="SettingsPageLyricsFgFontColor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8D3;}">
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsFgFontColorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveColored" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorAdaptiveGrayed" />
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorCustom" />
</ComboBox>
</controls:SettingsCard>
<ColorPicker
ColorSpectrumShape="Box"
IsAlphaEnabled="True"
IsAlphaSliderVisible="True"
IsAlphaTextInputVisible="True"
IsColorChannelTextInputVisible="True"
IsColorSliderVisible="True"
IsHexInputVisible="True"
IsMoreButtonVisible="True"
Color="{x:Bind ViewModel.LyricsCustomFgFontColor, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="Equal"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.LyricsFgFontColorType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
ComparisonCondition="NotEqual"
Value="2">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
@@ -524,11 +625,70 @@
<!-- Lyrics translation -->
<controls:Case Value="Translation">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsExpander>
<StackPanel Orientation="Horizontal">
<TextBox Text="http://127.0.0.1:5000" />
<Button Content="Verify" />
</StackPanel>
<controls:SettingsExpander
x:Uid="SettingsPageTranslationConfig"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8C1;}"
IsExpanded="True">
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLibreTranslateServer">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBox
x:Name="LibreTranslateServerTextBox"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
PlaceholderText="http://localhost:5000"
Text="{x:Bind ViewModel.LibreTranslateServer, Mode=TwoWay}" />
<Button
x:Uid="SettingsPageServerTestButton"
Command="{x:Bind ViewModel.LibreTranslateServerTestCommand}"
IsEnabled="{x:Bind ViewModel.IsLibreTranslateServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageTargetLanguage">
<ComboBox SelectedIndex="{x:Bind ViewModel.SelectedTargetLanguageIndex, Mode=TwoWay}">
<ComboBoxItem Content="العربية" Tag="ar" />
<ComboBoxItem Content="Azərbaycan dili" Tag="az" />
<ComboBoxItem Content="中文" Tag="zh" />
<ComboBoxItem Content="Čeština" Tag="cs" />
<ComboBoxItem Content="Dansk" Tag="da" />
<ComboBoxItem Content="Nederlands" Tag="nl" />
<ComboBoxItem Content="English" Tag="en" />
<ComboBoxItem Content="Esperanto" Tag="eo" />
<ComboBoxItem Content="Suomi" Tag="fi" />
<ComboBoxItem Content="Français" Tag="fr" />
<ComboBoxItem Content="Deutsch" Tag="de" />
<ComboBoxItem Content="Ελληνικά" Tag="el" />
<ComboBoxItem Content="עברית" Tag="he" />
<ComboBoxItem Content="हिन्दी" Tag="hi" />
<ComboBoxItem Content="Magyar" Tag="hu" />
<ComboBoxItem Content="Bahasa Indonesia" Tag="id" />
<ComboBoxItem Content="Gaeilge" Tag="ga" />
<ComboBoxItem Content="Italiano" Tag="it" />
<ComboBoxItem Content="日本語" Tag="ja" />
<ComboBoxItem Content="한국어" Tag="ko" />
<ComboBoxItem Content="فارسی" Tag="fa" />
<ComboBoxItem Content="Polski" Tag="pl" />
<ComboBoxItem Content="Português" Tag="pt" />
<ComboBoxItem Content="Русский" Tag="ru" />
<ComboBoxItem Content="Slovenčina" Tag="sk" />
<ComboBoxItem Content="Español" Tag="es" />
<ComboBoxItem Content="Svenska" Tag="sv" />
<ComboBoxItem Content="Türkçe" Tag="tr" />
<ComboBoxItem Content="Українська" Tag="uk" />
<ComboBoxItem Content="Tiếng Việt" Tag="vi" />
</ComboBox>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageTranslationInfo">
<controls:SettingsCard.Description>
<HyperlinkButton Margin="0,6,0,0" NavigateUri="https://github.com/LibreTranslate/LibreTranslate">
<TextBlock
x:Uid="SettingsPageTranslationInfoLink"
FontSize="14"
TextWrapping="Wrap" />
</HyperlinkButton>
</controls:SettingsCard.Description>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>
</controls:Case>
@@ -583,5 +743,16 @@
</ScrollViewer>
</NavigationView>
<scontrols:NotificationPanel
x:Name="TipContainerCenter"
Margin="0,0,0,52"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Canvas.ZIndex="1"
FlowDirection="RightToLeft"
Loaded="TipContainerCenter_Loaded"
Orientation="Vertical"
Visibility="Collapsed" />
</Grid>
</Page>

View File

@@ -12,6 +12,8 @@ namespace BetterLyrics.WinUI3.Views
{
public sealed partial class SettingsPage : Page
{
private bool _isUserToggle;
public SettingsPage()
{
this.InitializeComponent();
@@ -87,5 +89,21 @@ namespace BetterLyrics.WinUI3.Views
}
}
}
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
{
App.Current.SettingsWindowNotificationPanel = TipContainerCenter;
}
private async void AutoStartupToggleSwitch_Loaded(object sender, RoutedEventArgs e)
{
AutoStartupToggleSwitch.IsOn = await ViewModel.DetectIsAutoStartupEnabledAsync();
AutoStartupToggleSwitch.Toggled += AutoStartupToggleSwitch_Toggled;
}
private void AutoStartupToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
ViewModel.ToggleAutoStartupAsync(AutoStartupToggleSwitch.IsOn);
}
}
}