现已支持更改关键设置时提醒重启功能
This commit is contained in:
lincube
2026-03-09 22:26:42 +08:00
parent e97db00999
commit ec7b78bc63
17 changed files with 564 additions and 100 deletions

View File

@@ -96,15 +96,7 @@ public partial class App : Application
private void OnTrayRestartClick(object? sender, EventArgs e) private void OnTrayRestartClick(object? sender, EventArgs e)
{ {
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) AppRestartService.TryRestartApplication();
{
return;
}
if (AppRestartService.TryRestartCurrentProcess())
{
desktop.Shutdown();
}
} }
private void DisableAvaloniaDataAnnotationValidation() private void DisableAvaloniaDataAnnotationValidation()

View File

@@ -249,6 +249,13 @@
"settings.about.render_mode.current_format": "Current backend: {0}", "settings.about.render_mode.current_format": "Current backend: {0}",
"settings.about.render_mode.impl_format": "Runtime implementation: {0}", "settings.about.render_mode.impl_format": "Runtime implementation: {0}",
"settings.about.render_mode.impl_unavailable": "Runtime implementation details are unavailable.", "settings.about.render_mode.impl_unavailable": "Runtime implementation details are unavailable.",
"settings.restart_dialog.title": "Restart required",
"settings.restart_dialog.render_mode_message": "Restart the app to switch the rendering mode from \"{0}\" to \"{1}\". Restart now?",
"settings.restart_dialog.restart": "Restart now",
"settings.restart_dialog.cancel": "Cancel",
"settings.restart_dock.title": "Restart required",
"settings.restart_dock.description": "Some changes will take effect after restarting the app.",
"settings.restart_dock.button": "Restart app",
"settings.footer": "LanMountainDesktop Settings", "settings.footer": "LanMountainDesktop Settings",
"filepicker.title": "Select wallpaper", "filepicker.title": "Select wallpaper",
"filepicker.image_files": "Image files", "filepicker.image_files": "Image files",

View File

@@ -249,6 +249,13 @@
"settings.about.render_mode.current_format": "当前后端:{0}", "settings.about.render_mode.current_format": "当前后端:{0}",
"settings.about.render_mode.impl_format": "运行时实现:{0}", "settings.about.render_mode.impl_format": "运行时实现:{0}",
"settings.about.render_mode.impl_unavailable": "当前无法获取运行时实现信息。", "settings.about.render_mode.impl_unavailable": "当前无法获取运行时实现信息。",
"settings.restart_dialog.title": "需要重启应用",
"settings.restart_dialog.render_mode_message": "需要重启应用,才能将渲染模式从“{0}”切换到“{1}”。是否现在重启?",
"settings.restart_dialog.restart": "立即重启",
"settings.restart_dialog.cancel": "取消",
"settings.restart_dock.title": "需要重启应用",
"settings.restart_dock.description": "部分更改需要在重启应用后才会生效。",
"settings.restart_dock.button": "重启应用",
"settings.footer": "LanMountainDesktop 设置", "settings.footer": "LanMountainDesktop 设置",
"filepicker.title": "选择壁纸", "filepicker.title": "选择壁纸",
"filepicker.image_files": "图片文件", "filepicker.image_files": "图片文件",

View File

