diff --git a/LanMountainDesktop/Localization/en-US.json b/LanMountainDesktop/Localization/en-US.json index 6b5c827..eb3eb74 100644 --- a/LanMountainDesktop/Localization/en-US.json +++ b/LanMountainDesktop/Localization/en-US.json @@ -237,6 +237,13 @@ "settings.about.startup_header": "Windows Startup", "settings.about.startup_desc": "Launch the app automatically when signing in to Windows.", "settings.about.startup_toggle": "Launch at Windows sign-in", + "settings.about.render_mode_header": "App Rendering Mode", + "settings.about.render_mode_desc": "Choose the rendering backend. Restart the app after changing this option. Unsupported modes fall back to software.", + "settings.about.render_mode.default": "Default", + "settings.about.render_mode.software": "Software", + "settings.about.render_mode.angle_egl": "angleEgl", + "settings.about.render_mode.wgl": "WGL", + "settings.about.render_mode.vulkan": "Vulkan", "settings.footer": "LanMountainDesktop Settings", "filepicker.title": "Select wallpaper", "filepicker.image_files": "Image files", diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json index 4987de1..a940684 100644 --- a/LanMountainDesktop/Localization/zh-CN.json +++ b/LanMountainDesktop/Localization/zh-CN.json @@ -237,6 +237,13 @@ "settings.about.startup_header": "Windows 自启动", "settings.about.startup_desc": "在登录 Windows 时自动启动应用。", "settings.about.startup_toggle": "登录 Windows 时启动", + "settings.about.render_mode_header": "应用渲染模式", + "settings.about.render_mode_desc": "选择应用渲染后端。更改后需要重启应用生效。不支持的模式会回退到软件渲染。", + "settings.about.render_mode.default": "默认", + "settings.about.render_mode.software": "软件", + "settings.about.render_mode.angle_egl": "angleEgl", + "settings.about.render_mode.wgl": "WGL", + "settings.about.render_mode.vulkan": "Vulkan", "settings.footer": "LanMountainDesktop 设置", "filepicker.title": "选择壁纸", "filepicker.image_files": "图片文件", diff --git a/LanMountainDesktop/Models/AppSettingsSnapshot.cs b/LanMountainDesktop/Models/AppSettingsSnapshot.cs index 8e439da..390f899 100644 --- a/LanMountainDesktop/Models/AppSettingsSnapshot.cs +++ b/LanMountainDesktop/Models/AppSettingsSnapshot.cs @@ -48,6 +48,8 @@ public sealed class AppSettingsSnapshot public bool AutoStartWithWindows { get; set; } + public string AppRenderMode { get; set; } = "Default"; + public bool AutoCheckUpdates { get; set; } = true; public bool IncludePrereleaseUpdates { get; set; } diff --git a/LanMountainDesktop/Program.cs b/LanMountainDesktop/Program.cs index 042fc6d..abd28dc 100644 --- a/LanMountainDesktop/Program.cs +++ b/LanMountainDesktop/Program.cs @@ -1,5 +1,6 @@ -using Avalonia; +using Avalonia; using Avalonia.WebView.Desktop; +using LanMountainDesktop.Services; using System; namespace LanMountainDesktop; @@ -10,14 +11,42 @@ sealed class Program // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. [STAThread] - public static void Main(string[] args) => BuildAvaloniaApp() + public static void Main(string[] args) => BuildAvaloniaApp(LoadConfiguredRenderMode()) .StartWithClassicDesktopLifetime(args); // Avalonia configuration, don't remove; also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() + public static AppBuilder BuildAvaloniaApp(string renderMode = AppRenderingModeHelper.Default) + { + var builder = AppBuilder.Configure() .UsePlatformDetect() .UseDesktopWebView() .WithInterFont() .LogToTrace(); + + if (OperatingSystem.IsWindows()) + { + var configuredModes = AppRenderingModeHelper.GetWin32RenderingModes(renderMode); + if (configuredModes is { Length: > 0 }) + { + builder = builder.With(new Win32PlatformOptions + { + RenderingMode = configuredModes + }); + } + } + + return builder; + } + + private static string LoadConfiguredRenderMode() + { + try + { + return AppRenderingModeHelper.Normalize(new AppSettingsService().Load().AppRenderMode); + } + catch + { + return AppRenderingModeHelper.Default; + } + } } diff --git a/LanMountainDesktop/Services/AppRenderingModeHelper.cs b/LanMountainDesktop/Services/AppRenderingModeHelper.cs new file mode 100644 index 0000000..30e6ef1 --- /dev/null +++ b/LanMountainDesktop/Services/AppRenderingModeHelper.cs @@ -0,0 +1,42 @@ +using Avalonia; + +namespace LanMountainDesktop.Services; + +public static class AppRenderingModeHelper +{ + public const string Default = "Default"; + public const string Software = "Software"; + public const string AngleEgl = "AngleEgl"; + public const string Wgl = "Wgl"; + public const string Vulkan = "Vulkan"; + + public static string Normalize(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return Default; + } + + return value.Trim().ToUpperInvariant() switch + { + "SOFTWARE" => Software, + "ANGLEEGL" => AngleEgl, + "ANGLE_EGL" => AngleEgl, + "WGL" => Wgl, + "VULKAN" => Vulkan, + _ => Default + }; + } + + public static Win32RenderingMode[]? GetWin32RenderingModes(string? value) + { + return Normalize(value) switch + { + Software => [Win32RenderingMode.Software], + AngleEgl => [Win32RenderingMode.AngleEgl, Win32RenderingMode.Software], + Wgl => [Win32RenderingMode.Wgl, Win32RenderingMode.Software], + Vulkan => [Win32RenderingMode.Vulkan, Win32RenderingMode.Software], + _ => null + }; + } +} diff --git a/LanMountainDesktop/Views/MainWindow.Localization.cs b/LanMountainDesktop/Views/MainWindow.Localization.cs index d3a0582..a78c4ed 100644 --- a/LanMountainDesktop/Views/MainWindow.Localization.cs +++ b/LanMountainDesktop/Views/MainWindow.Localization.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Layout; using FluentIcons.Avalonia; using FluentIcons.Common; +using LanMountainDesktop.Services; namespace LanMountainDesktop.Views; @@ -315,6 +317,15 @@ public partial class MainWindow AboutStartupSettingsExpander.Description = L( "settings.about.startup_desc", "Launch the app automatically when signing in to Windows."); + AboutRenderModeSettingsExpander.Header = L("settings.about.render_mode_header", "Rendering Mode"); + AboutRenderModeSettingsExpander.Description = L( + "settings.about.render_mode_desc", + "Choose the rendering backend. Restart the app after changing this option. Unsupported modes fall back to software."); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Default, L("settings.about.render_mode.default", "Default")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Software, L("settings.about.render_mode.software", "Software")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.AngleEgl, L("settings.about.render_mode.angle_egl", "angleEgl")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan")); if (WallpaperPlacementComboBox?.ItemCount >= 5) { @@ -341,6 +352,19 @@ public partial class MainWindow UpdateWallpaperDisplay(); } + private void SetAppRenderModeComboItemContent(string tag, string content) + { + var item = AppRenderModeComboBox.Items + .OfType() + .FirstOrDefault(candidate => + string.Equals(candidate.Tag?.ToString(), tag, StringComparison.OrdinalIgnoreCase)); + + if (item is not null) + { + item.Content = content; + } + } + private string GetLocalizedTimeZoneDisplayName(TimeZoneInfo timeZone) { var offset = timeZone.GetUtcOffset(DateTime.UtcNow); diff --git a/LanMountainDesktop/Views/MainWindow.Settings.cs b/LanMountainDesktop/Views/MainWindow.Settings.cs index 270fa9f..1489cb3 100644 --- a/LanMountainDesktop/Views/MainWindow.Settings.cs +++ b/LanMountainDesktop/Views/MainWindow.Settings.cs @@ -978,6 +978,7 @@ public partial class MainWindow InitializeWeatherSettings(snapshot); _ = _componentSettingsService.Load(); InitializeAutoStartWithWindowsSetting(snapshot); + InitializeAppRenderModeSetting(snapshot); InitializeUpdateSettings(snapshot); InitializeDesktopSurfaceState(desktopLayoutSnapshot); InitializeLauncherVisibilitySettings(launcherSnapshot); @@ -1040,6 +1041,7 @@ public partial class MainWindow snapshot.WeatherIconPackId = _weatherIconPackId; snapshot.WeatherNoTlsRequests = _weatherNoTlsRequests; snapshot.AutoStartWithWindows = _autoStartWithWindows; + snapshot.AppRenderMode = _selectedAppRenderMode; snapshot.AutoCheckUpdates = _autoCheckUpdates; snapshot.IncludePrereleaseUpdates = IncludePrereleaseUpdates; snapshot.UpdateChannel = IncludePrereleaseUpdates ? UpdateChannelPreview : UpdateChannelStable; @@ -1220,6 +1222,43 @@ public partial class MainWindow } } + private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot) + { + _selectedAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode); + + if (AppRenderModeComboBox is null) + { + return; + } + + _suppressAppRenderModeSelectionEvents = true; + try + { + AppRenderModeComboBox.IsEnabled = OperatingSystem.IsWindows(); + SelectAppRenderModeInUi(_selectedAppRenderMode); + } + finally + { + _suppressAppRenderModeSelectionEvents = false; + } + } + + private void SelectAppRenderModeInUi(string renderMode) + { + if (AppRenderModeComboBox is null) + { + return; + } + + var selectedItem = AppRenderModeComboBox.Items + .OfType() + .FirstOrDefault(item => + string.Equals(item.Tag?.ToString(), renderMode, StringComparison.OrdinalIgnoreCase)); + + AppRenderModeComboBox.SelectedItem = selectedItem + ?? AppRenderModeComboBox.Items.OfType().FirstOrDefault(); + } + private static WeatherLocationMode ParseWeatherLocationMode(string? value) { return string.Equals(value, "Coordinates", StringComparison.OrdinalIgnoreCase) @@ -1487,6 +1526,25 @@ public partial class MainWindow PersistSettings(); } + private void OnAppRenderModeSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (_suppressAppRenderModeSelectionEvents || AppRenderModeComboBox is null) + { + return; + } + + var selectedMode = AppRenderingModeHelper.Normalize( + (AppRenderModeComboBox.SelectedItem as ComboBoxItem)?.Tag?.ToString()); + + if (string.Equals(_selectedAppRenderMode, selectedMode, StringComparison.Ordinal)) + { + return; + } + + _selectedAppRenderMode = selectedMode; + PersistSettings(); + } + private async void OnSearchWeatherCityClick(object? sender, RoutedEventArgs e) { if (_isWeatherSearchInProgress || WeatherCitySearchTextBox is null || WeatherCityResultsComboBox is null) @@ -2640,7 +2698,9 @@ public partial class MainWindow // --- AboutSettingsPage --- internal TextBlock AboutPanelTitleTextBlock => AboutSettingsPanel.FindControl("AboutPanelTitleTextBlock")!; internal FluentAvalonia.UI.Controls.SettingsExpander AboutStartupSettingsExpander => AboutSettingsPanel.FindControl("AboutStartupSettingsExpander")!; + internal FluentAvalonia.UI.Controls.SettingsExpander AboutRenderModeSettingsExpander => AboutSettingsPanel.FindControl("AboutRenderModeSettingsExpander")!; internal ToggleSwitch AutoStartWithWindowsToggleSwitch => AboutSettingsPanel.FindControl("AutoStartWithWindowsToggleSwitch")!; + internal ComboBox AppRenderModeComboBox => AboutSettingsPanel.FindControl("AppRenderModeComboBox")!; internal TextBlock VersionTextBlock => AboutSettingsPanel.FindControl("VersionTextBlock")!; internal TextBlock CodeNameTextBlock => AboutSettingsPanel.FindControl("CodeNameTextBlock")!; internal TextBlock FontInfoTextBlock => AboutSettingsPanel.FindControl("FontInfoTextBlock")!; diff --git a/LanMountainDesktop/Views/MainWindow.axaml.cs b/LanMountainDesktop/Views/MainWindow.axaml.cs index 78b14b5..126c77e 100644 --- a/LanMountainDesktop/Views/MainWindow.axaml.cs +++ b/LanMountainDesktop/Views/MainWindow.axaml.cs @@ -167,6 +167,8 @@ public partial class MainWindow : Window private bool _weatherNoTlsRequests; private bool _autoStartWithWindows; private bool _suppressAutoStartToggleEvents; + private bool _suppressAppRenderModeSelectionEvents; + private string _selectedAppRenderMode = AppRenderingModeHelper.Default; private string _weatherSearchKeyword = string.Empty; private bool _isWeatherSearchInProgress; private bool _isWeatherPreviewInProgress; @@ -248,6 +250,7 @@ public partial class MainWindow : Window AutoStartWithWindowsToggleSwitch.Checked += OnAutoStartWithWindowsToggled; AutoStartWithWindowsToggleSwitch.Unchecked += OnAutoStartWithWindowsToggled; + AppRenderModeComboBox.SelectionChanged += OnAppRenderModeSelectionChanged; } protected override void OnOpened(EventArgs e) diff --git a/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml index 2218e3d..b0d9d29 100644 --- a/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/AboutSettingsPage.axaml @@ -34,5 +34,27 @@ + + + + + + + + + + + + + + + + + diff --git a/LanMountainDesktop/Views/SettingsWindow.Controls.cs b/LanMountainDesktop/Views/SettingsWindow.Controls.cs index 8d97aa9..94e97ac 100644 --- a/LanMountainDesktop/Views/SettingsWindow.Controls.cs +++ b/LanMountainDesktop/Views/SettingsWindow.Controls.cs @@ -189,7 +189,9 @@ public partial class SettingsWindow // --- AboutSettingsPage --- internal TextBlock AboutPanelTitleTextBlock => AboutSettingsPanel.FindControl("AboutPanelTitleTextBlock")!; internal FluentAvalonia.UI.Controls.SettingsExpander AboutStartupSettingsExpander => AboutSettingsPanel.FindControl("AboutStartupSettingsExpander")!; + internal FluentAvalonia.UI.Controls.SettingsExpander AboutRenderModeSettingsExpander => AboutSettingsPanel.FindControl("AboutRenderModeSettingsExpander")!; internal ToggleSwitch AutoStartWithWindowsToggleSwitch => AboutSettingsPanel.FindControl("AutoStartWithWindowsToggleSwitch")!; + internal ComboBox AppRenderModeComboBox => AboutSettingsPanel.FindControl("AppRenderModeComboBox")!; internal TextBlock VersionTextBlock => AboutSettingsPanel.FindControl("VersionTextBlock")!; internal TextBlock CodeNameTextBlock => AboutSettingsPanel.FindControl("CodeNameTextBlock")!; internal TextBlock FontInfoTextBlock => AboutSettingsPanel.FindControl("FontInfoTextBlock")!; diff --git a/LanMountainDesktop/Views/SettingsWindow.Core.cs b/LanMountainDesktop/Views/SettingsWindow.Core.cs index 39cdb50..6ef04a3 100644 --- a/LanMountainDesktop/Views/SettingsWindow.Core.cs +++ b/LanMountainDesktop/Views/SettingsWindow.Core.cs @@ -128,6 +128,7 @@ public partial class SettingsWindow snapshot.WeatherIconPackId = _weatherIconPackId; snapshot.WeatherNoTlsRequests = _weatherNoTlsRequests; snapshot.AutoStartWithWindows = _autoStartWithWindows; + snapshot.AppRenderMode = _selectedAppRenderMode; snapshot.AutoCheckUpdates = _autoCheckUpdates; snapshot.IncludePrereleaseUpdates = IncludePrereleaseUpdates; snapshot.UpdateChannel = IncludePrereleaseUpdates ? UpdateChannelPreview : UpdateChannelStable; diff --git a/LanMountainDesktop/Views/SettingsWindow.Localization.cs b/LanMountainDesktop/Views/SettingsWindow.Localization.cs index 02722e7..187cc15 100644 --- a/LanMountainDesktop/Views/SettingsWindow.Localization.cs +++ b/LanMountainDesktop/Views/SettingsWindow.Localization.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Avalonia.Controls; +using LanMountainDesktop.Services; namespace LanMountainDesktop.Views; @@ -126,6 +127,15 @@ public partial class SettingsWindow FontInfoTextBlock.Text = Lf("settings.about.font_format", "Font: {0}", AppFontName); AboutStartupSettingsExpander.Header = L("settings.about.startup_header", "Windows Startup"); AboutStartupSettingsExpander.Description = L("settings.about.startup_desc", "Launch the app automatically when signing in to Windows."); + AboutRenderModeSettingsExpander.Header = L("settings.about.render_mode_header", "Rendering Mode"); + AboutRenderModeSettingsExpander.Description = L( + "settings.about.render_mode_desc", + "Choose the rendering backend. Restart the app after changing this option. Unsupported modes fall back to software."); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Default, L("settings.about.render_mode.default", "Default")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Software, L("settings.about.render_mode.software", "Software")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.AngleEgl, L("settings.about.render_mode.angle_egl", "angleEgl")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL")); + SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan")); var placementItems = WallpaperPlacementComboBox.Items.OfType().ToList(); if (placementItems.Count >= 5) @@ -142,6 +152,19 @@ public partial class SettingsWindow RenderLauncherHiddenItemsList(); } + private void SetAppRenderModeComboItemContent(string tag, string content) + { + var item = AppRenderModeComboBox.Items + .OfType() + .FirstOrDefault(candidate => + string.Equals(candidate.Tag?.ToString(), tag, StringComparison.OrdinalIgnoreCase)); + + if (item is not null) + { + item.Content = content; + } + } + private string GetLocalizedTimeZoneDisplayName(TimeZoneInfo timeZone) { var offset = timeZone.GetUtcOffset(DateTime.UtcNow); diff --git a/LanMountainDesktop/Views/SettingsWindow.WeatherLauncher.cs b/LanMountainDesktop/Views/SettingsWindow.WeatherLauncher.cs index 8f856ac..f4172bc 100644 --- a/LanMountainDesktop/Views/SettingsWindow.WeatherLauncher.cs +++ b/LanMountainDesktop/Views/SettingsWindow.WeatherLauncher.cs @@ -89,6 +89,33 @@ public partial class SettingsWindow } } + private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot) + { + _selectedAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode); + + _suppressAppRenderModeSelectionEvents = true; + try + { + AppRenderModeComboBox.IsEnabled = OperatingSystem.IsWindows(); + SelectAppRenderModeInUi(_selectedAppRenderMode); + } + finally + { + _suppressAppRenderModeSelectionEvents = false; + } + } + + private void SelectAppRenderModeInUi(string renderMode) + { + var selectedItem = AppRenderModeComboBox.Items + .OfType() + .FirstOrDefault(item => + string.Equals(item.Tag?.ToString(), renderMode, StringComparison.OrdinalIgnoreCase)); + + AppRenderModeComboBox.SelectedItem = selectedItem + ?? AppRenderModeComboBox.Items.OfType().FirstOrDefault(); + } + private static WeatherLocationMode ParseWeatherLocationMode(string? value) { return string.Equals(value, "Coordinates", StringComparison.OrdinalIgnoreCase) @@ -319,6 +346,25 @@ public partial class SettingsWindow PersistSettings(); } + private void OnAppRenderModeSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (_suppressAppRenderModeSelectionEvents) + { + return; + } + + var selectedMode = AppRenderingModeHelper.Normalize( + (AppRenderModeComboBox.SelectedItem as ComboBoxItem)?.Tag?.ToString()); + + if (string.Equals(_selectedAppRenderMode, selectedMode, StringComparison.Ordinal)) + { + return; + } + + _selectedAppRenderMode = selectedMode; + PersistSettings(); + } + private async void OnSearchWeatherCityClick(object? sender, RoutedEventArgs e) { if (_isWeatherSearchInProgress) diff --git a/LanMountainDesktop/Views/SettingsWindow.axaml.cs b/LanMountainDesktop/Views/SettingsWindow.axaml.cs index c48027f..5364fc8 100644 --- a/LanMountainDesktop/Views/SettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/SettingsWindow.axaml.cs @@ -120,6 +120,7 @@ public partial class SettingsWindow : Window private bool _suppressGridInsetEvents; private bool _suppressStatusBarSpacingEvents; private bool _suppressAutoStartToggleEvents; + private bool _suppressAppRenderModeSelectionEvents; private bool _isUpdatingWallpaperPreviewLayout; private IBrush? _defaultDesktopBackground; private Bitmap? _wallpaperBitmap; @@ -140,6 +141,7 @@ public partial class SettingsWindow : Window private string _statusBarSpacingMode = "Relaxed"; private int _statusBarCustomSpacingPercent = 12; private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent; + private string _selectedAppRenderMode = AppRenderingModeHelper.Default; private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle; private string _languageCode = "zh-CN"; private WeatherLocationMode _weatherLocationMode = WeatherLocationMode.CitySearch; @@ -222,6 +224,7 @@ public partial class SettingsWindow : Window DownloadAndInstallUpdateButton.Click += OnDownloadAndInstallUpdateClick; AutoStartWithWindowsToggleSwitch.Checked += OnAutoStartWithWindowsToggled; AutoStartWithWindowsToggleSwitch.Unchecked += OnAutoStartWithWindowsToggled; + AppRenderModeComboBox.SelectionChanged += OnAppRenderModeSelectionChanged; Opened += OnWindowOpened; } @@ -260,6 +263,7 @@ public partial class SettingsWindow : Window InitializeLocalization(snapshot.LanguageCode); InitializeWeatherSettings(snapshot); InitializeAutoStartWithWindowsSetting(snapshot); + InitializeAppRenderModeSetting(snapshot); InitializeUpdateSettings(snapshot); InitializeLauncherVisibilitySettings(launcherSnapshot); InitializeSettingsIcons();