@@ -3,11 +3,28 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
namespace LanMountainDesktop.Services; namespace LanMountainDesktop.Services;
public static class AppRestartService public static class AppRestartService
{ {
public static bool TryRestartApplication()
{
if (!TryRestartCurrentProcess())
{
return false;
}
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.Shutdown();
}
return true;
}
public static bool TryRestartCurrentProcess() public static bool TryRestartCurrentProcess()
{ {
try try

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
namespace LanMountainDesktop.Services;
public static class PendingRestartStateService
{
public const string RenderModeReason = "RenderMode";
private static readonly object Gate = new();
private static readonly HashSet<string> PendingReasons = new(StringComparer.OrdinalIgnoreCase);
public static event Action? StateChanged;
public static bool HasPendingRestart
{
get
{
lock (Gate)
{
return PendingReasons.Count > 0;
}
}
}
public static bool HasPendingReason(string reason)
{
lock (Gate)
{
return PendingReasons.Contains(reason);
}
}
public static void SetPending(string reason, bool pending)
{
if (string.IsNullOrWhiteSpace(reason))
{
return;
}
var changed = false;
lock (Gate)
{
changed = pending
? PendingReasons.Add(reason)
: PendingReasons.Remove(reason);
}
if (changed)
{
StateChanged?.Invoke();
}
}
}

View File

@@ -327,6 +327,7 @@ public partial class MainWindow
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL")); SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL"));
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan")); SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan"));
UpdateCurrentRenderBackendStatus(); UpdateCurrentRenderBackendStatus();
UpdatePendingRestartDock();
if (WallpaperPlacementComboBox?.ItemCount >= 5) if (WallpaperPlacementComboBox?.ItemCount >= 5)
{ {

View File

@@ -0,0 +1,112 @@
using System.Threading.Tasks;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow
{
private bool _isRestartPromptVisible;
private void OnPendingRestartStateChanged()
{
if (Dispatcher.UIThread.CheckAccess())
{
UpdatePendingRestartDock();
return;
}
Dispatcher.UIThread.Post(UpdatePendingRestartDock);
}
private void UpdatePendingRestartDock()
{
PendingRestartDock.IsVisible = PendingRestartStateService.HasPendingRestart;
PendingRestartDockTitleTextBlock.Text = L("settings.restart_dock.title", "Restart required");
PendingRestartDockDescriptionTextBlock.Text = L(
"settings.restart_dock.description",
"Some changes will take effect after restarting the app.");
PendingRestartDockButtonTextBlock.Text = L("settings.restart_dock.button", "Restart app");
}
private async void OnPendingRestartDockButtonClick(object? sender, RoutedEventArgs e)
{
await ShowGenericRestartPromptAsync();
}
private Task ShowRenderModeRestartPromptAsync(string selectedMode)
{
var message = Lf(
"settings.restart_dialog.render_mode_message",
"Restart the app to switch the rendering mode from \"{0}\" to \"{1}\". Restart now?",
GetLocalizedAppRenderModeDisplayName(_runningAppRenderMode),
GetLocalizedAppRenderModeDisplayName(selectedMode));
return ShowRestartPromptCoreAsync(message);
}
private Task ShowGenericRestartPromptAsync()
{
return ShowRestartPromptCoreAsync(L(
"settings.restart_dock.description",
"Some changes will take effect after restarting the app."));
}
private async Task ShowRestartPromptCoreAsync(string message)
{
if (_isRestartPromptVisible)
{
return;
}
_isRestartPromptVisible = true;
try
{
var dialog = new ContentDialog
{
Title = L("settings.restart_dialog.title", "Restart required"),
Content = message,
PrimaryButtonText = L("settings.restart_dialog.restart", "Restart now"),
CloseButtonText = L("settings.restart_dialog.cancel", "Cancel"),
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync(this);
if (result == ContentDialogResult.Primary)
{
if (!AppRestartService.TryRestartApplication())
{
UpdatePendingRestartDock();
}
return;
}
UpdatePendingRestartDock();
}
finally
{
_isRestartPromptVisible = false;
}
}
private string GetLocalizedAppRenderModeDisplayName(string renderMode)
{
if (renderMode == AppRenderBackendDiagnostics.Unknown)
{
return L("settings.about.render_mode.unknown", "Unknown");
}
return AppRenderingModeHelper.Normalize(renderMode) switch
{
AppRenderingModeHelper.Software => L("settings.about.render_mode.software", "Software"),
AppRenderingModeHelper.AngleEgl => L("settings.about.render_mode.angle_egl", "angleEgl"),
AppRenderingModeHelper.Wgl => L("settings.about.render_mode.wgl", "WGL"),
AppRenderingModeHelper.Vulkan => L("settings.about.render_mode.vulkan", "Vulkan"),
_ => L("settings.about.render_mode.default", "Default")
};
}
}

View File

@@ -1225,6 +1225,10 @@ public partial class MainWindow
private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot) private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot)
{ {
_selectedAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode); _selectedAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode);
_runningAppRenderMode = ResolveActiveAppRenderModeForUi(_selectedAppRenderMode);
var renderModeForUi = PendingRestartStateService.HasPendingReason(PendingRestartStateService.RenderModeReason)
? _selectedAppRenderMode
: _runningAppRenderMode;
if (AppRenderModeComboBox is null) if (AppRenderModeComboBox is null)
{ {
@@ -1235,7 +1239,7 @@ public partial class MainWindow
try try
{ {
AppRenderModeComboBox.IsEnabled = OperatingSystem.IsWindows(); AppRenderModeComboBox.IsEnabled = OperatingSystem.IsWindows();
SelectAppRenderModeInUi(_selectedAppRenderMode); SelectAppRenderModeInUi(renderModeForUi);
} }
finally finally
{ {
@@ -1250,13 +1254,27 @@ public partial class MainWindow
return; return;
} }
var selectedItem = AppRenderModeComboBox.Items AppRenderModeComboBox.SelectedIndex = GetAppRenderModeComboBoxIndex(renderMode);
.OfType<ComboBoxItem>() }
.FirstOrDefault(item =>
string.Equals(item.Tag?.ToString(), renderMode, StringComparison.OrdinalIgnoreCase));
AppRenderModeComboBox.SelectedItem = selectedItem private static int GetAppRenderModeComboBoxIndex(string renderMode)
?? AppRenderModeComboBox.Items.OfType<ComboBoxItem>().FirstOrDefault(); {
return AppRenderingModeHelper.Normalize(renderMode) switch
{
AppRenderingModeHelper.Software => 1,
AppRenderingModeHelper.AngleEgl => 2,
AppRenderingModeHelper.Wgl => 3,
AppRenderingModeHelper.Vulkan => 4,
_ => 0
};
}
private static string ResolveActiveAppRenderModeForUi(string configuredRenderMode)
{
var detectedRenderMode = AppRenderBackendDiagnostics.Detect().ActualBackend;
return string.Equals(detectedRenderMode, AppRenderBackendDiagnostics.Unknown, StringComparison.Ordinal)
? configuredRenderMode
: AppRenderingModeHelper.Normalize(detectedRenderMode);
} }
private static WeatherLocationMode ParseWeatherLocationMode(string? value) private static WeatherLocationMode ParseWeatherLocationMode(string? value)
@@ -1534,7 +1552,7 @@ public partial class MainWindow
} }
var selectedMode = AppRenderingModeHelper.Normalize( var selectedMode = AppRenderingModeHelper.Normalize(
(AppRenderModeComboBox.SelectedItem as ComboBoxItem)?.Tag?.ToString()); TryGetSelectedComboBoxTag(AppRenderModeComboBox));
if (string.Equals(_selectedAppRenderMode, selectedMode, StringComparison.Ordinal)) if (string.Equals(_selectedAppRenderMode, selectedMode, StringComparison.Ordinal))
{ {
@@ -1543,6 +1561,14 @@ public partial class MainWindow
_selectedAppRenderMode = selectedMode; _selectedAppRenderMode = selectedMode;
PersistSettings(); PersistSettings();
var requiresRestart = !string.Equals(_runningAppRenderMode, selectedMode, StringComparison.Ordinal);
PendingRestartStateService.SetPending(PendingRestartStateService.RenderModeReason, requiresRestart);
UpdatePendingRestartDock();
if (requiresRestart)
{
_ = ShowRenderModeRestartPromptAsync(selectedMode);
}
} }
private async void OnSearchWeatherCityClick(object? sender, RoutedEventArgs e) private async void OnSearchWeatherCityClick(object? sender, RoutedEventArgs e)

View File

@@ -377,88 +377,138 @@
<Border Classes="mica-strong" <Border Classes="mica-strong"
CornerRadius="{DynamicResource DesignCornerRadiusXl}" CornerRadius="{DynamicResource DesignCornerRadiusXl}"
Padding="18"> Padding="18">
<ui:NavigationView x:Name="SettingsNavView" <Grid RowDefinitions="*,Auto"
PaneDisplayMode="Left" RowSpacing="14">
IsSettingsVisible="False" <ui:NavigationView x:Name="SettingsNavView"
OpenPaneLength="220" Grid.Row="0"
SelectionChanged="OnSettingsNavSelectionChanged"> PaneDisplayMode="Left"
<ui:NavigationView.MenuItems> IsSettingsVisible="False"
<ui:NavigationViewItem x:Name="SettingsNavWallpaperItem" Content="壁纸" Tag="Wallpaper" ToolTip.Tip="壁纸"> OpenPaneLength="220"
<ui:NavigationViewItem.IconSource> SelectionChanged="OnSettingsNavSelectionChanged">
<ic:SymbolIconSource Symbol="Wallpaper" IconVariant="Regular" /> <ui:NavigationView.MenuItems>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavWallpaperItem" Content="壁纸" Tag="Wallpaper" ToolTip.Tip="壁纸">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavGridItem" Content="网格" Tag="Grid" ToolTip.Tip="网格"> <ic:SymbolIconSource Symbol="Wallpaper" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Grid" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavGridItem" Content="网格" Tag="Grid" ToolTip.Tip="网格">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavColorItem" Content="颜色" Tag="Color" ToolTip.Tip="颜色"> <ic:SymbolIconSource Symbol="Grid" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Color" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavColorItem" Content="颜色" Tag="Color" ToolTip.Tip="颜色">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavStatusBarItem" Content="状态栏" Tag="StatusBar" ToolTip.Tip="状态栏"> <ic:SymbolIconSource Symbol="Color" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Status" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavStatusBarItem" Content="状态栏" Tag="StatusBar" ToolTip.Tip="状态栏">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavWeatherItem" Content="天气" Tag="Weather" ToolTip.Tip="天气"> <ic:SymbolIconSource Symbol="Status" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="WeatherSunny" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavWeatherItem" Content="天气" Tag="Weather" ToolTip.Tip="天气">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavRegionItem" Content="地区" Tag="Region" ToolTip.Tip="地区"> <ic:SymbolIconSource Symbol="WeatherSunny" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Globe" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavRegionItem" Content="地区" Tag="Region" ToolTip.Tip="地区">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavUpdateItem" Content="更新" Tag="Update" ToolTip.Tip="更新"> <ic:SymbolIconSource Symbol="Globe" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="ArrowSync" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavUpdateItem" Content="更新" Tag="Update" ToolTip.Tip="更新">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavAboutItem" Content="关于" Tag="About" ToolTip.Tip="关于"> <ic:SymbolIconSource Symbol="ArrowSync" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Info" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavAboutItem" Content="关于" Tag="About" ToolTip.Tip="关于">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavLauncherItem" Content="应用启动台" Tag="Launcher" ToolTip.Tip="应用启动台"> <ic:SymbolIconSource Symbol="Info" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Apps" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavLauncherItem" Content="应用启动台" Tag="Launcher" ToolTip.Tip="应用启动台">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
<ui:NavigationViewItem x:Name="SettingsNavPluginsItem" Content="插件" Tag="Plugins" ToolTip.Tip="插件"> <ic:SymbolIconSource Symbol="Apps" IconVariant="Regular" />
<ui:NavigationViewItem.IconSource> </ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="PuzzlePiece" IconVariant="Regular" /> </ui:NavigationViewItem>
</ui:NavigationViewItem.IconSource> <ui:NavigationViewItem x:Name="SettingsNavPluginsItem" Content="插件" Tag="Plugins" ToolTip.Tip="插件">
</ui:NavigationViewItem> <ui:NavigationViewItem.IconSource>
</ui:NavigationView.MenuItems> <ic:SymbolIconSource Symbol="PuzzlePiece" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
</ui:NavigationView.MenuItems>
<ScrollViewer x:Name="SettingsContentScrollViewer" <ScrollViewer x:Name="SettingsContentScrollViewer"
Padding="0,0,16,0" Padding="0,0,16,0"
HorizontalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<Grid x:Name="SettingsContentPagesHost"> <Grid x:Name="SettingsContentPagesHost">
<pages:WallpaperSettingsPage x:Name="WallpaperSettingsPanel" IsVisible="True" /> <pages:WallpaperSettingsPage x:Name="WallpaperSettingsPanel" IsVisible="True" />
<pages:GridSettingsPage x:Name="GridSettingsPanel" IsVisible="False" /> <pages:GridSettingsPage x:Name="GridSettingsPanel" IsVisible="False" />
<pages:ColorSettingsPage x:Name="ColorSettingsPanel" IsVisible="False" /> <pages:ColorSettingsPage x:Name="ColorSettingsPanel" IsVisible="False" />
<pages:StatusBarSettingsPage x:Name="StatusBarSettingsPanel" IsVisible="False" /> <pages:StatusBarSettingsPage x:Name="StatusBarSettingsPanel" IsVisible="False" />
<pages:WeatherSettingsPage x:Name="WeatherSettingsPanel" IsVisible="False" /> <pages:WeatherSettingsPage x:Name="WeatherSettingsPanel" IsVisible="False" />
<pages:RegionSettingsPage x:Name="RegionSettingsPanel" IsVisible="False" /> <pages:RegionSettingsPage x:Name="RegionSettingsPanel" IsVisible="False" />
<pages:UpdateSettingsPage x:Name="UpdateSettingsPanel" IsVisible="False" /> <pages:UpdateSettingsPage x:Name="UpdateSettingsPanel" IsVisible="False" />
<pages:LauncherSettingsPage x:Name="LauncherSettingsPanel" IsVisible="False" /> <pages:LauncherSettingsPage x:Name="LauncherSettingsPanel" IsVisible="False" />
<pages:AboutSettingsPage x:Name="AboutSettingsPanel" IsVisible="False" /> <pages:AboutSettingsPage x:Name="AboutSettingsPanel" IsVisible="False" />
<pages:PluginSettingsPage x:Name="PluginSettingsPanel" IsVisible="False" /> <pages:PluginSettingsPage x:Name="PluginSettingsPanel" IsVisible="False" />
</Grid>
</ScrollViewer>
</ui:NavigationView>
<Border x:Name="PendingRestartDock"
Grid.Row="1"
IsVisible="False"
Classes="glass-panel"
CornerRadius="18"
Padding="14,12">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="12">
<Border Width="34"
Height="34"
CornerRadius="17"
Background="{DynamicResource AdaptiveAccentBrush}">
<fi:FluentIcon Icon="ArrowSync"
IconVariant="Regular"
FontSize="16"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="2"
VerticalAlignment="Center">
<TextBlock x:Name="PendingRestartDockTitleTextBlock"
FontSize="13"
FontWeight="SemiBold"
Text="Restart required" />
<TextBlock x:Name="PendingRestartDockDescriptionTextBlock"
TextWrapping="Wrap"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
Text="Your changes will apply after restarting the app." />
</StackPanel>
<Button x:Name="PendingRestartDockButton"
Grid.Column="2"
Padding="14,8"
Click="OnPendingRestartDockButtonClick">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:FluentIcon Icon="ArrowSync"
IconVariant="Regular" />
<TextBlock x:Name="PendingRestartDockButtonTextBlock"
VerticalAlignment="Center"
Text="Restart app" />
</StackPanel>
</Button>
</Grid> </Grid>
</ScrollViewer> </Border>
</ui:NavigationView> </Grid>
</Border> </Border>
</Border> </Border>
</Grid> </Grid>

View File

@@ -169,6 +169,7 @@ public partial class MainWindow : Window
private bool _suppressAutoStartToggleEvents; private bool _suppressAutoStartToggleEvents;
private bool _suppressAppRenderModeSelectionEvents; private bool _suppressAppRenderModeSelectionEvents;
private string _selectedAppRenderMode = AppRenderingModeHelper.Default; private string _selectedAppRenderMode = AppRenderingModeHelper.Default;
private string _runningAppRenderMode = AppRenderingModeHelper.Default;
private string _weatherSearchKeyword = string.Empty; private string _weatherSearchKeyword = string.Empty;
private bool _isWeatherSearchInProgress; private bool _isWeatherSearchInProgress;
private bool _isWeatherPreviewInProgress; private bool _isWeatherPreviewInProgress;
@@ -190,6 +191,7 @@ public partial class MainWindow : Window
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault(); _fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
AppSettingsService.SettingsSaved += OnExternalAppSettingsSaved; AppSettingsService.SettingsSaved += OnExternalAppSettingsSaved;
LauncherSettingsService.SettingsSaved += OnExternalLauncherSettingsSaved; LauncherSettingsService.SettingsSaved += OnExternalLauncherSettingsSaved;
PendingRestartStateService.StateChanged += OnPendingRestartStateChanged;
PropertyChanged += OnWindowPropertyChanged; PropertyChanged += OnWindowPropertyChanged;
InitializeDesktopSurfaceSwipeHandlers(); InitializeDesktopSurfaceSwipeHandlers();
InitializeDesktopComponentDragHandlers(); InitializeDesktopComponentDragHandlers();
@@ -314,6 +316,7 @@ public partial class MainWindow : Window
InitializeWeatherSettings(snapshot); InitializeWeatherSettings(snapshot);
_ = _componentSettingsService.Load(); _ = _componentSettingsService.Load();
InitializeAutoStartWithWindowsSetting(snapshot); InitializeAutoStartWithWindowsSetting(snapshot);
InitializeAppRenderModeSetting(snapshot);
InitializeUpdateSettings(snapshot); InitializeUpdateSettings(snapshot);
InitializeDesktopSurfaceState(desktopLayoutSnapshot); InitializeDesktopSurfaceState(desktopLayoutSnapshot);
InitializeLauncherVisibilitySettings(launcherSnapshot); InitializeLauncherVisibilitySettings(launcherSnapshot);
@@ -379,6 +382,7 @@ public partial class MainWindow : Window
_wallpaperBitmap = null; _wallpaperBitmap = null;
AppSettingsService.SettingsSaved -= OnExternalAppSettingsSaved; AppSettingsService.SettingsSaved -= OnExternalAppSettingsSaved;
LauncherSettingsService.SettingsSaved -= OnExternalLauncherSettingsSaved; LauncherSettingsService.SettingsSaved -= OnExternalLauncherSettingsSaved;
PendingRestartStateService.StateChanged -= OnPendingRestartStateChanged;
PropertyChanged -= OnWindowPropertyChanged; PropertyChanged -= OnWindowPropertyChanged;
DesktopHost.SizeChanged -= OnDesktopHostSizeChanged; DesktopHost.SizeChanged -= OnDesktopHostSizeChanged;
WallpaperPreviewHost.SizeChanged -= OnWallpaperPreviewHostSizeChanged; WallpaperPreviewHost.SizeChanged -= OnWallpaperPreviewHostSizeChanged;

View File

@@ -64,6 +64,7 @@
<ui:SettingsExpander.Footer> <ui:SettingsExpander.Footer>
<ComboBox x:Name="AppRenderModeComboBox" <ComboBox x:Name="AppRenderModeComboBox"
MinWidth="180" MinWidth="180"
SelectedIndex="0"
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<ComboBoxItem Content="Default" Tag="Default" /> <ComboBoxItem Content="Default" Tag="Default" />
<ComboBoxItem Content="Software" Tag="Software" /> <ComboBoxItem Content="Software" Tag="Software" />

View File

@@ -12,6 +12,7 @@ using FluentIcons.Avalonia.Fluent;
using FluentIcons.Common; using FluentIcons.Common;
using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models; using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Views.Components; using LanMountainDesktop.Views.Components;
namespace LanMountainDesktop.Views; namespace LanMountainDesktop.Views;
@@ -43,6 +44,7 @@ public partial class SettingsWindow
} }
_launcherIconCache.Clear(); _launcherIconCache.Clear();
PendingRestartStateService.StateChanged -= OnPendingRestartStateChanged;
base.OnClosed(e); base.OnClosed(e);
} }

View File

@@ -137,6 +137,7 @@ public partial class SettingsWindow
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL")); SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL"));
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan")); SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan"));
UpdateCurrentRenderBackendStatus(); UpdateCurrentRenderBackendStatus();
UpdatePendingRestartDock();
var placementItems = WallpaperPlacementComboBox.Items.OfType<ComboBoxItem>().ToList(); var placementItems = WallpaperPlacementComboBox.Items.OfType<ComboBoxItem>().ToList();
if (placementItems.Count >= 5) if (placementItems.Count >= 5)

View File

@@ -0,0 +1,112 @@
using System.Threading.Tasks;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class SettingsWindow
{
private bool _isRestartPromptVisible;
private void OnPendingRestartStateChanged()
{
if (Dispatcher.UIThread.CheckAccess())
{
UpdatePendingRestartDock();
return;
}
Dispatcher.UIThread.Post(UpdatePendingRestartDock);
}
private void UpdatePendingRestartDock()
{
PendingRestartDock.IsVisible = PendingRestartStateService.HasPendingRestart;
PendingRestartDockTitleTextBlock.Text = L("settings.restart_dock.title", "Restart required");
PendingRestartDockDescriptionTextBlock.Text = L(
"settings.restart_dock.description",
"Some changes will take effect after restarting the app.");
PendingRestartDockButtonTextBlock.Text = L("settings.restart_dock.button", "Restart app");
}
private async void OnPendingRestartDockButtonClick(object? sender, RoutedEventArgs e)
{
await ShowGenericRestartPromptAsync();
}
private Task ShowRenderModeRestartPromptAsync(string selectedMode)
{
var message = Lf(
"settings.restart_dialog.render_mode_message",
"Restart the app to switch the rendering mode from \"{0}\" to \"{1}\". Restart now?",
GetLocalizedAppRenderModeDisplayName(_runningAppRenderMode),
GetLocalizedAppRenderModeDisplayName(selectedMode));
return ShowRestartPromptCoreAsync(message);
}
private Task ShowGenericRestartPromptAsync()
{
return ShowRestartPromptCoreAsync(L(
"settings.restart_dock.description",
"Some changes will take effect after restarting the app."));
}
private async Task ShowRestartPromptCoreAsync(string message)
{
if (_isRestartPromptVisible)
{
return;
}
_isRestartPromptVisible = true;
try
{
var dialog = new ContentDialog
{
Title = L("settings.restart_dialog.title", "Restart required"),
Content = message,
PrimaryButtonText = L("settings.restart_dialog.restart", "Restart now"),
CloseButtonText = L("settings.restart_dialog.cancel", "Cancel"),
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync(this);
if (result == ContentDialogResult.Primary)
{
if (!AppRestartService.TryRestartApplication())
{
UpdatePendingRestartDock();
}
return;
}
UpdatePendingRestartDock();
}
finally
{
_isRestartPromptVisible = false;
}
}
private string GetLocalizedAppRenderModeDisplayName(string renderMode)
{
if (renderMode == AppRenderBackendDiagnostics.Unknown)
{
return L("settings.about.render_mode.unknown", "Unknown");
}
return AppRenderingModeHelper.Normalize(renderMode) switch
{
AppRenderingModeHelper.Software => L("settings.about.render_mode.software", "Software"),
AppRenderingModeHelper.AngleEgl => L("settings.about.render_mode.angle_egl", "angleEgl"),
AppRenderingModeHelper.Wgl => L("settings.about.render_mode.wgl", "WGL"),
AppRenderingModeHelper.Vulkan => L("settings.about.render_mode.vulkan", "Vulkan"),
_ => L("settings.about.render_mode.default", "Default")
};
}
}

View File

@@ -92,12 +92,16 @@ public partial class SettingsWindow
private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot) private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot)
{ {
_selectedAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode); _selectedAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode);
_runningAppRenderMode = ResolveActiveAppRenderModeForUi(_selectedAppRenderMode);
var renderModeForUi = PendingRestartStateService.HasPendingReason(PendingRestartStateService.RenderModeReason)
? _selectedAppRenderMode
: _runningAppRenderMode;
_suppressAppRenderModeSelectionEvents = true; _suppressAppRenderModeSelectionEvents = true;
try try
{ {
AppRenderModeComboBox.IsEnabled = OperatingSystem.IsWindows(); AppRenderModeComboBox.IsEnabled = OperatingSystem.IsWindows();
SelectAppRenderModeInUi(_selectedAppRenderMode); SelectAppRenderModeInUi(renderModeForUi);
} }
finally finally
{ {
@@ -107,13 +111,27 @@ public partial class SettingsWindow
private void SelectAppRenderModeInUi(string renderMode) private void SelectAppRenderModeInUi(string renderMode)
{ {
var selectedItem = AppRenderModeComboBox.Items AppRenderModeComboBox.SelectedIndex = GetAppRenderModeComboBoxIndex(renderMode);
.OfType<ComboBoxItem>() }
.FirstOrDefault(item =>
string.Equals(item.Tag?.ToString(), renderMode, StringComparison.OrdinalIgnoreCase));
AppRenderModeComboBox.SelectedItem = selectedItem private static int GetAppRenderModeComboBoxIndex(string renderMode)
?? AppRenderModeComboBox.Items.OfType<ComboBoxItem>().FirstOrDefault(); {
return AppRenderingModeHelper.Normalize(renderMode) switch
{
AppRenderingModeHelper.Software => 1,
AppRenderingModeHelper.AngleEgl => 2,
AppRenderingModeHelper.Wgl => 3,
AppRenderingModeHelper.Vulkan => 4,
_ => 0
};
}
private static string ResolveActiveAppRenderModeForUi(string configuredRenderMode)
{
var detectedRenderMode = AppRenderBackendDiagnostics.Detect().ActualBackend;
return string.Equals(detectedRenderMode, AppRenderBackendDiagnostics.Unknown, StringComparison.Ordinal)
? configuredRenderMode
: AppRenderingModeHelper.Normalize(detectedRenderMode);
} }
private static WeatherLocationMode ParseWeatherLocationMode(string? value) private static WeatherLocationMode ParseWeatherLocationMode(string? value)
@@ -354,7 +372,7 @@ public partial class SettingsWindow
} }
var selectedMode = AppRenderingModeHelper.Normalize( var selectedMode = AppRenderingModeHelper.Normalize(
(AppRenderModeComboBox.SelectedItem as ComboBoxItem)?.Tag?.ToString()); TryGetSelectedComboBoxTag(AppRenderModeComboBox));
if (string.Equals(_selectedAppRenderMode, selectedMode, StringComparison.Ordinal)) if (string.Equals(_selectedAppRenderMode, selectedMode, StringComparison.Ordinal))
{ {
@@ -363,6 +381,14 @@ public partial class SettingsWindow
_selectedAppRenderMode = selectedMode; _selectedAppRenderMode = selectedMode;
PersistSettings(); PersistSettings();
var requiresRestart = !string.Equals(_runningAppRenderMode, selectedMode, StringComparison.Ordinal);
PendingRestartStateService.SetPending(PendingRestartStateService.RenderModeReason, requiresRestart);
UpdatePendingRestartDock();
if (requiresRestart)
{
_ = ShowRenderModeRestartPromptAsync(selectedMode);
}
} }
private async void OnSearchWeatherCityClick(object? sender, RoutedEventArgs e) private async void OnSearchWeatherCityClick(object? sender, RoutedEventArgs e)

View File

@@ -79,7 +79,10 @@
Classes="mica-strong" Classes="mica-strong"
CornerRadius="0,0,24,24" CornerRadius="0,0,24,24"
Padding="18"> Padding="18">
<Grid RowDefinitions="*,Auto"
RowSpacing="14">
<ui:NavigationView x:Name="SettingsNavView" <ui:NavigationView x:Name="SettingsNavView"
Grid.Row="0"
PaneDisplayMode="Left" PaneDisplayMode="Left"
IsSettingsVisible="False" IsSettingsVisible="False"
OpenPaneLength="240" OpenPaneLength="240"
@@ -155,6 +158,53 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</ui:NavigationView> </ui:NavigationView>
<Border x:Name="PendingRestartDock"
Grid.Row="1"
IsVisible="False"
Classes="glass-panel"
CornerRadius="18"
Padding="14,12">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="12">
<Border Width="34"
Height="34"
CornerRadius="17"
Background="{DynamicResource AdaptiveAccentBrush}">
<fi:FluentIcon Icon="ArrowSync"
IconVariant="Regular"
FontSize="16"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="2"
VerticalAlignment="Center">
<TextBlock x:Name="PendingRestartDockTitleTextBlock"
FontSize="13"
FontWeight="SemiBold"
Text="Restart required" />
<TextBlock x:Name="PendingRestartDockDescriptionTextBlock"
TextWrapping="Wrap"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
Text="Your changes will apply after restarting the app." />
</StackPanel>
<Button x:Name="PendingRestartDockButton"
Grid.Column="2"
Padding="14,8"
Click="OnPendingRestartDockButtonClick">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:FluentIcon Icon="ArrowSync"
IconVariant="Regular" />
<TextBlock x:Name="PendingRestartDockButtonTextBlock"
VerticalAlignment="Center"
Text="Restart app" />
</StackPanel>
</Button>
</Grid>
</Border>
</Grid>
</Border> </Border>
</Grid> </Grid>
</Border> </Border>

View File

@@ -142,6 +142,7 @@ public partial class SettingsWindow : Window
private int _statusBarCustomSpacingPercent = 12; private int _statusBarCustomSpacingPercent = 12;
private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent; private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent;
private string _selectedAppRenderMode = AppRenderingModeHelper.Default; private string _selectedAppRenderMode = AppRenderingModeHelper.Default;
private string _runningAppRenderMode = AppRenderingModeHelper.Default;
private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle; private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle;
private string _languageCode = "zh-CN"; private string _languageCode = "zh-CN";
private WeatherLocationMode _weatherLocationMode = WeatherLocationMode.CitySearch; private WeatherLocationMode _weatherLocationMode = WeatherLocationMode.CitySearch;
@@ -165,6 +166,7 @@ public partial class SettingsWindow : Window
InitializePluginSettingsNavigation(); InitializePluginSettingsNavigation();
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault(); _fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
RequestedThemeVariant = Application.Current?.RequestedThemeVariant ?? ThemeVariant.Default; RequestedThemeVariant = Application.Current?.RequestedThemeVariant ?? ThemeVariant.Default;
PendingRestartStateService.StateChanged += OnPendingRestartStateChanged;
HookEvents(); HookEvents();
} }