mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
setting_re1
This commit is contained in:
@@ -9,9 +9,9 @@
|
||||
<OutputPath>bin\$(Configuration)\$(TargetFramework)\content\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<PluginPackageOutputDirectory>..\..\..\LanMountainDesktop\bin\$(Configuration)\$(TargetFramework)\Extensions\Plugins\</PluginPackageOutputDirectory>
|
||||
<PluginPackagePath>$(PluginPackageOutputDirectory)$(AssemblyName).laapp</PluginPackagePath>
|
||||
<LegacyLoosePluginOutputDirectory>..\..\..\LanMountainDesktop\bin\$(Configuration)\$(TargetFramework)\Extensions\Plugins\SamplePlugin\</LegacyLoosePluginOutputDirectory>
|
||||
<PluginPackageOutputDirectory>$(MSBuildThisFileDirectory)artifacts\Packages\</PluginPackageOutputDirectory>
|
||||
<PluginPackagePath>$(PluginPackageOutputDirectory)$(AssemblyName).$(Version).laapp</PluginPackagePath>
|
||||
<LegacyLoosePluginOutputDirectory>$(MSBuildThisFileDirectory)artifacts\Loose\</LegacyLoosePluginOutputDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Threading;
|
||||
using AvaloniaWebView;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
using LanMountainDesktop.Views;
|
||||
using AvaloniaWebView;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
|
||||
namespace LanMountainDesktop;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private enum DesktopShellState
|
||||
{
|
||||
ForegroundDesktop = 0,
|
||||
MinimizedToTaskbar = 1,
|
||||
TrayOnly = 2
|
||||
}
|
||||
|
||||
private enum ShutdownIntent
|
||||
{
|
||||
None = 0,
|
||||
ExitRequested = 1,
|
||||
RestartRequested = 2
|
||||
}
|
||||
|
||||
private readonly AppSettingsService _appSettingsService = new();
|
||||
private readonly LocalizationService _localizationService = new();
|
||||
private readonly IHostApplicationLifecycle _hostApplicationLifecycle = new HostApplicationLifecycleService();
|
||||
private bool _exitCleanupCompleted;
|
||||
private DesktopShellState _desktopShellState = DesktopShellState.ForegroundDesktop;
|
||||
private ShutdownIntent _shutdownIntent;
|
||||
|
||||
private SettingsWindow? _traySettingsWindow;
|
||||
private readonly IndependentSettingsModuleService _independentSettingsModuleService = new();
|
||||
private TrayIcons? _trayIcons;
|
||||
private PluginRuntimeService? _pluginRuntimeService;
|
||||
private MainWindow? _mainWindow;
|
||||
private bool _mainWindowClosed;
|
||||
|
||||
internal static SingleInstanceService? CurrentSingleInstanceService { get; set; }
|
||||
internal static IHostApplicationLifecycle? CurrentHostApplicationLifecycle =>
|
||||
@@ -35,6 +53,11 @@ public partial class App : Application
|
||||
public PluginRuntimeService? PluginRuntimeService => _pluginRuntimeService;
|
||||
public IHostApplicationLifecycle HostApplicationLifecycle => _hostApplicationLifecycle;
|
||||
|
||||
internal void OpenIndependentSettingsModule(string source, string? pageTag = null)
|
||||
{
|
||||
_independentSettingsModuleService.ShowOrActivate(source, pageTag);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AppLogger.Info("App", "Initializing application resources.");
|
||||
@@ -62,12 +85,8 @@ public partial class App : Application
|
||||
AppLogger.Info("App", "Desktop lifetime exit triggered.");
|
||||
PerformExitCleanup();
|
||||
};
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(),
|
||||
};
|
||||
AppLogger.Info("App", $"Main window created. LogFile={AppLogger.LogFilePath}");
|
||||
LogBrowserStartupDiagnostics();
|
||||
|
||||
CreateAndAssignMainWindow(desktop, "FrameworkInitialization");
|
||||
CurrentSingleInstanceService?.StartActivationListener(ActivateMainWindow);
|
||||
}
|
||||
|
||||
@@ -81,42 +100,14 @@ public partial class App : Application
|
||||
Reason: "User selected Exit App from the tray menu."));
|
||||
}
|
||||
|
||||
private void OnTrayShowDesktopClick(object? sender, EventArgs e)
|
||||
{
|
||||
RestoreOrCreateMainWindow(showSingleInstanceNotice: false, source: "TrayMenu");
|
||||
}
|
||||
|
||||
private void OnTraySettingsClick(object? sender, EventArgs e)
|
||||
{
|
||||
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_traySettingsWindow is { } existingWindow && existingWindow.IsVisible)
|
||||
{
|
||||
existingWindow.WindowState = Avalonia.Controls.WindowState.Normal;
|
||||
existingWindow.Activate();
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsWindow = new SettingsWindow();
|
||||
settingsWindow.Closed += (_, _) =>
|
||||
{
|
||||
if (ReferenceEquals(_traySettingsWindow, settingsWindow))
|
||||
{
|
||||
_traySettingsWindow = null;
|
||||
}
|
||||
};
|
||||
|
||||
_traySettingsWindow = settingsWindow;
|
||||
settingsWindow.Show();
|
||||
settingsWindow.Activate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("TraySettings", "Failed to open settings window.", ex);
|
||||
}
|
||||
}, DispatcherPriority.Normal);
|
||||
OpenIndependentSettingsModule("TrayMenu");
|
||||
}
|
||||
|
||||
private void OnTrayRestartClick(object? sender, EventArgs e)
|
||||
@@ -209,19 +200,25 @@ public partial class App : Application
|
||||
{
|
||||
var menu = new NativeMenu();
|
||||
|
||||
var settingsItem = new NativeMenuItem(L("tray.menu.settings", "设置"));
|
||||
var showDesktopItem = new NativeMenuItem(L("tray.menu.show_desktop", "Open Desktop"));
|
||||
showDesktopItem.Click += OnTrayShowDesktopClick;
|
||||
menu.Items.Add(showDesktopItem);
|
||||
|
||||
menu.Items.Add(new NativeMenuItemSeparator());
|
||||
|
||||
var settingsItem = new NativeMenuItem(L("tray.menu.settings", "Settings"));
|
||||
settingsItem.Click += OnTraySettingsClick;
|
||||
menu.Items.Add(settingsItem);
|
||||
|
||||
menu.Items.Add(new NativeMenuItemSeparator());
|
||||
|
||||
var restartItem = new NativeMenuItem(L("tray.menu.restart", "重启应用"));
|
||||
var restartItem = new NativeMenuItem(L("tray.menu.restart", "Restart App"));
|
||||
restartItem.Click += OnTrayRestartClick;
|
||||
menu.Items.Add(restartItem);
|
||||
|
||||
menu.Items.Add(new NativeMenuItemSeparator());
|
||||
|
||||
var exitItem = new NativeMenuItem(L("tray.menu.exit", "退出应用"));
|
||||
var exitItem = new NativeMenuItem(L("tray.menu.exit", "Exit App"));
|
||||
exitItem.Click += OnTrayExitClick;
|
||||
menu.Items.Add(exitItem);
|
||||
|
||||
@@ -245,6 +242,11 @@ public partial class App : Application
|
||||
}
|
||||
|
||||
private void ActivateMainWindow()
|
||||
{
|
||||
RestoreOrCreateMainWindow(showSingleInstanceNotice: true, source: "SingleInstance");
|
||||
}
|
||||
|
||||
private void RestoreOrCreateMainWindow(bool showSingleInstanceNotice, string source)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
@@ -253,13 +255,11 @@ public partial class App : Application
|
||||
return;
|
||||
}
|
||||
|
||||
if (desktop.MainWindow is not Window mainWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var mainWindow = GetOrCreateMainWindow(desktop, source);
|
||||
mainWindow.ShowInTaskbar = true;
|
||||
|
||||
if (!mainWindow.IsVisible)
|
||||
{
|
||||
mainWindow.Show();
|
||||
@@ -278,18 +278,68 @@ public partial class App : Application
|
||||
mainWindow.Activate();
|
||||
mainWindow.Topmost = true;
|
||||
mainWindow.Topmost = false;
|
||||
if (mainWindow is MainWindow lanMountainMainWindow)
|
||||
SetDesktopShellState(DesktopShellState.ForegroundDesktop, $"Restore:{source}");
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Desktop restored. Source='{source}'; MainWindowClosed={_mainWindowClosed}; ShowSingleInstanceNotice={showSingleInstanceNotice}; WindowState='{mainWindow.WindowState}'.");
|
||||
|
||||
if (showSingleInstanceNotice)
|
||||
{
|
||||
lanMountainMainWindow.ShowSingleInstanceNotice();
|
||||
mainWindow.ShowSingleInstanceNotice();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("SingleInstance", "Failed to activate the existing main window.", ex);
|
||||
AppLogger.Warn("DesktopShell", $"Failed to restore desktop shell. Source='{source}'.", ex);
|
||||
}
|
||||
}, DispatcherPriority.Send);
|
||||
}
|
||||
|
||||
internal void PrepareForShutdown(bool isRestart, string source)
|
||||
{
|
||||
void Mark()
|
||||
{
|
||||
_shutdownIntent = isRestart
|
||||
? ShutdownIntent.RestartRequested
|
||||
: ShutdownIntent.ExitRequested;
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Shutdown intent marked. Intent='{_shutdownIntent}'; Source='{source}'; CurrentShellState='{_desktopShellState}'.");
|
||||
}
|
||||
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
Mark();
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(Mark, DispatcherPriority.Send).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
internal void ResetShutdownIntent(string source)
|
||||
{
|
||||
void Reset()
|
||||
{
|
||||
if (_shutdownIntent == ShutdownIntent.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Warn(
|
||||
"DesktopShell",
|
||||
$"Shutdown intent cleared without process exit. PreviousIntent='{_shutdownIntent}'; Source='{source}'.");
|
||||
_shutdownIntent = ShutdownIntent.None;
|
||||
}
|
||||
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(Reset, DispatcherPriority.Send).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void OnAppSettingsSaved(string _)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
@@ -311,18 +361,7 @@ public partial class App : Application
|
||||
_exitCleanupCompleted = true;
|
||||
AppSettingsService.SettingsSaved -= OnAppSettingsSaved;
|
||||
|
||||
try
|
||||
{
|
||||
_traySettingsWindow?.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("App", "Failed to close tray-opened settings window during shutdown.", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_traySettingsWindow = null;
|
||||
}
|
||||
_independentSettingsModuleService.CloseIfOpen();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -342,6 +381,171 @@ public partial class App : Application
|
||||
DisposeTrayIcon();
|
||||
}
|
||||
|
||||
private MainWindow CreateAndAssignMainWindow(
|
||||
IClassicDesktopStyleApplicationLifetime desktop,
|
||||
string reason)
|
||||
{
|
||||
var mainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(),
|
||||
ShowInTaskbar = true
|
||||
};
|
||||
|
||||
AttachMainWindow(mainWindow);
|
||||
desktop.MainWindow = mainWindow;
|
||||
AppLogger.Info("App", $"Main window created. Reason='{reason}'. LogFile={AppLogger.LogFilePath}");
|
||||
LogBrowserStartupDiagnostics();
|
||||
SetDesktopShellState(DesktopShellState.ForegroundDesktop, $"MainWindowCreated:{reason}");
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
private MainWindow GetOrCreateMainWindow(
|
||||
IClassicDesktopStyleApplicationLifetime desktop,
|
||||
string reason)
|
||||
{
|
||||
if (_mainWindow is not null && !_mainWindowClosed)
|
||||
{
|
||||
return _mainWindow;
|
||||
}
|
||||
|
||||
if (desktop.MainWindow is MainWindow desktopMainWindow && !_mainWindowClosed)
|
||||
{
|
||||
AttachMainWindow(desktopMainWindow);
|
||||
return desktopMainWindow;
|
||||
}
|
||||
|
||||
return CreateAndAssignMainWindow(desktop, reason);
|
||||
}
|
||||
|
||||
private void AttachMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
if (ReferenceEquals(_mainWindow, mainWindow))
|
||||
{
|
||||
_mainWindowClosed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mainWindow is not null)
|
||||
{
|
||||
_mainWindow.Closing -= OnMainWindowClosing;
|
||||
_mainWindow.Closed -= OnMainWindowClosed;
|
||||
_mainWindow.PropertyChanged -= OnMainWindowPropertyChanged;
|
||||
}
|
||||
|
||||
_mainWindow = mainWindow;
|
||||
_mainWindowClosed = false;
|
||||
mainWindow.Closing += OnMainWindowClosing;
|
||||
mainWindow.Closed += OnMainWindowClosed;
|
||||
mainWindow.PropertyChanged += OnMainWindowPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnMainWindowClosing(object? sender, WindowClosingEventArgs e)
|
||||
{
|
||||
if (sender is not MainWindow mainWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Main window closing requested. Intent='{_shutdownIntent}'; ShellState='{_desktopShellState}'; WindowState='{mainWindow.WindowState}'; IsVisible={mainWindow.IsVisible}.");
|
||||
|
||||
if (_shutdownIntent is ShutdownIntent.ExitRequested or ShutdownIntent.RestartRequested)
|
||||
{
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Main window close allowed. Intent='{_shutdownIntent}'; ShellState='{_desktopShellState}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
e.Cancel = true;
|
||||
HideMainWindowToTray(mainWindow, "MainWindowClosing");
|
||||
}
|
||||
|
||||
private void OnMainWindowClosed(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender is not MainWindow mainWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.Closing -= OnMainWindowClosing;
|
||||
mainWindow.Closed -= OnMainWindowClosed;
|
||||
mainWindow.PropertyChanged -= OnMainWindowPropertyChanged;
|
||||
|
||||
if (ReferenceEquals(_mainWindow, mainWindow))
|
||||
{
|
||||
_mainWindow = null;
|
||||
}
|
||||
|
||||
_mainWindowClosed = true;
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Main window closed. Intent='{_shutdownIntent}'; ShellState='{_desktopShellState}'.");
|
||||
|
||||
if (_shutdownIntent == ShutdownIntent.None)
|
||||
{
|
||||
SetDesktopShellState(DesktopShellState.TrayOnly, "MainWindowClosedUnexpected");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMainWindowPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is not MainWindow mainWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Property != Window.WindowStateProperty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_shutdownIntent != ShutdownIntent.None || !mainWindow.IsVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainWindow.WindowState == WindowState.Minimized)
|
||||
{
|
||||
SetDesktopShellState(DesktopShellState.MinimizedToTaskbar, "MainWindowMinimized");
|
||||
return;
|
||||
}
|
||||
|
||||
SetDesktopShellState(DesktopShellState.ForegroundDesktop, "MainWindowRestored");
|
||||
}
|
||||
|
||||
private void HideMainWindowToTray(MainWindow mainWindow, string source)
|
||||
{
|
||||
try
|
||||
{
|
||||
mainWindow.ShowInTaskbar = false;
|
||||
mainWindow.Hide();
|
||||
SetDesktopShellState(DesktopShellState.TrayOnly, source);
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Main window hidden to tray. Source='{source}'; WindowState='{mainWindow.WindowState}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("DesktopShell", $"Failed to hide main window to tray. Source='{source}'.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDesktopShellState(DesktopShellState state, string source)
|
||||
{
|
||||
if (_desktopShellState == state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var previous = _desktopShellState;
|
||||
_desktopShellState = state;
|
||||
AppLogger.Info(
|
||||
"DesktopShell",
|
||||
$"Shell state changed. Previous='{previous}'; Current='{state}'; Source='{source}'.");
|
||||
}
|
||||
|
||||
private void LogBrowserStartupDiagnostics()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"app.title": "LanMountainDesktop",
|
||||
"tray.tooltip": "LanMountainDesktop",
|
||||
"tray.menu.show_desktop": "Open Desktop",
|
||||
"tray.menu.settings": "Settings",
|
||||
"tray.menu.restart": "Restart App",
|
||||
"tray.menu.exit": "Exit App",
|
||||
@@ -8,10 +9,10 @@
|
||||
"tooltip.back_to_windows": "Back to Windows",
|
||||
"tooltip.open_settings": "Settings",
|
||||
"settings.title": "Settings",
|
||||
"settings.shell.title": "Application Settings",
|
||||
"settings.shell.subtitle": "LanMountainDesktop standalone preferences",
|
||||
"settings.shell.title": "Settings",
|
||||
"settings.shell.subtitle": "LanMountainDesktop independent settings module",
|
||||
"settings.shell.sidebar_hint": "Choose a category to adjust application behavior, desktop layout, and appearance.",
|
||||
"settings.shell.footer_hint": "Tray-opened settings are managed in this standalone window.",
|
||||
"settings.shell.footer_hint": "Tray-opened settings are managed in this independent settings module.",
|
||||
"settings.back_to_desktop": "Back to Desktop",
|
||||
"settings.nav_header": "Settings",
|
||||
"settings.nav.group_desktop": "Desktop",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"app.title": "LanMountainDesktop",
|
||||
"tray.tooltip": "LanMountainDesktop",
|
||||
"tray.menu.show_desktop": "打开桌面",
|
||||
"tray.menu.settings": "设置",
|
||||
"tray.menu.restart": "重启应用",
|
||||
"tray.menu.exit": "退出应用",
|
||||
@@ -8,10 +9,10 @@
|
||||
"tooltip.back_to_windows": "回到Windows",
|
||||
"tooltip.open_settings": "设置",
|
||||
"settings.title": "设置",
|
||||
"settings.shell.title": "应用设置",
|
||||
"settings.shell.subtitle": "LanMountainDesktop 独立设置窗口",
|
||||
"settings.shell.title": "设置",
|
||||
"settings.shell.subtitle": "LanMountainDesktop 独立设置模块",
|
||||
"settings.shell.sidebar_hint": "选择一个分类以调整应用行为、桌面布局与外观。",
|
||||
"settings.shell.footer_hint": "托盘菜单打开的设置会统一在这个独立窗口中管理。",
|
||||
"settings.shell.footer_hint": "托盘菜单打开的设置会统一在这个独立设置模块中管理。",
|
||||
"settings.back_to_desktop": "返回桌面",
|
||||
"settings.nav_header": "设置选项",
|
||||
"settings.nav.group_desktop": "桌面",
|
||||
|
||||
@@ -11,18 +11,21 @@ public sealed class HostApplicationLifecycleService : IHostApplicationLifecycle
|
||||
{
|
||||
public bool TryExit(HostApplicationLifecycleRequest? request = null)
|
||||
{
|
||||
App? app = null;
|
||||
try
|
||||
{
|
||||
AppLogger.Info(
|
||||
"HostLifecycle",
|
||||
$"Exit requested. Source='{request?.Source ?? "Unknown"}'; Reason='{request?.Reason ?? string.Empty}'.");
|
||||
|
||||
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
|
||||
app = Application.Current as App;
|
||||
if (app?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
AppLogger.Warn("HostLifecycle", "Exit request ignored because desktop lifetime is unavailable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
app.PrepareForShutdown(isRestart: false, request?.Source ?? "Unknown");
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
desktop.Shutdown();
|
||||
@@ -36,6 +39,7 @@ public sealed class HostApplicationLifecycleService : IHostApplicationLifecycle
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
app?.ResetShutdownIntent(request?.Source ?? "Unknown");
|
||||
AppLogger.Warn("HostLifecycle", "Failed to exit the application.", ex);
|
||||
return false;
|
||||
}
|
||||
@@ -43,6 +47,7 @@ public sealed class HostApplicationLifecycleService : IHostApplicationLifecycle
|
||||
|
||||
public bool TryRestart(HostApplicationLifecycleRequest? request = null)
|
||||
{
|
||||
App? app = null;
|
||||
try
|
||||
{
|
||||
var startInfo = AppRestartService.CreateRestartStartInfo();
|
||||
@@ -55,6 +60,8 @@ public sealed class HostApplicationLifecycleService : IHostApplicationLifecycle
|
||||
}
|
||||
|
||||
Process.Start(startInfo);
|
||||
app = Application.Current as App;
|
||||
app?.PrepareForShutdown(isRestart: true, request?.Source ?? "Unknown");
|
||||
var exitRequest = request is null
|
||||
? new HostApplicationLifecycleRequest(Reason: "Restart accepted.")
|
||||
: request with
|
||||
@@ -68,6 +75,7 @@ public sealed class HostApplicationLifecycleService : IHostApplicationLifecycle
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
app?.ResetShutdownIntent(request?.Source ?? "Unknown");
|
||||
AppLogger.Warn("HostLifecycle", "Failed to restart the application.", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using Avalonia.Threading;
|
||||
using LanMountainDesktop.Views;
|
||||
|
||||
namespace LanMountainDesktop.Services;
|
||||
|
||||
internal sealed class IndependentSettingsModuleService
|
||||
{
|
||||
private SettingsWindow? _window;
|
||||
|
||||
public void ShowOrActivate(string source, string? pageTag = null)
|
||||
{
|
||||
AppLogger.Info("IndependentSettingsModule", $"OpenRequested; Source='{source}'; PageTag='{pageTag ?? "<default>"}'.");
|
||||
|
||||
void ShowCore()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_window is not { } window)
|
||||
{
|
||||
AppLogger.Info("IndependentSettingsModule", $"WindowConstructionStarted; Source='{source}'.");
|
||||
window = new SettingsWindow();
|
||||
AppLogger.Info("IndependentSettingsModule", $"WindowConstructionCompleted; Source='{source}'.");
|
||||
window.Closed += (_, _) =>
|
||||
{
|
||||
if (ReferenceEquals(_window, window))
|
||||
{
|
||||
_window = null;
|
||||
}
|
||||
|
||||
AppLogger.Info("IndependentSettingsModule", "WindowClosed.");
|
||||
};
|
||||
_window = window;
|
||||
}
|
||||
|
||||
window.Open(pageTag);
|
||||
AppLogger.Info(
|
||||
"IndependentSettingsModule",
|
||||
$"WindowActivated; Source='{source}'; ReusedExisting={ReferenceEquals(_window, window)}; WasVisible={window.IsVisible}; PageTag='{pageTag ?? "<default>"}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("IndependentSettingsModule", $"Failed to open independent settings module window. Source='{source}'.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
ShowCore();
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(ShowCore, DispatcherPriority.Normal);
|
||||
}
|
||||
|
||||
public void CloseIfOpen()
|
||||
{
|
||||
void CloseCore()
|
||||
{
|
||||
if (_window is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_window.PrepareForForceClose();
|
||||
_window.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("IndependentSettingsModule", "Failed to close independent settings module window during shutdown.", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_window = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (Dispatcher.UIThread.CheckAccess())
|
||||
{
|
||||
CloseCore();
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(CloseCore, DispatcherPriority.Send);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Windowing;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public class IndependentSettingsModuleWindowBase : AppWindow
|
||||
{
|
||||
public IndependentSettingsModuleWindowBase()
|
||||
{
|
||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
|
||||
TitleBar.Height = 48;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
TransparencyLevelHint = [WindowTransparencyLevel.Mica];
|
||||
Background = Brushes.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
internal enum IndependentSettingsPageCategory
|
||||
{
|
||||
Internal = 0,
|
||||
External = 1,
|
||||
About = 2,
|
||||
Debug = 3
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentIcons.Common;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
internal sealed record IndependentSettingsPageDefinition(
|
||||
string Tag,
|
||||
string Title,
|
||||
string Description,
|
||||
Symbol Icon,
|
||||
IndependentSettingsPageCategory Category,
|
||||
int SortOrder,
|
||||
string? ToolTip = null);
|
||||
@@ -410,7 +410,10 @@ public partial class MainWindow
|
||||
_reopenSettingsAfterComponentLibraryClose = false;
|
||||
if (shouldReopenSettings)
|
||||
{
|
||||
OpenSettingsPage();
|
||||
if (Application.Current is App app)
|
||||
{
|
||||
app.OpenIndependentSettingsModule("ComponentLibrary");
|
||||
}
|
||||
}
|
||||
}, FluttermotionToken.Slow);
|
||||
}
|
||||
|
||||
@@ -80,11 +80,13 @@ public partial class MainWindow
|
||||
|
||||
if (_isSettingsOpen)
|
||||
{
|
||||
CloseSettingsPage();
|
||||
return;
|
||||
CloseSettingsPage(immediate: true);
|
||||
}
|
||||
|
||||
OpenSettingsPage();
|
||||
if (Application.Current is App app)
|
||||
{
|
||||
app.OpenIndependentSettingsModule("MainWindow");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseSettingsClick(object? sender, RoutedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
internal sealed record SettingsComponentCategorySummary(string Name, string CountText);
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -8,16 +8,16 @@
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.AboutSettingsPage">
|
||||
|
||||
<StackPanel x:Name="AboutSettingsPanel" Spacing="20">
|
||||
<TextBlock x:Name="AboutPanelTitleTextBlock" FontSize="24" FontWeight="SemiBold" Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" Text="About" />
|
||||
<TextBlock x:Name="AboutPanelTitleTextBlock" FontSize="24" FontWeight="SemiBold" Foreground="{DynamicResource TextFillColorPrimaryBrush}" Text="About" />
|
||||
|
||||
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}" CornerRadius="{DynamicResource DesignCornerRadiusMd}" Padding="20">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="LanMountainDesktop" FontSize="20" FontWeight="SemiBold" Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
<TextBlock Text="Modern desktop shell experience." FontSize="13" Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<TextBlock Text="LanMountainDesktop" FontSize="20" FontWeight="SemiBold" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<TextBlock Text="Modern desktop shell experience." FontSize="13" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<Separator Background="{DynamicResource AdaptiveButtonBorderBrush}" Margin="0,8" />
|
||||
<TextBlock x:Name="VersionTextBlock" Text="Version: 1.0.0" FontSize="13" Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
<TextBlock x:Name="VersionTextBlock" Text="Version: 1.0.0" FontSize="13" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<TextBlock x:Name="CodeNameTextBlock" Text="Code Name: Administrate" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AdaptiveAccentBrush}" />
|
||||
<TextBlock x:Name="FontInfoTextBlock" Text="Font: MiSans" FontSize="12" Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<TextBlock x:Name="FontInfoTextBlock" Text="Font: MiSans" FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -49,17 +49,17 @@
|
||||
Text="Current actual backend"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<TextBlock x:Name="CurrentRenderBackendValueTextBlock"
|
||||
Text="Current backend: Software"
|
||||
FontSize="13"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<TextBlock x:Name="CurrentRenderBackendImplementationTextBlock"
|
||||
Text="Runtime implementation is unavailable."
|
||||
FontSize="12"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ComboBox x:Name="AppRenderModeComboBox"
|
||||
@@ -77,3 +77,4 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="960"
|
||||
d:DesignHeight="1400"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.AppearanceSettingsPage">
|
||||
<StackPanel MaxWidth="920"
|
||||
Spacing="16">
|
||||
<TextBlock x:Name="AppearancePageSubtitleTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Personalize wallpaper, desktop grid, and accent colors in one place." />
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="AppearanceWallpaperSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Wallpaper" />
|
||||
<TextBlock x:Name="AppearanceWallpaperSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Use lightweight thumbnails and asset controls instead of heavy live preview." />
|
||||
<ContentControl x:Name="WallpaperContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="AppearanceGridSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Grid" />
|
||||
<TextBlock x:Name="AppearanceGridSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Tune grid density, spacing, and safe edge inset for the desktop canvas." />
|
||||
<ContentControl x:Name="GridContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="AppearanceColorSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Color" />
|
||||
<TextBlock x:Name="AppearanceColorSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Choose theme mode and accent colors with Fluent-consistent swatches." />
|
||||
<ContentControl x:Name="ColorContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
public partial class AppearanceSettingsPage : UserControl
|
||||
{
|
||||
public AppearanceSettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -8,19 +8,27 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.ColorSettingsPage">
|
||||
<StackPanel x:Name="ColorSettingsPanel"
|
||||
MaxWidth="920"
|
||||
Spacing="16">
|
||||
<TextBlock x:Name="ColorPanelTitleTextBlock"
|
||||
IsVisible="False"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Color" />
|
||||
|
||||
<TextBlock x:Name="ColorPanelSubtitleTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Adjust theme mode and accent colors. The desktop shell will reuse these colors consistently." />
|
||||
|
||||
<Border Classes="settings-expander-shell">
|
||||
<ui:SettingsExpander x:Name="ThemeModeSettingsExpander"
|
||||
Header="日夜模式"
|
||||
Description="切换应用的浅色或深色主题。">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
|
||||
<ic:SymbolIconSource Symbol="DarkTheme"
|
||||
IconVariant="Regular" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ToggleSwitch x:Name="NightModeToggleSwitch"
|
||||
@@ -35,14 +43,15 @@
|
||||
Header="主题色"
|
||||
Description="选择应用的主题点缀色。">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
|
||||
<ic:SymbolIconSource Symbol="Color"
|
||||
IconVariant="Regular" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
|
||||
<ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="RecommendedColorsLabelTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="Recommended Colors" />
|
||||
<WrapPanel ItemWidth="72"
|
||||
ItemHeight="56"
|
||||
@@ -119,7 +128,7 @@
|
||||
ColumnSpacing="10">
|
||||
<TextBlock x:Name="SystemMonetColorsLabelTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="System Monet Colors" />
|
||||
<Button x:Name="RefreshMonetColorsButton"
|
||||
Grid.Column="1"
|
||||
@@ -197,8 +206,13 @@
|
||||
</ui:SettingsExpander>
|
||||
</Border>
|
||||
|
||||
<TextBlock x:Name="ThemeColorStatusTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
|
||||
Text="Theme color is ready." />
|
||||
<Border Classes="settings-expander-shell"
|
||||
Padding="16,14">
|
||||
<TextBlock x:Name="ThemeColorStatusTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Theme color is ready." />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="960"
|
||||
d:DesignHeight="1200"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.ComponentsSettingsPage">
|
||||
<StackPanel MaxWidth="920"
|
||||
Spacing="16">
|
||||
<TextBlock x:Name="ComponentsPageSubtitleTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Review available desktop components and configure the status bar area." />
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="ComponentsSummarySectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Component Library" />
|
||||
<TextBlock x:Name="ComponentsSummarySectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Built-in and plugin-contributed components available to the desktop editor." />
|
||||
<TextBlock x:Name="ComponentsSummaryTextBlock"
|
||||
TextWrapping="Wrap"
|
||||
Text="Loading component catalog..." />
|
||||
<StackPanel x:Name="ComponentCategoryItemsPanel"
|
||||
Spacing="4" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="ComponentsStatusBarSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Status Bar" />
|
||||
<TextBlock x:Name="ComponentsStatusBarSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Clock and status-bar component spacing are managed here." />
|
||||
<ContentControl x:Name="StatusBarContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
public partial class ComponentsSettingsPage : UserControl
|
||||
{
|
||||
public ComponentsSettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="960"
|
||||
d:DesignHeight="1400"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.GeneralSettingsPage">
|
||||
<StackPanel MaxWidth="920"
|
||||
Spacing="16">
|
||||
<TextBlock x:Name="GeneralPageSubtitleTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Manage language, launcher, and weather behavior from the independent settings module." />
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="GeneralRegionSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Region" />
|
||||
<TextBlock x:Name="GeneralRegionSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Language and time zone settings affect the entire desktop shell." />
|
||||
<ContentControl x:Name="RegionContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="GeneralLauncherSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Launcher" />
|
||||
<TextBlock x:Name="GeneralLauncherSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Restore hidden entries and adjust how the app launcher behaves." />
|
||||
<ContentControl x:Name="LauncherContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="settings-page-shell">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock x:Name="GeneralWeatherSectionTitleTextBlock"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Text="Weather" />
|
||||
<TextBlock x:Name="GeneralWeatherSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Configure shared weather source, location, and icon style for weather widgets." />
|
||||
<ContentControl x:Name="WeatherContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
public partial class GeneralSettingsPage : UserControl
|
||||
{
|
||||
public GeneralSettingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -9,30 +9,32 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.GridSettingsPage">
|
||||
<Grid x:Name="GridSettingsPanel"
|
||||
ColumnDefinitions="*, *"
|
||||
ColumnDefinitions="280, *"
|
||||
RowDefinitions="Auto, *">
|
||||
<TextBlock x:Name="GridPanelTitleTextBlock"
|
||||
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
FontSize="28"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Margin="0,0,0,24"
|
||||
Text="调整网格布局" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Margin="0,0,0,20"
|
||||
Text="璋冩暣缃戞牸甯冨眬" />
|
||||
|
||||
<!-- Left Column: Grid Preview -->
|
||||
<Border x:Name="GridPreviewHost"
|
||||
Grid.Row="1" Grid.Column="0"
|
||||
Margin="0,0,16,0"
|
||||
Margin="0,0,20,0"
|
||||
Width="256"
|
||||
MaxWidth="256"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Stretch">
|
||||
HorizontalAlignment="Left">
|
||||
<Border x:Name="GridPreviewFrame"
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="28"
|
||||
CornerRadius="22"
|
||||
Background="#FF1A1A1A"
|
||||
Padding="12">
|
||||
Padding="8">
|
||||
<Border x:Name="GridPreviewViewport"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="16"
|
||||
CornerRadius="14"
|
||||
Background="#30111827">
|
||||
<Panel>
|
||||
<Canvas x:Name="GridPreviewLinesCanvas"
|
||||
@@ -63,7 +65,7 @@
|
||||
<Border x:Name="GridPreviewTaskbarFixedActionsHost" Grid.Column="0">
|
||||
<StackPanel x:Name="GridPreviewBackButtonVisual" Orientation="Horizontal" Spacing="3">
|
||||
<fi:SymbolIcon Classes="icon-s" Symbol="Window" />
|
||||
<TextBlock x:Name="GridPreviewBackButtonTextBlock" Text="回到Windows" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="GridPreviewBackButtonTextBlock" Text="鍥炲埌Windows" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel x:Name="GridPreviewTaskbarDynamicActionsHost"
|
||||
@@ -90,7 +92,7 @@
|
||||
|
||||
<!-- Right Column: Settings Content -->
|
||||
<StackPanel Grid.Row="1" Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Margin="20,0,0,0"
|
||||
Spacing="16">
|
||||
|
||||
<Border Classes="settings-expander-shell">
|
||||
@@ -109,7 +111,7 @@
|
||||
Width="80"
|
||||
Minimum="6"
|
||||
Maximum="96"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Value="12" />
|
||||
</Grid>
|
||||
</ui:SettingsExpander.Footer>
|
||||
@@ -144,14 +146,14 @@
|
||||
Width="80"
|
||||
Minimum="0"
|
||||
Maximum="30"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Value="18" />
|
||||
</Grid>
|
||||
</ui:SettingsExpander.Footer>
|
||||
<ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBlock x:Name="GridEdgeInsetComputedPxTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text=">= 0 px" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
@@ -161,12 +163,13 @@
|
||||
<Button x:Name="ApplyGridButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="0,10"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Content="应用" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Content="搴旂敤" />
|
||||
|
||||
<TextBlock x:Name="GridInfoTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="Grid: - cols x - rows (1:1)" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -11,7 +11,7 @@
|
||||
<TextBlock x:Name="LauncherSettingsPanelTitleTextBlock"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="App Launcher" />
|
||||
|
||||
<Border Classes="settings-expander-shell">
|
||||
@@ -25,11 +25,11 @@
|
||||
<ui:SettingsExpander.Footer>
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock x:Name="LauncherHiddenItemsDescriptionTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="Right-click an icon in launcher to hide it. Hidden entries appear here." />
|
||||
<TextBlock x:Name="LauncherHiddenItemsEmptyTextBlock"
|
||||
IsVisible="False"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="No hidden items." />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Footer>
|
||||
@@ -37,3 +37,4 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -12,7 +12,7 @@
|
||||
<TextBlock x:Name="RegionPanelTitleTextBlock"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Region" />
|
||||
|
||||
<Border Classes="settings-expander-shell">
|
||||
@@ -25,7 +25,7 @@
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ComboBox x:Name="LanguageComboBox"
|
||||
Width="220">
|
||||
<ComboBoxItem x:Name="LanguageChineseItem" Tag="zh-CN" Content="中文" />
|
||||
<ComboBoxItem x:Name="LanguageChineseItem" Tag="zh-CN" Content="涓枃" />
|
||||
<ComboBoxItem x:Name="LanguageEnglishItem" Tag="en-US" Content="English" />
|
||||
</ComboBox>
|
||||
</ui:SettingsExpander.Footer>
|
||||
@@ -47,3 +47,4 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -11,7 +11,7 @@
|
||||
<TextBlock x:Name="StatusBarPanelTitleTextBlock"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Status Bar" />
|
||||
|
||||
<Border Classes="settings-expander-shell">
|
||||
@@ -80,7 +80,7 @@
|
||||
<ui:SettingsExpanderItem.Footer>
|
||||
<TextBlock x:Name="StatusBarSpacingComputedPxTextBlock"
|
||||
HorizontalAlignment="Right"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text=">= 0 px" />
|
||||
</ui:SettingsExpanderItem.Footer>
|
||||
</ui:SettingsExpanderItem>
|
||||
@@ -88,3 +88,4 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -12,7 +12,7 @@
|
||||
<TextBlock x:Name="UpdatePanelTitleTextBlock"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Update" />
|
||||
|
||||
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
|
||||
@@ -22,31 +22,31 @@
|
||||
<TextBlock x:Name="UpdateCurrentVersionLabelTextBlock"
|
||||
Text="Current Version"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock x:Name="UpdateCurrentVersionValueTextBlock"
|
||||
Grid.Column="1"
|
||||
Text="-"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
|
||||
<TextBlock x:Name="UpdateLatestVersionLabelTextBlock"
|
||||
Grid.Row="1"
|
||||
Text="Latest Release"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock x:Name="UpdateLatestVersionValueTextBlock"
|
||||
Grid.Row="1" Grid.Column="1"
|
||||
Text="-"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
|
||||
<TextBlock x:Name="UpdatePublishedAtLabelTextBlock"
|
||||
Grid.Row="2"
|
||||
Text="Published At"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock x:Name="UpdatePublishedAtValueTextBlock"
|
||||
Grid.Row="2" Grid.Column="1"
|
||||
Text="-"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<TextBlock x:Name="UpdateChannelLabelTextBlock"
|
||||
Text="Update Channel"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<ListBox x:Name="UpdateChannelChipListBox"
|
||||
Classes="settings-chip-list">
|
||||
<ListBox.ItemsPanel>
|
||||
@@ -110,13 +110,14 @@
|
||||
IsVisible="False" />
|
||||
<TextBlock x:Name="UpdateDownloadProgressTextBlock"
|
||||
Text="Download progress: -"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock x:Name="UpdateStatusTextBlock"
|
||||
Text="Ready to check for updates."
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpander.Footer>
|
||||
</ui:SettingsExpander>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -9,31 +9,33 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.WallpaperSettingsPage">
|
||||
<Grid x:Name="WallpaperSettingsPanel"
|
||||
ColumnDefinitions="*, *"
|
||||
ColumnDefinitions="280, *"
|
||||
RowDefinitions="Auto, *">
|
||||
<TextBlock x:Name="WallpaperPanelTitleTextBlock"
|
||||
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
FontSize="28"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Margin="0,0,0,24"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Margin="0,0,0,20"
|
||||
Text="Personalize Wallpaper" />
|
||||
|
||||
<!-- Left Column: Monitor Preview -->
|
||||
<Border x:Name="WallpaperPreviewHost"
|
||||
Grid.Row="1" Grid.Column="0"
|
||||
Margin="0,0,16,0"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch">
|
||||
Margin="0,0,20,0"
|
||||
Width="256"
|
||||
MaxWidth="256"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left">
|
||||
<!-- Monitor Frame (Bezel) -->
|
||||
<Border x:Name="WallpaperPreviewFrame"
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="28"
|
||||
CornerRadius="22"
|
||||
Background="#FF1A1A1A"
|
||||
Padding="12">
|
||||
Padding="8">
|
||||
<Border x:Name="WallpaperPreviewViewport"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="12"
|
||||
CornerRadius="14"
|
||||
Background="#30111827">
|
||||
<Grid>
|
||||
<Image x:Name="WallpaperPreviewVideoImage"
|
||||
@@ -96,14 +98,14 @@
|
||||
|
||||
<!-- Right Column: Settings Content -->
|
||||
<StackPanel Grid.Row="1" Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Margin="20,0,0,0"
|
||||
Spacing="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Preview status" FontSize="12" Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<TextBlock Text="Preview status" FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock x:Name="WallpaperPathTextBlock"
|
||||
FontSize="14"
|
||||
FontWeight="Medium"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="No file selected" />
|
||||
<TextBlock x:Name="WallpaperStatusTextBlock"
|
||||
@@ -114,7 +116,7 @@
|
||||
|
||||
<Separator Background="{DynamicResource SurfaceStrokeColorDefaultBrush}" Height="1" Margin="0,8" />
|
||||
|
||||
<TextBlock Text="Choose image or video" FontSize="16" FontWeight="SemiBold" Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
<TextBlock Text="Choose image or video" FontSize="16" FontWeight="SemiBold" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
|
||||
<Grid ColumnDefinitions="*, *" ColumnSpacing="12">
|
||||
<Button x:Name="PickWallpaperButton"
|
||||
@@ -149,3 +151,4 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -11,7 +11,7 @@
|
||||
<Style Selector="StackPanel.weather-settings-root TextBlock.section-eyebrow">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.weather-settings-root Border.preview-icon-shell">
|
||||
@@ -42,7 +42,7 @@
|
||||
<TextBlock x:Name="WeatherPanelTitleTextBlock"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Weather" />
|
||||
|
||||
<StackPanel Spacing="8">
|
||||
@@ -70,7 +70,7 @@
|
||||
Text="--" />
|
||||
<TextBlock x:Name="WeatherPreviewUpdatedTextBlock"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="-" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<TextBlock x:Name="WeatherPreviewResultTextBlock"
|
||||
Grid.Row="1"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="Use refresh to verify your weather configuration." />
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -138,7 +138,7 @@
|
||||
FontWeight="SemiBold"
|
||||
Text="City Selection" />
|
||||
<TextBlock x:Name="WeatherLocationSelectionDescriptionTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Select the current city used for weather queries." />
|
||||
</StackPanel>
|
||||
@@ -157,7 +157,7 @@
|
||||
FontSize="12"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
@@ -216,7 +216,7 @@
|
||||
|
||||
<TextBlock x:Name="WeatherSearchStatusTextBlock"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Search by city name and apply one location." />
|
||||
</StackPanel>
|
||||
@@ -266,7 +266,7 @@
|
||||
Watermark="Display name (optional)" />
|
||||
<TextBlock x:Name="WeatherCoordinateStatusTextBlock"
|
||||
FontSize="12"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ui:SettingsExpanderItem>
|
||||
@@ -288,7 +288,7 @@
|
||||
FontWeight="SemiBold"
|
||||
Text="Exclude List" />
|
||||
<TextBlock x:Name="WeatherAlertListDescriptionTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="One exclusion rule per line." />
|
||||
</StackPanel>
|
||||
@@ -321,7 +321,7 @@
|
||||
<Border Classes="settings-note-shell">
|
||||
<TextBlock x:Name="WeatherFooterHintTextBlock"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="Desktop weather widgets will reuse the location and alert exclusion settings configured here." />
|
||||
</Border>
|
||||
|
||||
@@ -352,3 +352,4 @@
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
78
LanMountainDesktop/Views/SettingsWindow.ComponentsSummary.cs
Normal file
78
LanMountainDesktop/Views/SettingsWindow.ComponentsSummary.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
private void UpdateComponentsSettingsSummary()
|
||||
{
|
||||
if (ComponentsSettingsHubPanel is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var definitions = _componentRegistry
|
||||
.GetAll()
|
||||
.OrderBy(definition => definition.Category, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(definition => definition.DisplayName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
var runtime = (Application.Current as App)?.PluginRuntimeService;
|
||||
var pluginComponentIds = runtime?.DesktopComponents
|
||||
.Select(contribution => contribution.Registration.ComponentId)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase) ?? [];
|
||||
|
||||
var pluginCount = definitions.Count(definition => pluginComponentIds.Contains(definition.Id));
|
||||
var builtInCount = definitions.Count - pluginCount;
|
||||
var desktopCount = definitions.Count(definition => definition.AllowDesktopPlacement);
|
||||
var statusBarCount = definitions.Count(definition => definition.AllowStatusBarPlacement);
|
||||
|
||||
ComponentsSettingsHubPanel.ComponentsSummaryTextBlock.Text = Lf(
|
||||
"settings.components.summary_format",
|
||||
"Available components: {0}. Built-in: {1}. Plugin-provided: {2}. Desktop: {3}. Status bar: {4}.",
|
||||
definitions.Count,
|
||||
builtInCount,
|
||||
pluginCount,
|
||||
desktopCount,
|
||||
statusBarCount);
|
||||
|
||||
ComponentsSettingsHubPanel.ComponentCategoryItemsPanel.Children.Clear();
|
||||
foreach (var group in definitions
|
||||
.GroupBy(definition => definition.Category, StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
ComponentsSettingsHubPanel.ComponentCategoryItemsPanel.Children.Add(new Border
|
||||
{
|
||||
Margin = new Thickness(0, 4, 0, 0),
|
||||
Padding = new Thickness(12, 10),
|
||||
Background = GetThemeBrush("LayerFillColorDefaultBrush"),
|
||||
BorderBrush = GetThemeBrush("CardStrokeColorDefaultBrush"),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(12),
|
||||
Child = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
Spacing = 8,
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
Text = group.Key
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Foreground = GetThemeBrush("TextFillColorSecondaryBrush"),
|
||||
Text = Lf("settings.components.category_count_format", "{0} item(s)", group.Count())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,217 +1,261 @@
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using LanMountainDesktop.Views.Components;
|
||||
using LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
// --- WallpaperSettingsPage ---
|
||||
internal TextBlock WallpaperPanelTitleTextBlock => WallpaperSettingsPanel.FindControl<TextBlock>("WallpaperPanelTitleTextBlock")!;
|
||||
internal TextBlock WallpaperPathTextBlock => WallpaperSettingsPanel.FindControl<TextBlock>("WallpaperPathTextBlock")!;
|
||||
internal TextBlock WallpaperStatusTextBlock => WallpaperSettingsPanel.FindControl<TextBlock>("WallpaperStatusTextBlock")!;
|
||||
internal ComboBox WallpaperPlacementComboBox => WallpaperSettingsPanel.FindControl<ComboBox>("WallpaperPlacementComboBox")!;
|
||||
internal Border WallpaperPreviewHost => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewHost")!;
|
||||
internal Border WallpaperPreviewFrame => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewFrame")!;
|
||||
internal Border WallpaperPreviewViewport => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewViewport")!;
|
||||
internal Image WallpaperPreviewVideoImage => WallpaperSettingsPanel.FindControl<Image>("WallpaperPreviewVideoImage")!;
|
||||
internal Grid WallpaperPreviewGrid => WallpaperSettingsPanel.FindControl<Grid>("WallpaperPreviewGrid")!;
|
||||
internal Border WallpaperPreviewTopStatusBarHost => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewTopStatusBarHost")!;
|
||||
internal StackPanel WallpaperPreviewTopStatusComponentsPanel => WallpaperSettingsPanel.FindControl<StackPanel>("WallpaperPreviewTopStatusComponentsPanel")!;
|
||||
internal LanMountainDesktop.Views.Components.ClockWidget WallpaperPreviewClockWidget => WallpaperSettingsPanel.FindControl<LanMountainDesktop.Views.Components.ClockWidget>("WallpaperPreviewClockWidget")!;
|
||||
internal Border WallpaperPreviewBottomTaskbarContainer => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewBottomTaskbarContainer")!;
|
||||
internal Border WallpaperPreviewTaskbarFixedActionsHost => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewTaskbarFixedActionsHost")!;
|
||||
internal StackPanel WallpaperPreviewBackButtonVisual => WallpaperSettingsPanel.FindControl<StackPanel>("WallpaperPreviewBackButtonVisual")!;
|
||||
internal TextBlock WallpaperPreviewBackButtonTextBlock => WallpaperSettingsPanel.FindControl<TextBlock>("WallpaperPreviewBackButtonTextBlock")!;
|
||||
internal StackPanel WallpaperPreviewTaskbarDynamicActionsHost => WallpaperSettingsPanel.FindControl<StackPanel>("WallpaperPreviewTaskbarDynamicActionsHost")!;
|
||||
internal Border WallpaperPreviewTaskbarSettingsActionHost => WallpaperSettingsPanel.FindControl<Border>("WallpaperPreviewTaskbarSettingsActionHost")!;
|
||||
internal StackPanel WallpaperPreviewComponentLibraryVisual => WallpaperSettingsPanel.FindControl<StackPanel>("WallpaperPreviewComponentLibraryVisual")!;
|
||||
internal TextBlock WallpaperPreviewComponentLibraryTextBlock => WallpaperSettingsPanel.FindControl<TextBlock>("WallpaperPreviewComponentLibraryTextBlock")!;
|
||||
internal FluentIcons.Avalonia.SymbolIcon WallpaperPreviewSettingsButtonIcon => WallpaperSettingsPanel.FindControl<FluentIcons.Avalonia.SymbolIcon>("WallpaperPreviewSettingsButtonIcon")!;
|
||||
internal Button PickWallpaperButton => WallpaperSettingsPanel.FindControl<Button>("PickWallpaperButton")!;
|
||||
internal Button ClearWallpaperButton => WallpaperSettingsPanel.FindControl<Button>("ClearWallpaperButton")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WallpaperPlacementSettingsExpander => WallpaperSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WallpaperPlacementSettingsExpander")!;
|
||||
private string CurrentControlAccessStage => _controlsBound ? "module init" : "control binding";
|
||||
|
||||
// --- GridSettingsPage ---
|
||||
internal TextBlock GridPanelTitleTextBlock => GridSettingsPanel.FindControl<TextBlock>("GridPanelTitleTextBlock")!;
|
||||
internal Border GridPreviewHost => GridSettingsPanel.FindControl<Border>("GridPreviewHost")!;
|
||||
internal Border GridPreviewFrame => GridSettingsPanel.FindControl<Border>("GridPreviewFrame")!;
|
||||
internal Border GridPreviewViewport => GridSettingsPanel.FindControl<Border>("GridPreviewViewport")!;
|
||||
internal Canvas GridPreviewLinesCanvas => GridSettingsPanel.FindControl<Canvas>("GridPreviewLinesCanvas")!;
|
||||
internal Grid GridPreviewGrid => GridSettingsPanel.FindControl<Grid>("GridPreviewGrid")!;
|
||||
internal Border GridPreviewTopStatusBarHost => GridSettingsPanel.FindControl<Border>("GridPreviewTopStatusBarHost")!;
|
||||
internal StackPanel GridPreviewTopStatusComponentsPanel => GridSettingsPanel.FindControl<StackPanel>("GridPreviewTopStatusComponentsPanel")!;
|
||||
internal Border GridPreviewBottomTaskbarContainer => GridSettingsPanel.FindControl<Border>("GridPreviewBottomTaskbarContainer")!;
|
||||
internal Border GridPreviewTaskbarFixedActionsHost => GridSettingsPanel.FindControl<Border>("GridPreviewTaskbarFixedActionsHost")!;
|
||||
internal StackPanel GridPreviewBackButtonVisual => GridSettingsPanel.FindControl<StackPanel>("GridPreviewBackButtonVisual")!;
|
||||
internal TextBlock GridPreviewBackButtonTextBlock => GridSettingsPanel.FindControl<TextBlock>("GridPreviewBackButtonTextBlock")!;
|
||||
internal StackPanel GridPreviewTaskbarDynamicActionsHost => GridSettingsPanel.FindControl<StackPanel>("GridPreviewTaskbarDynamicActionsHost")!;
|
||||
internal Border GridPreviewTaskbarSettingsActionHost => GridSettingsPanel.FindControl<Border>("GridPreviewTaskbarSettingsActionHost")!;
|
||||
internal StackPanel GridPreviewComponentLibraryVisual => GridSettingsPanel.FindControl<StackPanel>("GridPreviewComponentLibraryVisual")!;
|
||||
internal FluentIcons.Avalonia.FluentIcon GridPreviewComponentLibraryIcon => GridSettingsPanel.FindControl<FluentIcons.Avalonia.FluentIcon>("GridPreviewComponentLibraryIcon")!;
|
||||
internal TextBlock GridPreviewComponentLibraryTextBlock => GridSettingsPanel.FindControl<TextBlock>("GridPreviewComponentLibraryTextBlock")!;
|
||||
internal FluentIcons.Avalonia.SymbolIcon GridPreviewSettingsButtonIcon => GridSettingsPanel.FindControl<FluentIcons.Avalonia.SymbolIcon>("GridPreviewSettingsButtonIcon")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander GridRowsSettingsExpander => GridSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("GridRowsSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander GridSpacingSettingsExpander => GridSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("GridSpacingSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander GridEdgeInsetSettingsExpander => GridSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("GridEdgeInsetSettingsExpander")!;
|
||||
internal Slider GridSizeSlider => GridSettingsPanel.FindControl<Slider>("GridSizeSlider")!;
|
||||
internal FluentAvalonia.UI.Controls.NumberBox GridSizeNumberBox => GridSettingsPanel.FindControl<FluentAvalonia.UI.Controls.NumberBox>("GridSizeNumberBox")!;
|
||||
internal ComboBox GridSpacingPresetComboBox => GridSettingsPanel.FindControl<ComboBox>("GridSpacingPresetComboBox")!;
|
||||
internal ComboBoxItem GridSpacingRelaxedComboBoxItem => GridSettingsPanel.FindControl<ComboBoxItem>("GridSpacingRelaxedComboBoxItem")!;
|
||||
internal ComboBoxItem GridSpacingCompactComboBoxItem => GridSettingsPanel.FindControl<ComboBoxItem>("GridSpacingCompactComboBoxItem")!;
|
||||
internal Slider GridEdgeInsetSlider => GridSettingsPanel.FindControl<Slider>("GridEdgeInsetSlider")!;
|
||||
internal FluentAvalonia.UI.Controls.NumberBox GridEdgeInsetNumberBox => GridSettingsPanel.FindControl<FluentAvalonia.UI.Controls.NumberBox>("GridEdgeInsetNumberBox")!;
|
||||
internal TextBlock GridEdgeInsetComputedPxTextBlock => GridSettingsPanel.FindControl<TextBlock>("GridEdgeInsetComputedPxTextBlock")!;
|
||||
internal Button ApplyGridButton => GridSettingsPanel.FindControl<Button>("ApplyGridButton")!;
|
||||
internal TextBlock GridInfoTextBlock => GridSettingsPanel.FindControl<TextBlock>("GridInfoTextBlock")!;
|
||||
private T? TryGetOptionalPageControl<T>(Control? pageRoot, string controlName)
|
||||
where T : Control
|
||||
{
|
||||
return pageRoot?.FindControl<T>(controlName);
|
||||
}
|
||||
|
||||
// --- ColorSettingsPage ---
|
||||
internal TextBlock ColorPanelTitleTextBlock => ColorSettingsPanel.FindControl<TextBlock>("ColorPanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander ThemeModeSettingsExpander => ColorSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("ThemeModeSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander ThemeColorSettingsExpander => ColorSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("ThemeColorSettingsExpander")!;
|
||||
internal ToggleSwitch NightModeToggleSwitch => ColorSettingsPanel.FindControl<ToggleSwitch>("NightModeToggleSwitch")!;
|
||||
internal TextBlock ThemeColorStatusTextBlock => ColorSettingsPanel.FindControl<TextBlock>("ThemeColorStatusTextBlock")!;
|
||||
internal TextBlock RecommendedColorsLabelTextBlock => ColorSettingsPanel.FindControl<TextBlock>("RecommendedColorsLabelTextBlock")!;
|
||||
internal TextBlock SystemMonetColorsLabelTextBlock => ColorSettingsPanel.FindControl<TextBlock>("SystemMonetColorsLabelTextBlock")!;
|
||||
internal Button RecommendedColorButton1 => ColorSettingsPanel.FindControl<Button>("RecommendedColorButton1")!;
|
||||
internal Button RecommendedColorButton2 => ColorSettingsPanel.FindControl<Button>("RecommendedColorButton2")!;
|
||||
internal Button RecommendedColorButton3 => ColorSettingsPanel.FindControl<Button>("RecommendedColorButton3")!;
|
||||
internal Button RecommendedColorButton4 => ColorSettingsPanel.FindControl<Button>("RecommendedColorButton4")!;
|
||||
internal Button RecommendedColorButton5 => ColorSettingsPanel.FindControl<Button>("RecommendedColorButton5")!;
|
||||
internal Button RecommendedColorButton6 => ColorSettingsPanel.FindControl<Button>("RecommendedColorButton6")!;
|
||||
internal Border RecommendedColorSwatch1 => ColorSettingsPanel.FindControl<Border>("RecommendedColorSwatch1")!;
|
||||
internal Border RecommendedColorSwatch2 => ColorSettingsPanel.FindControl<Border>("RecommendedColorSwatch2")!;
|
||||
internal Border RecommendedColorSwatch3 => ColorSettingsPanel.FindControl<Border>("RecommendedColorSwatch3")!;
|
||||
internal Border RecommendedColorSwatch4 => ColorSettingsPanel.FindControl<Border>("RecommendedColorSwatch4")!;
|
||||
internal Border RecommendedColorSwatch5 => ColorSettingsPanel.FindControl<Border>("RecommendedColorSwatch5")!;
|
||||
internal Border RecommendedColorSwatch6 => ColorSettingsPanel.FindControl<Border>("RecommendedColorSwatch6")!;
|
||||
internal Button RefreshMonetColorsButton => ColorSettingsPanel.FindControl<Button>("RefreshMonetColorsButton")!;
|
||||
internal Button MonetColorButton1 => ColorSettingsPanel.FindControl<Button>("MonetColorButton1")!;
|
||||
internal Button MonetColorButton2 => ColorSettingsPanel.FindControl<Button>("MonetColorButton2")!;
|
||||
internal Button MonetColorButton3 => ColorSettingsPanel.FindControl<Button>("MonetColorButton3")!;
|
||||
internal Button MonetColorButton4 => ColorSettingsPanel.FindControl<Button>("MonetColorButton4")!;
|
||||
internal Button MonetColorButton5 => ColorSettingsPanel.FindControl<Button>("MonetColorButton5")!;
|
||||
internal Button MonetColorButton6 => ColorSettingsPanel.FindControl<Button>("MonetColorButton6")!;
|
||||
internal Border MonetColorSwatch1 => ColorSettingsPanel.FindControl<Border>("MonetColorSwatch1")!;
|
||||
internal Border MonetColorSwatch2 => ColorSettingsPanel.FindControl<Border>("MonetColorSwatch2")!;
|
||||
internal Border MonetColorSwatch3 => ColorSettingsPanel.FindControl<Border>("MonetColorSwatch3")!;
|
||||
internal Border MonetColorSwatch4 => ColorSettingsPanel.FindControl<Border>("MonetColorSwatch4")!;
|
||||
internal Border MonetColorSwatch5 => ColorSettingsPanel.FindControl<Border>("MonetColorSwatch5")!;
|
||||
internal Border MonetColorSwatch6 => ColorSettingsPanel.FindControl<Border>("MonetColorSwatch6")!;
|
||||
private T RequirePageControl<T>(Control pageRoot, string controlName)
|
||||
where T : Control
|
||||
{
|
||||
var control = pageRoot.FindControl<T>(controlName);
|
||||
if (control is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Independent settings module control resolution failed. Page='{pageRoot.Name ?? pageRoot.GetType().Name}'; Control='{controlName}'; Stage='{CurrentControlAccessStage}'.");
|
||||
}
|
||||
|
||||
// --- StatusBarSettingsPage ---
|
||||
internal TextBlock StatusBarPanelTitleTextBlock => StatusBarSettingsPanel.FindControl<TextBlock>("StatusBarPanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander StatusBarClockSettingsExpander => StatusBarSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("StatusBarClockSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander StatusBarSpacingSettingsExpander => StatusBarSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("StatusBarSpacingSettingsExpander")!;
|
||||
internal ToggleSwitch StatusBarClockToggleSwitch => StatusBarSettingsPanel.FindControl<ToggleSwitch>("StatusBarClockToggleSwitch")!;
|
||||
internal RadioButton ClockFormatHMSSRadio => StatusBarSettingsPanel.FindControl<RadioButton>("ClockFormatHMSSRadio")!;
|
||||
internal RadioButton ClockFormatHMRadio => StatusBarSettingsPanel.FindControl<RadioButton>("ClockFormatHMRadio")!;
|
||||
internal ComboBox StatusBarSpacingModeComboBox => StatusBarSettingsPanel.FindControl<ComboBox>("StatusBarSpacingModeComboBox")!;
|
||||
internal ComboBoxItem StatusBarSpacingModeCompactItem => StatusBarSettingsPanel.FindControl<ComboBoxItem>("StatusBarSpacingModeCompactItem")!;
|
||||
internal ComboBoxItem StatusBarSpacingModeRelaxedItem => StatusBarSettingsPanel.FindControl<ComboBoxItem>("StatusBarSpacingModeRelaxedItem")!;
|
||||
internal ComboBoxItem StatusBarSpacingModeCustomItem => StatusBarSettingsPanel.FindControl<ComboBoxItem>("StatusBarSpacingModeCustomItem")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpanderItem StatusBarSpacingCustomPanel => StatusBarSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpanderItem>("StatusBarSpacingCustomPanel")!;
|
||||
internal Slider StatusBarSpacingSlider => StatusBarSettingsPanel.FindControl<Slider>("StatusBarSpacingSlider")!;
|
||||
internal FluentAvalonia.UI.Controls.NumberBox StatusBarSpacingNumberBox => StatusBarSettingsPanel.FindControl<FluentAvalonia.UI.Controls.NumberBox>("StatusBarSpacingNumberBox")!;
|
||||
internal TextBlock StatusBarSpacingComputedPxTextBlock => StatusBarSettingsPanel.FindControl<TextBlock>("StatusBarSpacingComputedPxTextBlock")!;
|
||||
return control;
|
||||
}
|
||||
|
||||
// --- RegionSettingsPage ---
|
||||
internal TextBlock RegionPanelTitleTextBlock => RegionSettingsPanel.FindControl<TextBlock>("RegionPanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander LanguageSettingsExpander => RegionSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("LanguageSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander TimeZoneSettingsExpander => RegionSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("TimeZoneSettingsExpander")!;
|
||||
internal ComboBox LanguageComboBox => RegionSettingsPanel.FindControl<ComboBox>("LanguageComboBox")!;
|
||||
internal ComboBoxItem LanguageChineseItem => RegionSettingsPanel.FindControl<ComboBoxItem>("LanguageChineseItem")!;
|
||||
internal ComboBoxItem LanguageEnglishItem => RegionSettingsPanel.FindControl<ComboBoxItem>("LanguageEnglishItem")!;
|
||||
internal ComboBox TimeZoneComboBox => RegionSettingsPanel.FindControl<ComboBox>("TimeZoneComboBox")!;
|
||||
private T RequireSettingsPage<T>(T? page, string pageName)
|
||||
where T : Control
|
||||
{
|
||||
return page ?? throw new InvalidOperationException(
|
||||
$"Independent settings module page resolution failed. Page='{pageName}'; Stage='{CurrentControlAccessStage}'.");
|
||||
}
|
||||
|
||||
// --- WeatherSettingsPage ---
|
||||
internal TextBlock WeatherPanelTitleTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherPanelTitleTextBlock")!;
|
||||
internal TextBlock WeatherPreviewSectionTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherPreviewSectionTextBlock")!;
|
||||
internal TextBlock WeatherSettingsSectionTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherSettingsSectionTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherPreviewSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherPreviewSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherLocationSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherLocationSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherCitySearchSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherCitySearchSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherCoordinateSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherCoordinateSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherAlertFilterSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherAlertFilterSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherIconPackSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherIconPackSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherNoTlsSettingsExpander => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherNoTlsSettingsExpander")!;
|
||||
internal Button WeatherPreviewButton => WeatherSettingsPanel.FindControl<Button>("WeatherPreviewButton")!;
|
||||
internal ComboBox WeatherLocationModeComboBox => WeatherSettingsPanel.FindControl<ComboBox>("WeatherLocationModeComboBox")!;
|
||||
internal ComboBoxItem WeatherLocationModeCityItem => WeatherSettingsPanel.FindControl<ComboBoxItem>("WeatherLocationModeCityItem")!;
|
||||
internal ComboBoxItem WeatherLocationModeCoordinatesItem => WeatherSettingsPanel.FindControl<ComboBoxItem>("WeatherLocationModeCoordinatesItem")!;
|
||||
internal ListBoxItem WeatherLocationModeCityChipItem => WeatherSettingsPanel.FindControl<ListBoxItem>("WeatherLocationModeCityChipItem")!;
|
||||
internal ListBoxItem WeatherLocationModeCoordinatesChipItem => WeatherSettingsPanel.FindControl<ListBoxItem>("WeatherLocationModeCoordinatesChipItem")!;
|
||||
internal ListBox WeatherLocationModeChipListBox => WeatherSettingsPanel.FindControl<ListBox>("WeatherLocationModeChipListBox")!;
|
||||
internal ToggleSwitch WeatherAutoRefreshToggleSwitch => WeatherSettingsPanel.FindControl<ToggleSwitch>("WeatherAutoRefreshToggleSwitch")!;
|
||||
internal Button WeatherSearchButton => WeatherSettingsPanel.FindControl<Button>("WeatherSearchButton")!;
|
||||
internal Button WeatherApplyCityButton => WeatherSettingsPanel.FindControl<Button>("WeatherApplyCityButton")!;
|
||||
internal Button WeatherApplyCoordinatesButton => WeatherSettingsPanel.FindControl<Button>("WeatherApplyCoordinatesButton")!;
|
||||
internal TextBox WeatherExcludedAlertsTextBox => WeatherSettingsPanel.FindControl<TextBox>("WeatherExcludedAlertsTextBox")!;
|
||||
internal ComboBox WeatherIconPackComboBox => WeatherSettingsPanel.FindControl<ComboBox>("WeatherIconPackComboBox")!;
|
||||
internal ToggleSwitch WeatherNoTlsToggleSwitch => WeatherSettingsPanel.FindControl<ToggleSwitch>("WeatherNoTlsToggleSwitch")!;
|
||||
internal TextBox WeatherCitySearchTextBox => WeatherSettingsPanel.FindControl<TextBox>("WeatherCitySearchTextBox")!;
|
||||
internal ComboBox WeatherCityResultsComboBox => WeatherSettingsPanel.FindControl<ComboBox>("WeatherCityResultsComboBox")!;
|
||||
internal TextBlock WeatherSearchStatusTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherSearchStatusTextBlock")!;
|
||||
internal TextBox WeatherLocationKeyTextBox => WeatherSettingsPanel.FindControl<TextBox>("WeatherLocationKeyTextBox")!;
|
||||
internal TextBox WeatherLocationNameTextBox => WeatherSettingsPanel.FindControl<TextBox>("WeatherLocationNameTextBox")!;
|
||||
internal FluentAvalonia.UI.Controls.NumberBox WeatherLatitudeNumberBox => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.NumberBox>("WeatherLatitudeNumberBox")!;
|
||||
internal FluentAvalonia.UI.Controls.NumberBox WeatherLongitudeNumberBox => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.NumberBox>("WeatherLongitudeNumberBox")!;
|
||||
internal TextBlock WeatherCoordinateStatusTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherCoordinateStatusTextBlock")!;
|
||||
internal TextBlock WeatherPreviewResultTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherPreviewResultTextBlock")!;
|
||||
internal Image WeatherPreviewIconImage => WeatherSettingsPanel.FindControl<Image>("WeatherPreviewIconImage")!;
|
||||
internal FluentIcons.Avalonia.Fluent.SymbolIcon WeatherPreviewIconSymbol => WeatherSettingsPanel.FindControl<FluentIcons.Avalonia.Fluent.SymbolIcon>("WeatherPreviewIconSymbol")!;
|
||||
internal TextBlock WeatherPreviewTemperatureTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherPreviewTemperatureTextBlock")!;
|
||||
internal TextBlock WeatherPreviewUpdatedTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherPreviewUpdatedTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.ProgressRing WeatherSearchProgressRing => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.ProgressRing>("WeatherSearchProgressRing")!;
|
||||
internal FluentAvalonia.UI.Controls.ProgressRing WeatherPreviewProgressRing => WeatherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.ProgressRing>("WeatherPreviewProgressRing")!;
|
||||
internal ComboBoxItem WeatherIconPackFluentRegularItem => WeatherSettingsPanel.FindControl<ComboBoxItem>("WeatherIconPackFluentRegularItem")!;
|
||||
internal ComboBoxItem WeatherIconPackFluentFilledItem => WeatherSettingsPanel.FindControl<ComboBoxItem>("WeatherIconPackFluentFilledItem")!;
|
||||
internal TextBlock WeatherLocationSelectionTitleTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherLocationSelectionTitleTextBlock")!;
|
||||
internal TextBlock WeatherLocationSelectionDescriptionTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherLocationSelectionDescriptionTextBlock")!;
|
||||
internal TextBlock WeatherLocationValueTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherLocationValueTextBlock")!;
|
||||
internal TextBlock WeatherLocationStatusTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherLocationStatusTextBlock")!;
|
||||
internal TextBlock WeatherAlertListTitleTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherAlertListTitleTextBlock")!;
|
||||
internal TextBlock WeatherAlertListDescriptionTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherAlertListDescriptionTextBlock")!;
|
||||
internal TextBlock WeatherFooterHintTextBlock => WeatherSettingsPanel.FindControl<TextBlock>("WeatherFooterHintTextBlock")!;
|
||||
private WallpaperSettingsPage WallpaperSettingsPageRoot => RequireSettingsPage(WallpaperSettingsPanel, nameof(WallpaperSettingsPanel));
|
||||
private GridSettingsPage GridSettingsPageRoot => RequireSettingsPage(GridSettingsPanel, nameof(GridSettingsPanel));
|
||||
private ColorSettingsPage ColorSettingsPageRoot => RequireSettingsPage(ColorSettingsPanel, nameof(ColorSettingsPanel));
|
||||
private StatusBarSettingsPage StatusBarSettingsPageRoot => RequireSettingsPage(StatusBarSettingsPanel, nameof(StatusBarSettingsPanel));
|
||||
private RegionSettingsPage RegionSettingsPageRoot => RequireSettingsPage(RegionSettingsPanel, nameof(RegionSettingsPanel));
|
||||
private WeatherSettingsPage WeatherSettingsPageRoot => RequireSettingsPage(WeatherSettingsPanel, nameof(WeatherSettingsPanel));
|
||||
private UpdateSettingsPage UpdateSettingsPageRoot => RequireSettingsPage(UpdateSettingsPanel, nameof(UpdateSettingsPanel));
|
||||
private AboutSettingsPage AboutSettingsPageRoot => RequireSettingsPage(AboutSettingsPanel, nameof(AboutSettingsPanel));
|
||||
private LauncherSettingsPage LauncherSettingsPageRoot => RequireSettingsPage(LauncherSettingsPanel, nameof(LauncherSettingsPanel));
|
||||
|
||||
// --- UpdateSettingsPage ---
|
||||
internal TextBlock UpdatePanelTitleTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdatePanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander UpdateOptionsSettingsExpander => UpdateSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("UpdateOptionsSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander UpdateActionsSettingsExpander => UpdateSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("UpdateActionsSettingsExpander")!;
|
||||
internal TextBlock UpdateCurrentVersionLabelTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateCurrentVersionLabelTextBlock")!;
|
||||
internal TextBlock UpdateCurrentVersionValueTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateCurrentVersionValueTextBlock")!;
|
||||
internal TextBlock UpdateLatestVersionLabelTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateLatestVersionLabelTextBlock")!;
|
||||
internal TextBlock UpdateLatestVersionValueTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateLatestVersionValueTextBlock")!;
|
||||
internal TextBlock UpdatePublishedAtLabelTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdatePublishedAtLabelTextBlock")!;
|
||||
internal TextBlock UpdatePublishedAtValueTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdatePublishedAtValueTextBlock")!;
|
||||
internal TextBlock UpdateChannelLabelTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateChannelLabelTextBlock")!;
|
||||
internal ListBoxItem UpdateChannelStableChipItem => UpdateSettingsPanel.FindControl<ListBoxItem>("UpdateChannelStableChipItem")!;
|
||||
internal ListBoxItem UpdateChannelPreviewChipItem => UpdateSettingsPanel.FindControl<ListBoxItem>("UpdateChannelPreviewChipItem")!;
|
||||
internal ToggleSwitch AutoCheckUpdatesToggleSwitch => UpdateSettingsPanel.FindControl<ToggleSwitch>("AutoCheckUpdatesToggleSwitch")! ;
|
||||
internal ListBox UpdateChannelChipListBox => UpdateSettingsPanel.FindControl<ListBox>("UpdateChannelChipListBox")!;
|
||||
internal Button CheckForUpdatesButton => UpdateSettingsPanel.FindControl<Button>("CheckForUpdatesButton")!;
|
||||
internal Button DownloadAndInstallUpdateButton => UpdateSettingsPanel.FindControl<Button>("DownloadAndInstallUpdateButton")!;
|
||||
internal ProgressBar UpdateDownloadProgressBar => UpdateSettingsPanel.FindControl<ProgressBar>("UpdateDownloadProgressBar")!;
|
||||
internal TextBlock UpdateDownloadProgressTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateDownloadProgressTextBlock")!;
|
||||
internal TextBlock UpdateStatusTextBlock => UpdateSettingsPanel.FindControl<TextBlock>("UpdateStatusTextBlock")!;
|
||||
private T WallpaperControl<T>(string name) where T : Control => RequirePageControl<T>(WallpaperSettingsPageRoot, name);
|
||||
private T GridControl<T>(string name) where T : Control => RequirePageControl<T>(GridSettingsPageRoot, name);
|
||||
private T ColorControl<T>(string name) where T : Control => RequirePageControl<T>(ColorSettingsPageRoot, name);
|
||||
private T StatusBarControl<T>(string name) where T : Control => RequirePageControl<T>(StatusBarSettingsPageRoot, name);
|
||||
private T RegionControl<T>(string name) where T : Control => RequirePageControl<T>(RegionSettingsPageRoot, name);
|
||||
private T WeatherControl<T>(string name) where T : Control => RequirePageControl<T>(WeatherSettingsPageRoot, name);
|
||||
private T UpdateControl<T>(string name) where T : Control => RequirePageControl<T>(UpdateSettingsPageRoot, name);
|
||||
private T AboutControl<T>(string name) where T : Control => RequirePageControl<T>(AboutSettingsPageRoot, name);
|
||||
private T LauncherControl<T>(string name) where T : Control => RequirePageControl<T>(LauncherSettingsPageRoot, name);
|
||||
|
||||
// --- AboutSettingsPage ---
|
||||
internal TextBlock AboutPanelTitleTextBlock => AboutSettingsPanel.FindControl<TextBlock>("AboutPanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander AboutStartupSettingsExpander => AboutSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("AboutStartupSettingsExpander")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander AboutRenderModeSettingsExpander => AboutSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("AboutRenderModeSettingsExpander")!;
|
||||
internal ToggleSwitch AutoStartWithWindowsToggleSwitch => AboutSettingsPanel.FindControl<ToggleSwitch>("AutoStartWithWindowsToggleSwitch")!;
|
||||
internal ComboBox AppRenderModeComboBox => AboutSettingsPanel.FindControl<ComboBox>("AppRenderModeComboBox")!;
|
||||
internal TextBlock CurrentRenderBackendLabelTextBlock => AboutSettingsPanel.FindControl<TextBlock>("CurrentRenderBackendLabelTextBlock")!;
|
||||
internal TextBlock CurrentRenderBackendValueTextBlock => AboutSettingsPanel.FindControl<TextBlock>("CurrentRenderBackendValueTextBlock")!;
|
||||
internal TextBlock CurrentRenderBackendImplementationTextBlock => AboutSettingsPanel.FindControl<TextBlock>("CurrentRenderBackendImplementationTextBlock")!;
|
||||
internal TextBlock VersionTextBlock => AboutSettingsPanel.FindControl<TextBlock>("VersionTextBlock")!;
|
||||
internal TextBlock CodeNameTextBlock => AboutSettingsPanel.FindControl<TextBlock>("CodeNameTextBlock")!;
|
||||
internal TextBlock FontInfoTextBlock => AboutSettingsPanel.FindControl<TextBlock>("FontInfoTextBlock")!;
|
||||
internal TextBlock WallpaperPanelTitleTextBlock => WallpaperControl<TextBlock>("WallpaperPanelTitleTextBlock");
|
||||
internal TextBlock WallpaperPathTextBlock => WallpaperControl<TextBlock>("WallpaperPathTextBlock");
|
||||
internal TextBlock WallpaperStatusTextBlock => WallpaperControl<TextBlock>("WallpaperStatusTextBlock");
|
||||
internal ComboBox WallpaperPlacementComboBox => WallpaperControl<ComboBox>("WallpaperPlacementComboBox");
|
||||
internal Border WallpaperPreviewHost => WallpaperControl<Border>("WallpaperPreviewHost");
|
||||
internal Border WallpaperPreviewFrame => WallpaperControl<Border>("WallpaperPreviewFrame");
|
||||
internal Border WallpaperPreviewViewport => WallpaperControl<Border>("WallpaperPreviewViewport");
|
||||
internal Image WallpaperPreviewVideoImage => WallpaperControl<Image>("WallpaperPreviewVideoImage");
|
||||
internal Grid WallpaperPreviewGrid => WallpaperControl<Grid>("WallpaperPreviewGrid");
|
||||
internal Border WallpaperPreviewTopStatusBarHost => WallpaperControl<Border>("WallpaperPreviewTopStatusBarHost");
|
||||
internal StackPanel WallpaperPreviewTopStatusComponentsPanel => WallpaperControl<StackPanel>("WallpaperPreviewTopStatusComponentsPanel");
|
||||
internal ClockWidget WallpaperPreviewClockWidget => WallpaperControl<ClockWidget>("WallpaperPreviewClockWidget");
|
||||
internal Border WallpaperPreviewBottomTaskbarContainer => WallpaperControl<Border>("WallpaperPreviewBottomTaskbarContainer");
|
||||
internal Border WallpaperPreviewTaskbarFixedActionsHost => WallpaperControl<Border>("WallpaperPreviewTaskbarFixedActionsHost");
|
||||
internal StackPanel WallpaperPreviewBackButtonVisual => WallpaperControl<StackPanel>("WallpaperPreviewBackButtonVisual");
|
||||
internal TextBlock WallpaperPreviewBackButtonTextBlock => WallpaperControl<TextBlock>("WallpaperPreviewBackButtonTextBlock");
|
||||
internal StackPanel WallpaperPreviewTaskbarDynamicActionsHost => WallpaperControl<StackPanel>("WallpaperPreviewTaskbarDynamicActionsHost");
|
||||
internal Border WallpaperPreviewTaskbarSettingsActionHost => WallpaperControl<Border>("WallpaperPreviewTaskbarSettingsActionHost");
|
||||
internal StackPanel WallpaperPreviewComponentLibraryVisual => WallpaperControl<StackPanel>("WallpaperPreviewComponentLibraryVisual");
|
||||
internal TextBlock WallpaperPreviewComponentLibraryTextBlock => WallpaperControl<TextBlock>("WallpaperPreviewComponentLibraryTextBlock");
|
||||
internal FluentIcons.Avalonia.SymbolIcon WallpaperPreviewSettingsButtonIcon => WallpaperControl<FluentIcons.Avalonia.SymbolIcon>("WallpaperPreviewSettingsButtonIcon");
|
||||
internal Button PickWallpaperButton => WallpaperControl<Button>("PickWallpaperButton");
|
||||
internal Button ClearWallpaperButton => WallpaperControl<Button>("ClearWallpaperButton");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WallpaperPlacementSettingsExpander => WallpaperControl<FluentAvalonia.UI.Controls.SettingsExpander>("WallpaperPlacementSettingsExpander");
|
||||
private Image? OptionalWallpaperPreviewVideoImage => TryGetOptionalPageControl<Image>(WallpaperSettingsPanel, "WallpaperPreviewVideoImage");
|
||||
private Border? OptionalWallpaperPreviewViewport => TryGetOptionalPageControl<Border>(WallpaperSettingsPanel, "WallpaperPreviewViewport");
|
||||
private bool IsWallpaperSettingsPageVisible => string.Equals(NormalizeSettingsPageTag(_selectedSettingsTabTag), "Appearance", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// --- LauncherSettingsPage ---
|
||||
internal TextBlock LauncherSettingsPanelTitleTextBlock => LauncherSettingsPanel.FindControl<TextBlock>("LauncherSettingsPanelTitleTextBlock")!;
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander LauncherHiddenItemsSettingsExpander => LauncherSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("LauncherHiddenItemsSettingsExpander")!;
|
||||
internal TextBlock LauncherHiddenItemsEmptyTextBlock => LauncherSettingsPanel.FindControl<TextBlock>("LauncherHiddenItemsEmptyTextBlock")!;
|
||||
internal TextBlock LauncherHiddenItemsDescriptionTextBlock => LauncherSettingsPanel.FindControl<TextBlock>("LauncherHiddenItemsDescriptionTextBlock")!;
|
||||
internal TextBlock GridPanelTitleTextBlock => GridControl<TextBlock>("GridPanelTitleTextBlock");
|
||||
internal Border GridPreviewHost => GridControl<Border>("GridPreviewHost");
|
||||
internal Border GridPreviewFrame => GridControl<Border>("GridPreviewFrame");
|
||||
internal Border GridPreviewViewport => GridControl<Border>("GridPreviewViewport");
|
||||
internal Canvas GridPreviewLinesCanvas => GridControl<Canvas>("GridPreviewLinesCanvas");
|
||||
internal Grid GridPreviewGrid => GridControl<Grid>("GridPreviewGrid");
|
||||
internal Border GridPreviewTopStatusBarHost => GridControl<Border>("GridPreviewTopStatusBarHost");
|
||||
internal StackPanel GridPreviewTopStatusComponentsPanel => GridControl<StackPanel>("GridPreviewTopStatusComponentsPanel");
|
||||
internal Border GridPreviewBottomTaskbarContainer => GridControl<Border>("GridPreviewBottomTaskbarContainer");
|
||||
internal Border GridPreviewTaskbarFixedActionsHost => GridControl<Border>("GridPreviewTaskbarFixedActionsHost");
|
||||
internal StackPanel GridPreviewBackButtonVisual => GridControl<StackPanel>("GridPreviewBackButtonVisual");
|
||||
internal TextBlock GridPreviewBackButtonTextBlock => GridControl<TextBlock>("GridPreviewBackButtonTextBlock");
|
||||
internal StackPanel GridPreviewTaskbarDynamicActionsHost => GridControl<StackPanel>("GridPreviewTaskbarDynamicActionsHost");
|
||||
internal Border GridPreviewTaskbarSettingsActionHost => GridControl<Border>("GridPreviewTaskbarSettingsActionHost");
|
||||
internal StackPanel GridPreviewComponentLibraryVisual => GridControl<StackPanel>("GridPreviewComponentLibraryVisual");
|
||||
internal FluentIcons.Avalonia.FluentIcon GridPreviewComponentLibraryIcon => GridControl<FluentIcons.Avalonia.FluentIcon>("GridPreviewComponentLibraryIcon");
|
||||
internal TextBlock GridPreviewComponentLibraryTextBlock => GridControl<TextBlock>("GridPreviewComponentLibraryTextBlock");
|
||||
internal FluentIcons.Avalonia.SymbolIcon GridPreviewSettingsButtonIcon => GridControl<FluentIcons.Avalonia.SymbolIcon>("GridPreviewSettingsButtonIcon");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander GridRowsSettingsExpander => GridControl<FluentAvalonia.UI.Controls.SettingsExpander>("GridRowsSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander GridSpacingSettingsExpander => GridControl<FluentAvalonia.UI.Controls.SettingsExpander>("GridSpacingSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander GridEdgeInsetSettingsExpander => GridControl<FluentAvalonia.UI.Controls.SettingsExpander>("GridEdgeInsetSettingsExpander");
|
||||
internal Slider GridSizeSlider => GridControl<Slider>("GridSizeSlider");
|
||||
internal FluentAvalonia.UI.Controls.NumberBox GridSizeNumberBox => GridControl<FluentAvalonia.UI.Controls.NumberBox>("GridSizeNumberBox");
|
||||
internal ComboBox GridSpacingPresetComboBox => GridControl<ComboBox>("GridSpacingPresetComboBox");
|
||||
internal ComboBoxItem GridSpacingRelaxedComboBoxItem => GridControl<ComboBoxItem>("GridSpacingRelaxedComboBoxItem");
|
||||
internal ComboBoxItem GridSpacingCompactComboBoxItem => GridControl<ComboBoxItem>("GridSpacingCompactComboBoxItem");
|
||||
internal Slider GridEdgeInsetSlider => GridControl<Slider>("GridEdgeInsetSlider");
|
||||
internal FluentAvalonia.UI.Controls.NumberBox GridEdgeInsetNumberBox => GridControl<FluentAvalonia.UI.Controls.NumberBox>("GridEdgeInsetNumberBox");
|
||||
internal TextBlock GridEdgeInsetComputedPxTextBlock => GridControl<TextBlock>("GridEdgeInsetComputedPxTextBlock");
|
||||
internal Button ApplyGridButton => GridControl<Button>("ApplyGridButton");
|
||||
internal TextBlock GridInfoTextBlock => GridControl<TextBlock>("GridInfoTextBlock");
|
||||
|
||||
internal TextBlock ColorPanelTitleTextBlock => ColorControl<TextBlock>("ColorPanelTitleTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander ThemeModeSettingsExpander => ColorControl<FluentAvalonia.UI.Controls.SettingsExpander>("ThemeModeSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander ThemeColorSettingsExpander => ColorControl<FluentAvalonia.UI.Controls.SettingsExpander>("ThemeColorSettingsExpander");
|
||||
internal ToggleSwitch NightModeToggleSwitch => ColorControl<ToggleSwitch>("NightModeToggleSwitch");
|
||||
internal TextBlock ThemeColorStatusTextBlock => ColorControl<TextBlock>("ThemeColorStatusTextBlock");
|
||||
internal TextBlock RecommendedColorsLabelTextBlock => ColorControl<TextBlock>("RecommendedColorsLabelTextBlock");
|
||||
internal TextBlock SystemMonetColorsLabelTextBlock => ColorControl<TextBlock>("SystemMonetColorsLabelTextBlock");
|
||||
internal Button RecommendedColorButton1 => ColorControl<Button>("RecommendedColorButton1");
|
||||
internal Button RecommendedColorButton2 => ColorControl<Button>("RecommendedColorButton2");
|
||||
internal Button RecommendedColorButton3 => ColorControl<Button>("RecommendedColorButton3");
|
||||
internal Button RecommendedColorButton4 => ColorControl<Button>("RecommendedColorButton4");
|
||||
internal Button RecommendedColorButton5 => ColorControl<Button>("RecommendedColorButton5");
|
||||
internal Button RecommendedColorButton6 => ColorControl<Button>("RecommendedColorButton6");
|
||||
internal Border RecommendedColorSwatch1 => ColorControl<Border>("RecommendedColorSwatch1");
|
||||
internal Border RecommendedColorSwatch2 => ColorControl<Border>("RecommendedColorSwatch2");
|
||||
internal Border RecommendedColorSwatch3 => ColorControl<Border>("RecommendedColorSwatch3");
|
||||
internal Border RecommendedColorSwatch4 => ColorControl<Border>("RecommendedColorSwatch4");
|
||||
internal Border RecommendedColorSwatch5 => ColorControl<Border>("RecommendedColorSwatch5");
|
||||
internal Border RecommendedColorSwatch6 => ColorControl<Border>("RecommendedColorSwatch6");
|
||||
internal Button RefreshMonetColorsButton => ColorControl<Button>("RefreshMonetColorsButton");
|
||||
internal Button MonetColorButton1 => ColorControl<Button>("MonetColorButton1");
|
||||
internal Button MonetColorButton2 => ColorControl<Button>("MonetColorButton2");
|
||||
internal Button MonetColorButton3 => ColorControl<Button>("MonetColorButton3");
|
||||
internal Button MonetColorButton4 => ColorControl<Button>("MonetColorButton4");
|
||||
internal Button MonetColorButton5 => ColorControl<Button>("MonetColorButton5");
|
||||
internal Button MonetColorButton6 => ColorControl<Button>("MonetColorButton6");
|
||||
internal Border MonetColorSwatch1 => ColorControl<Border>("MonetColorSwatch1");
|
||||
internal Border MonetColorSwatch2 => ColorControl<Border>("MonetColorSwatch2");
|
||||
internal Border MonetColorSwatch3 => ColorControl<Border>("MonetColorSwatch3");
|
||||
internal Border MonetColorSwatch4 => ColorControl<Border>("MonetColorSwatch4");
|
||||
internal Border MonetColorSwatch5 => ColorControl<Border>("MonetColorSwatch5");
|
||||
internal Border MonetColorSwatch6 => ColorControl<Border>("MonetColorSwatch6");
|
||||
|
||||
internal TextBlock StatusBarPanelTitleTextBlock => StatusBarControl<TextBlock>("StatusBarPanelTitleTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander StatusBarClockSettingsExpander => StatusBarControl<FluentAvalonia.UI.Controls.SettingsExpander>("StatusBarClockSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander StatusBarSpacingSettingsExpander => StatusBarControl<FluentAvalonia.UI.Controls.SettingsExpander>("StatusBarSpacingSettingsExpander");
|
||||
internal ToggleSwitch StatusBarClockToggleSwitch => StatusBarControl<ToggleSwitch>("StatusBarClockToggleSwitch");
|
||||
internal RadioButton ClockFormatHMSSRadio => StatusBarControl<RadioButton>("ClockFormatHMSSRadio");
|
||||
internal RadioButton ClockFormatHMRadio => StatusBarControl<RadioButton>("ClockFormatHMRadio");
|
||||
internal ComboBox StatusBarSpacingModeComboBox => StatusBarControl<ComboBox>("StatusBarSpacingModeComboBox");
|
||||
internal ComboBoxItem StatusBarSpacingModeCompactItem => StatusBarControl<ComboBoxItem>("StatusBarSpacingModeCompactItem");
|
||||
internal ComboBoxItem StatusBarSpacingModeRelaxedItem => StatusBarControl<ComboBoxItem>("StatusBarSpacingModeRelaxedItem");
|
||||
internal ComboBoxItem StatusBarSpacingModeCustomItem => StatusBarControl<ComboBoxItem>("StatusBarSpacingModeCustomItem");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpanderItem StatusBarSpacingCustomPanel => StatusBarControl<FluentAvalonia.UI.Controls.SettingsExpanderItem>("StatusBarSpacingCustomPanel");
|
||||
internal Slider StatusBarSpacingSlider => StatusBarControl<Slider>("StatusBarSpacingSlider");
|
||||
internal FluentAvalonia.UI.Controls.NumberBox StatusBarSpacingNumberBox => StatusBarControl<FluentAvalonia.UI.Controls.NumberBox>("StatusBarSpacingNumberBox");
|
||||
internal TextBlock StatusBarSpacingComputedPxTextBlock => StatusBarControl<TextBlock>("StatusBarSpacingComputedPxTextBlock");
|
||||
|
||||
internal TextBlock RegionPanelTitleTextBlock => RegionControl<TextBlock>("RegionPanelTitleTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander LanguageSettingsExpander => RegionControl<FluentAvalonia.UI.Controls.SettingsExpander>("LanguageSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander TimeZoneSettingsExpander => RegionControl<FluentAvalonia.UI.Controls.SettingsExpander>("TimeZoneSettingsExpander");
|
||||
internal ComboBox LanguageComboBox => RegionControl<ComboBox>("LanguageComboBox");
|
||||
internal ComboBoxItem LanguageChineseItem => RegionControl<ComboBoxItem>("LanguageChineseItem");
|
||||
internal ComboBoxItem LanguageEnglishItem => RegionControl<ComboBoxItem>("LanguageEnglishItem");
|
||||
internal ComboBox TimeZoneComboBox => RegionControl<ComboBox>("TimeZoneComboBox");
|
||||
|
||||
internal TextBlock WeatherPanelTitleTextBlock => WeatherControl<TextBlock>("WeatherPanelTitleTextBlock");
|
||||
internal TextBlock WeatherPreviewSectionTextBlock => WeatherControl<TextBlock>("WeatherPreviewSectionTextBlock");
|
||||
internal TextBlock WeatherSettingsSectionTextBlock => WeatherControl<TextBlock>("WeatherSettingsSectionTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherPreviewSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherPreviewSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherLocationSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherLocationSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherCitySearchSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherCitySearchSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherCoordinateSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherCoordinateSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherAlertFilterSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherAlertFilterSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherIconPackSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherIconPackSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander WeatherNoTlsSettingsExpander => WeatherControl<FluentAvalonia.UI.Controls.SettingsExpander>("WeatherNoTlsSettingsExpander");
|
||||
internal Button WeatherPreviewButton => WeatherControl<Button>("WeatherPreviewButton");
|
||||
internal ComboBox WeatherLocationModeComboBox => WeatherControl<ComboBox>("WeatherLocationModeComboBox");
|
||||
internal ComboBoxItem WeatherLocationModeCityItem => WeatherControl<ComboBoxItem>("WeatherLocationModeCityItem");
|
||||
internal ComboBoxItem WeatherLocationModeCoordinatesItem => WeatherControl<ComboBoxItem>("WeatherLocationModeCoordinatesItem");
|
||||
internal ListBoxItem WeatherLocationModeCityChipItem => WeatherControl<ListBoxItem>("WeatherLocationModeCityChipItem");
|
||||
internal ListBoxItem WeatherLocationModeCoordinatesChipItem => WeatherControl<ListBoxItem>("WeatherLocationModeCoordinatesChipItem");
|
||||
internal ListBox WeatherLocationModeChipListBox => WeatherControl<ListBox>("WeatherLocationModeChipListBox");
|
||||
internal ToggleSwitch WeatherAutoRefreshToggleSwitch => WeatherControl<ToggleSwitch>("WeatherAutoRefreshToggleSwitch");
|
||||
internal Button WeatherSearchButton => WeatherControl<Button>("WeatherSearchButton");
|
||||
internal Button WeatherApplyCityButton => WeatherControl<Button>("WeatherApplyCityButton");
|
||||
internal Button WeatherApplyCoordinatesButton => WeatherControl<Button>("WeatherApplyCoordinatesButton");
|
||||
internal TextBox WeatherExcludedAlertsTextBox => WeatherControl<TextBox>("WeatherExcludedAlertsTextBox");
|
||||
internal ComboBox WeatherIconPackComboBox => WeatherControl<ComboBox>("WeatherIconPackComboBox");
|
||||
internal ToggleSwitch WeatherNoTlsToggleSwitch => WeatherControl<ToggleSwitch>("WeatherNoTlsToggleSwitch");
|
||||
internal TextBox WeatherCitySearchTextBox => WeatherControl<TextBox>("WeatherCitySearchTextBox");
|
||||
internal ComboBox WeatherCityResultsComboBox => WeatherControl<ComboBox>("WeatherCityResultsComboBox");
|
||||
internal TextBlock WeatherSearchStatusTextBlock => WeatherControl<TextBlock>("WeatherSearchStatusTextBlock");
|
||||
internal TextBox WeatherLocationKeyTextBox => WeatherControl<TextBox>("WeatherLocationKeyTextBox");
|
||||
internal TextBox WeatherLocationNameTextBox => WeatherControl<TextBox>("WeatherLocationNameTextBox");
|
||||
internal FluentAvalonia.UI.Controls.NumberBox WeatherLatitudeNumberBox => WeatherControl<FluentAvalonia.UI.Controls.NumberBox>("WeatherLatitudeNumberBox");
|
||||
internal FluentAvalonia.UI.Controls.NumberBox WeatherLongitudeNumberBox => WeatherControl<FluentAvalonia.UI.Controls.NumberBox>("WeatherLongitudeNumberBox");
|
||||
internal TextBlock WeatherCoordinateStatusTextBlock => WeatherControl<TextBlock>("WeatherCoordinateStatusTextBlock");
|
||||
internal TextBlock WeatherPreviewResultTextBlock => WeatherControl<TextBlock>("WeatherPreviewResultTextBlock");
|
||||
internal Image WeatherPreviewIconImage => WeatherControl<Image>("WeatherPreviewIconImage");
|
||||
internal FluentIcons.Avalonia.Fluent.SymbolIcon WeatherPreviewIconSymbol => WeatherControl<FluentIcons.Avalonia.Fluent.SymbolIcon>("WeatherPreviewIconSymbol");
|
||||
internal TextBlock WeatherPreviewTemperatureTextBlock => WeatherControl<TextBlock>("WeatherPreviewTemperatureTextBlock");
|
||||
internal TextBlock WeatherPreviewUpdatedTextBlock => WeatherControl<TextBlock>("WeatherPreviewUpdatedTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.ProgressRing WeatherSearchProgressRing => WeatherControl<FluentAvalonia.UI.Controls.ProgressRing>("WeatherSearchProgressRing");
|
||||
internal FluentAvalonia.UI.Controls.ProgressRing WeatherPreviewProgressRing => WeatherControl<FluentAvalonia.UI.Controls.ProgressRing>("WeatherPreviewProgressRing");
|
||||
internal ComboBoxItem WeatherIconPackFluentRegularItem => WeatherControl<ComboBoxItem>("WeatherIconPackFluentRegularItem");
|
||||
internal ComboBoxItem WeatherIconPackFluentFilledItem => WeatherControl<ComboBoxItem>("WeatherIconPackFluentFilledItem");
|
||||
internal TextBlock WeatherLocationSelectionTitleTextBlock => WeatherControl<TextBlock>("WeatherLocationSelectionTitleTextBlock");
|
||||
internal TextBlock WeatherLocationSelectionDescriptionTextBlock => WeatherControl<TextBlock>("WeatherLocationSelectionDescriptionTextBlock");
|
||||
internal TextBlock WeatherLocationValueTextBlock => WeatherControl<TextBlock>("WeatherLocationValueTextBlock");
|
||||
internal TextBlock WeatherLocationStatusTextBlock => WeatherControl<TextBlock>("WeatherLocationStatusTextBlock");
|
||||
internal TextBlock WeatherAlertListTitleTextBlock => WeatherControl<TextBlock>("WeatherAlertListTitleTextBlock");
|
||||
internal TextBlock WeatherAlertListDescriptionTextBlock => WeatherControl<TextBlock>("WeatherAlertListDescriptionTextBlock");
|
||||
internal TextBlock WeatherFooterHintTextBlock => WeatherControl<TextBlock>("WeatherFooterHintTextBlock");
|
||||
|
||||
internal TextBlock UpdatePanelTitleTextBlock => UpdateControl<TextBlock>("UpdatePanelTitleTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander UpdateOptionsSettingsExpander => UpdateControl<FluentAvalonia.UI.Controls.SettingsExpander>("UpdateOptionsSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander UpdateActionsSettingsExpander => UpdateControl<FluentAvalonia.UI.Controls.SettingsExpander>("UpdateActionsSettingsExpander");
|
||||
internal TextBlock UpdateCurrentVersionLabelTextBlock => UpdateControl<TextBlock>("UpdateCurrentVersionLabelTextBlock");
|
||||
internal TextBlock UpdateCurrentVersionValueTextBlock => UpdateControl<TextBlock>("UpdateCurrentVersionValueTextBlock");
|
||||
internal TextBlock UpdateLatestVersionLabelTextBlock => UpdateControl<TextBlock>("UpdateLatestVersionLabelTextBlock");
|
||||
internal TextBlock UpdateLatestVersionValueTextBlock => UpdateControl<TextBlock>("UpdateLatestVersionValueTextBlock");
|
||||
internal TextBlock UpdatePublishedAtLabelTextBlock => UpdateControl<TextBlock>("UpdatePublishedAtLabelTextBlock");
|
||||
internal TextBlock UpdatePublishedAtValueTextBlock => UpdateControl<TextBlock>("UpdatePublishedAtValueTextBlock");
|
||||
internal TextBlock UpdateChannelLabelTextBlock => UpdateControl<TextBlock>("UpdateChannelLabelTextBlock");
|
||||
internal ListBoxItem UpdateChannelStableChipItem => UpdateControl<ListBoxItem>("UpdateChannelStableChipItem");
|
||||
internal ListBoxItem UpdateChannelPreviewChipItem => UpdateControl<ListBoxItem>("UpdateChannelPreviewChipItem");
|
||||
internal ToggleSwitch AutoCheckUpdatesToggleSwitch => UpdateControl<ToggleSwitch>("AutoCheckUpdatesToggleSwitch");
|
||||
internal ListBox UpdateChannelChipListBox => UpdateControl<ListBox>("UpdateChannelChipListBox");
|
||||
internal Button CheckForUpdatesButton => UpdateControl<Button>("CheckForUpdatesButton");
|
||||
internal Button DownloadAndInstallUpdateButton => UpdateControl<Button>("DownloadAndInstallUpdateButton");
|
||||
internal ProgressBar UpdateDownloadProgressBar => UpdateControl<ProgressBar>("UpdateDownloadProgressBar");
|
||||
internal TextBlock UpdateDownloadProgressTextBlock => UpdateControl<TextBlock>("UpdateDownloadProgressTextBlock");
|
||||
internal TextBlock UpdateStatusTextBlock => UpdateControl<TextBlock>("UpdateStatusTextBlock");
|
||||
|
||||
internal TextBlock AboutPanelTitleTextBlock => AboutControl<TextBlock>("AboutPanelTitleTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander AboutStartupSettingsExpander => AboutControl<FluentAvalonia.UI.Controls.SettingsExpander>("AboutStartupSettingsExpander");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander AboutRenderModeSettingsExpander => AboutControl<FluentAvalonia.UI.Controls.SettingsExpander>("AboutRenderModeSettingsExpander");
|
||||
internal ToggleSwitch AutoStartWithWindowsToggleSwitch => AboutControl<ToggleSwitch>("AutoStartWithWindowsToggleSwitch");
|
||||
internal ComboBox AppRenderModeComboBox => AboutControl<ComboBox>("AppRenderModeComboBox");
|
||||
internal TextBlock CurrentRenderBackendLabelTextBlock => AboutControl<TextBlock>("CurrentRenderBackendLabelTextBlock");
|
||||
internal TextBlock CurrentRenderBackendValueTextBlock => AboutControl<TextBlock>("CurrentRenderBackendValueTextBlock");
|
||||
internal TextBlock CurrentRenderBackendImplementationTextBlock => AboutControl<TextBlock>("CurrentRenderBackendImplementationTextBlock");
|
||||
internal TextBlock VersionTextBlock => AboutControl<TextBlock>("VersionTextBlock");
|
||||
internal TextBlock CodeNameTextBlock => AboutControl<TextBlock>("CodeNameTextBlock");
|
||||
internal TextBlock FontInfoTextBlock => AboutControl<TextBlock>("FontInfoTextBlock");
|
||||
|
||||
internal TextBlock LauncherSettingsPanelTitleTextBlock => LauncherControl<TextBlock>("LauncherSettingsPanelTitleTextBlock");
|
||||
internal FluentAvalonia.UI.Controls.SettingsExpander LauncherHiddenItemsSettingsExpander => LauncherControl<FluentAvalonia.UI.Controls.SettingsExpander>("LauncherHiddenItemsSettingsExpander");
|
||||
internal TextBlock LauncherHiddenItemsEmptyTextBlock => LauncherControl<TextBlock>("LauncherHiddenItemsEmptyTextBlock");
|
||||
internal TextBlock LauncherHiddenItemsDescriptionTextBlock => LauncherControl<TextBlock>("LauncherHiddenItemsDescriptionTextBlock");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,149 +8,332 @@ using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using FluentIcons.Avalonia.Fluent;
|
||||
using FluentIcons.Common;
|
||||
using LanMountainDesktop.ComponentSystem;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Views.Components;
|
||||
using LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
using FluentIconVariant = FluentIcons.Common.IconVariant;
|
||||
using FluentSymbol = FluentIcons.Common.Symbol;
|
||||
using FluentSymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
protected override void OnClosed(EventArgs e)
|
||||
private readonly Dictionary<string, Control> _builtInSettingsPageHosts = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
internal void Open(string? pageTag = null)
|
||||
{
|
||||
_persistSettingsDebounceTimer?.Dispose();
|
||||
_persistSettingsDebounceTimer = null;
|
||||
|
||||
StopVideoWallpaper();
|
||||
_previewVideoWallpaperPlayer?.Dispose();
|
||||
_previewVideoWallpaperPlayer = null;
|
||||
_previewVideoWallpaperMedia?.Dispose();
|
||||
_previewVideoWallpaperMedia = null;
|
||||
_previewVideoFrameRefreshTimer?.Stop();
|
||||
_previewVideoFrameRefreshTimer = null;
|
||||
_libVlc?.Dispose();
|
||||
_libVlc = null;
|
||||
|
||||
_releaseUpdateService.Dispose();
|
||||
_wallpaperBitmap?.Dispose();
|
||||
_wallpaperBitmap = null;
|
||||
_launcherFolderIconBitmap?.Dispose();
|
||||
_launcherFolderIconBitmap = null;
|
||||
|
||||
foreach (var icon in _launcherIconCache.Values)
|
||||
if (!string.IsNullOrWhiteSpace(pageTag))
|
||||
{
|
||||
icon.Dispose();
|
||||
_selectedSettingsTabTag = NormalizeSettingsPageTag(pageTag);
|
||||
if (_independentModuleInitializationCompleted)
|
||||
{
|
||||
SelectSettingsTab(_selectedSettingsTabTag, persistSelection: false);
|
||||
}
|
||||
}
|
||||
|
||||
_launcherIconCache.Clear();
|
||||
PendingRestartStateService.StateChanged -= OnPendingRestartStateChanged;
|
||||
base.OnClosed(e);
|
||||
if (!IsVisible)
|
||||
{
|
||||
Show();
|
||||
}
|
||||
|
||||
if (WindowState == WindowState.Minimized)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
Activate();
|
||||
}
|
||||
|
||||
internal void PrepareForForceClose()
|
||||
{
|
||||
_allowIndependentSettingsModuleRealClose = true;
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
AppLogger.Info(
|
||||
"IndependentSettingsModule",
|
||||
$"PreviewCleanupStarted; Stage='WindowCloseCleanup'; Module='WallpaperPreview'; CloseRequested={_isIndependentSettingsModuleClosing}.");
|
||||
|
||||
try
|
||||
{
|
||||
_persistSettingsDebounceTimer?.Dispose();
|
||||
_persistSettingsDebounceTimer = null;
|
||||
|
||||
StopVideoWallpaper();
|
||||
_previewVideoWallpaperPlayer?.Dispose();
|
||||
_previewVideoWallpaperPlayer = null;
|
||||
_previewVideoWallpaperMedia?.Dispose();
|
||||
_previewVideoWallpaperMedia = null;
|
||||
_previewVideoFrameRefreshTimer?.Stop();
|
||||
_previewVideoFrameRefreshTimer = null;
|
||||
_libVlc?.Dispose();
|
||||
_libVlc = null;
|
||||
|
||||
_releaseUpdateService.Dispose();
|
||||
_wallpaperBitmap?.Dispose();
|
||||
_wallpaperBitmap = null;
|
||||
_launcherFolderIconBitmap?.Dispose();
|
||||
_launcherFolderIconBitmap = null;
|
||||
|
||||
foreach (var icon in _launcherIconCache.Values)
|
||||
{
|
||||
icon.Dispose();
|
||||
}
|
||||
|
||||
_launcherIconCache.Clear();
|
||||
AppLogger.Info(
|
||||
"IndependentSettingsModule",
|
||||
$"PreviewCleanupCompleted; Stage='WindowCloseCleanup'; Module='WallpaperPreview'; CloseRequested={_isIndependentSettingsModuleClosing}.");
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
{
|
||||
AppLogger.Warn(
|
||||
"IndependentSettingsModule",
|
||||
$"PreviewCleanupFailed; Stage='WindowCloseCleanup'; Module='WallpaperPreview'; Downgraded=True; CloseRequested={_isIndependentSettingsModuleClosing}.",
|
||||
ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PendingRestartStateService.StateChanged -= OnPendingRestartStateChanged;
|
||||
Closing -= OnIndependentSettingsModuleClosing;
|
||||
base.OnClosed(e);
|
||||
AppLogger.Info("IndependentSettingsModule", $"WindowClosed; CloseRequested={_isIndependentSettingsModuleClosing}.");
|
||||
_isIndependentSettingsModuleClosing = false;
|
||||
_allowIndependentSettingsModuleRealClose = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeSettingsNavigation()
|
||||
{
|
||||
_settingsPageDefinitions.Clear();
|
||||
_settingsNavItems.Clear();
|
||||
_pluginSettingsNavItems.Clear();
|
||||
|
||||
SettingsPrimaryNavHost.Children.Clear();
|
||||
SettingsSecondaryNavHost.Children.Clear();
|
||||
SettingsPluginNavHost.Children.Clear();
|
||||
SettingsPluginNavSection.IsVisible = false;
|
||||
|
||||
AddSettingsNavItem(SettingsPrimaryNavHost, "Wallpaper", Symbol.Wallpaper, "Wallpaper");
|
||||
AddSettingsNavItem(SettingsPrimaryNavHost, "Grid", Symbol.Grid, "Grid");
|
||||
AddSettingsNavItem(SettingsPrimaryNavHost, "Color", Symbol.Color, "Color");
|
||||
AddSettingsNavItem(SettingsPrimaryNavHost, "StatusBar", Symbol.Status, "Status Bar");
|
||||
AddSettingsNavItem(SettingsPrimaryNavHost, "Weather", Symbol.WeatherSunny, "Weather");
|
||||
|
||||
AddSettingsNavItem(SettingsSecondaryNavHost, "Region", Symbol.Globe, "Region");
|
||||
AddSettingsNavItem(SettingsSecondaryNavHost, "Launcher", Symbol.Apps, "App Launcher");
|
||||
AddSettingsNavItem(SettingsSecondaryNavHost, "Update", Symbol.ArrowSync, "Update");
|
||||
AddSettingsNavItem(SettingsSecondaryNavHost, "About", Symbol.Info, "About");
|
||||
AddSettingsNavItem(SettingsSecondaryNavHost, "Plugins", Symbol.PuzzlePiece, "Plugins");
|
||||
AddSettingsNavItem(SettingsSecondaryNavHost, "PluginMarket", Symbol.PuzzlePiece, "Plugin Market");
|
||||
InitializePluginSettingsNavigation();
|
||||
RegisterBuiltInSettingsPageDefinitions();
|
||||
RegisterPluginSettingsDefinitions();
|
||||
RebuildSettingsNavigationMenu();
|
||||
}
|
||||
|
||||
private void OnSettingsNavItemClick(object? sender, RoutedEventArgs e)
|
||||
private void RegisterBuiltInSettingsPageDefinitions()
|
||||
{
|
||||
if (sender is not Button button || button.Tag is not string tag)
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"General",
|
||||
L("settings.nav.general", "General"),
|
||||
L("settings.page_desc.general", "Manage language, launcher, and weather behavior from the independent settings module."),
|
||||
FluentSymbol.Settings,
|
||||
IndependentSettingsPageCategory.Internal,
|
||||
0));
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"Appearance",
|
||||
L("settings.nav.appearance", "Appearance"),
|
||||
L("settings.page_desc.appearance", "Personalize wallpaper, desktop grid, and accent colors in one place."),
|
||||
FluentSymbol.PaintBrush,
|
||||
IndependentSettingsPageCategory.Internal,
|
||||
10));
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"Components",
|
||||
L("settings.nav.components", "Components"),
|
||||
L("settings.page_desc.components", "Review available desktop components and configure the status bar area."),
|
||||
FluentSymbol.Apps,
|
||||
IndependentSettingsPageCategory.Internal,
|
||||
20));
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"Update",
|
||||
L("settings.nav.update", "Update"),
|
||||
L("settings.page_desc.update", "Check for updates and control the update channel."),
|
||||
FluentSymbol.ArrowSync,
|
||||
IndependentSettingsPageCategory.Internal,
|
||||
30));
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"Plugins",
|
||||
L("settings.nav.plugins", "Plugins"),
|
||||
L("settings.page_desc.plugins", "Review installed plugins, runtime state, and local package installation."),
|
||||
FluentSymbol.PuzzlePiece,
|
||||
IndependentSettingsPageCategory.External,
|
||||
100));
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"PluginMarket",
|
||||
L("settings.nav.plugin_market", "Plugin Market"),
|
||||
L("settings.page_desc.pluginmarket", "Browse the official plugin market and stage installs safely."),
|
||||
FluentSymbol.ShoppingBag,
|
||||
IndependentSettingsPageCategory.External,
|
||||
110));
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
"About",
|
||||
L("settings.nav.about", "About"),
|
||||
L("settings.page_desc.about", "See version information, rendering backend, and startup behavior."),
|
||||
FluentSymbol.Info,
|
||||
IndependentSettingsPageCategory.About,
|
||||
200));
|
||||
}
|
||||
|
||||
private void RegisterSettingsPageDefinition(IndependentSettingsPageDefinition definition)
|
||||
{
|
||||
_settingsPageDefinitions[definition.Tag] = definition;
|
||||
}
|
||||
|
||||
private void RebuildSettingsNavigationMenu()
|
||||
{
|
||||
if (SettingsNavView is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectSettingsTab(tag, persistSelection: true);
|
||||
}
|
||||
var selectedTag = NormalizeSettingsPageTag(_selectedSettingsTabTag);
|
||||
SettingsNavView.MenuItems.Clear();
|
||||
_settingsNavItems.Clear();
|
||||
_pluginSettingsNavItems.Clear();
|
||||
|
||||
private Button AddSettingsNavItem(Panel host, string tag, Symbol symbol, string title)
|
||||
{
|
||||
var button = CreateSettingsNavItem(tag, symbol, title);
|
||||
host.Children.Add(button);
|
||||
_settingsNavItems[tag] = button;
|
||||
return button;
|
||||
}
|
||||
|
||||
private Button CreateSettingsNavItem(string tag, Symbol symbol, string title)
|
||||
{
|
||||
var icon = new SymbolIcon
|
||||
IndependentSettingsPageCategory? lastCategory = null;
|
||||
foreach (var definition in _settingsPageDefinitions.Values
|
||||
.OrderBy(definition => GetSettingsPageCategoryOrder(definition.Category))
|
||||
.ThenBy(definition => definition.SortOrder)
|
||||
.ThenBy(definition => definition.Title, StringComparer.CurrentCulture))
|
||||
{
|
||||
Symbol = symbol,
|
||||
IconVariant = IconVariant.Regular
|
||||
};
|
||||
icon.Classes.Add("settings-nav-icon");
|
||||
if (lastCategory is not null && lastCategory != definition.Category)
|
||||
{
|
||||
SettingsNavView.MenuItems.Add(new NavigationViewItemSeparator());
|
||||
}
|
||||
|
||||
var iconShell = new Border
|
||||
{
|
||||
Child = icon,
|
||||
Classes = { "settings-sidebar-icon-shell" }
|
||||
};
|
||||
var navItem = CreateSettingsNavItem(definition);
|
||||
SettingsNavView.MenuItems.Add(navItem);
|
||||
_settingsNavItems[definition.Tag] = navItem;
|
||||
if (definition.Category == IndependentSettingsPageCategory.External)
|
||||
{
|
||||
_pluginSettingsNavItems[definition.Tag] = navItem;
|
||||
}
|
||||
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = title,
|
||||
Classes = { "settings-nav-label" }
|
||||
};
|
||||
|
||||
var contentGrid = new Grid
|
||||
{
|
||||
ColumnDefinitions = new ColumnDefinitions("Auto,*"),
|
||||
ColumnSpacing = 12
|
||||
};
|
||||
contentGrid.Children.Add(iconShell);
|
||||
contentGrid.Children.Add(label);
|
||||
Grid.SetColumn(label, 1);
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Tag = tag,
|
||||
Content = contentGrid,
|
||||
Classes = { "settings-sidebar-item" }
|
||||
};
|
||||
button.Click += OnSettingsNavItemClick;
|
||||
return button;
|
||||
}
|
||||
|
||||
private IEnumerable<Button> EnumerateSettingsNavItems()
|
||||
{
|
||||
foreach (var button in SettingsPrimaryNavHost.Children.OfType<Button>())
|
||||
{
|
||||
yield return button;
|
||||
lastCategory = definition.Category;
|
||||
}
|
||||
|
||||
foreach (var button in SettingsSecondaryNavHost.Children.OfType<Button>())
|
||||
if (_settingsNavItems.TryGetValue(selectedTag, out var selectedItem))
|
||||
{
|
||||
yield return button;
|
||||
SettingsNavView.SelectedItem = selectedItem;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var button in SettingsPluginNavHost.Children.OfType<Button>())
|
||||
if (SettingsNavView.MenuItems.OfType<NavigationViewItem>().FirstOrDefault() is { } firstItem)
|
||||
{
|
||||
yield return button;
|
||||
_selectedSettingsTabTag = firstItem.Tag?.ToString() ?? "General";
|
||||
SettingsNavView.SelectedItem = firstItem;
|
||||
}
|
||||
}
|
||||
|
||||
private Button? GetSettingsNavItem(string tag)
|
||||
private NavigationViewItem CreateSettingsNavItem(IndependentSettingsPageDefinition definition)
|
||||
{
|
||||
var item = new NavigationViewItem
|
||||
{
|
||||
Content = definition.Title,
|
||||
Tag = definition.Tag,
|
||||
IconSource = new FluentSymbolIconSource
|
||||
{
|
||||
Symbol = definition.Icon,
|
||||
IconVariant = FluentIconVariant.Regular
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(definition.ToolTip))
|
||||
{
|
||||
ToolTip.SetTip(item, definition.ToolTip);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static int GetSettingsPageCategoryOrder(IndependentSettingsPageCategory category)
|
||||
{
|
||||
return category switch
|
||||
{
|
||||
IndependentSettingsPageCategory.Internal => 0,
|
||||
IndependentSettingsPageCategory.External => 1,
|
||||
IndependentSettingsPageCategory.About => 2,
|
||||
IndependentSettingsPageCategory.Debug => 3,
|
||||
_ => int.MaxValue
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeSettingsPageHosts()
|
||||
{
|
||||
_builtInSettingsPageHosts.Clear();
|
||||
|
||||
GeneralSettingsHubPanel = new GeneralSettingsPage();
|
||||
AppearanceSettingsHubPanel = new AppearanceSettingsPage();
|
||||
ComponentsSettingsHubPanel = new ComponentsSettingsPage();
|
||||
WallpaperSettingsPanel = new WallpaperSettingsPage();
|
||||
GridSettingsPanel = new GridSettingsPage();
|
||||
ColorSettingsPanel = new ColorSettingsPage();
|
||||
StatusBarSettingsPanel = new StatusBarSettingsPage();
|
||||
WeatherSettingsPanel = new WeatherSettingsPage();
|
||||
RegionSettingsPanel = new RegionSettingsPage();
|
||||
UpdateSettingsPanel = new UpdateSettingsPage();
|
||||
LauncherSettingsPanel = new LauncherSettingsPage();
|
||||
AboutSettingsPanel = new AboutSettingsPage();
|
||||
PluginSettingsPanel = new PluginSettingsPage();
|
||||
PluginMarketSettingsPanel = new PluginMarketSettingsPage();
|
||||
|
||||
GeneralSettingsHubPanel.RegionContentHost.Content = RegionSettingsPanel;
|
||||
GeneralSettingsHubPanel.LauncherContentHost.Content = LauncherSettingsPanel;
|
||||
GeneralSettingsHubPanel.WeatherContentHost.Content = WeatherSettingsPanel;
|
||||
|
||||
AppearanceSettingsHubPanel.WallpaperContentHost.Content = WallpaperSettingsPanel;
|
||||
AppearanceSettingsHubPanel.GridContentHost.Content = GridSettingsPanel;
|
||||
AppearanceSettingsHubPanel.ColorContentHost.Content = ColorSettingsPanel;
|
||||
|
||||
ComponentsSettingsHubPanel.StatusBarContentHost.Content = StatusBarSettingsPanel;
|
||||
|
||||
RegisterBuiltInSettingsPage("General", GeneralSettingsHubPanel);
|
||||
RegisterBuiltInSettingsPage("Appearance", AppearanceSettingsHubPanel);
|
||||
RegisterBuiltInSettingsPage("Components", ComponentsSettingsHubPanel);
|
||||
RegisterBuiltInSettingsPage("Update", UpdateSettingsPanel);
|
||||
RegisterBuiltInSettingsPage("About", AboutSettingsPanel);
|
||||
RegisterBuiltInSettingsPage("Plugins", PluginSettingsPanel);
|
||||
RegisterBuiltInSettingsPage("PluginMarket", PluginMarketSettingsPanel);
|
||||
}
|
||||
|
||||
private void RegisterBuiltInSettingsPage(string tag, Control? page)
|
||||
{
|
||||
if (page is not null)
|
||||
{
|
||||
_builtInSettingsPageHosts[tag] = page;
|
||||
}
|
||||
}
|
||||
|
||||
private Control? ResolveSettingsPageHost(string? tag)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_builtInSettingsPageHosts.TryGetValue(tag, out var builtIn))
|
||||
{
|
||||
return builtIn;
|
||||
}
|
||||
|
||||
return _pluginSettingsPageHosts.GetValueOrDefault(tag);
|
||||
}
|
||||
|
||||
private void OnSettingsNavSelectionChanged(object? sender, NavigationViewSelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.SelectedItem is NavigationViewItem selectedItem &&
|
||||
selectedItem.Tag is not null)
|
||||
{
|
||||
_selectedSettingsTabTag = NormalizeSettingsPageTag(selectedItem.Tag.ToString());
|
||||
}
|
||||
|
||||
AppLogger.Info("IndependentSettingsModule", $"NavigationChanged; Tag='{_selectedSettingsTabTag}'.");
|
||||
UpdateSettingsTabContent();
|
||||
SchedulePersistSettings(0);
|
||||
}
|
||||
|
||||
private NavigationViewItem? GetSettingsNavItem(string tag)
|
||||
{
|
||||
if (_settingsNavItems.TryGetValue(tag, out var builtIn))
|
||||
{
|
||||
@@ -160,51 +343,20 @@ public partial class SettingsWindow
|
||||
return _pluginSettingsNavItems.GetValueOrDefault(tag);
|
||||
}
|
||||
|
||||
private static void SetSettingsNavItemLabel(Button? button, string text)
|
||||
{
|
||||
if (button?.Content is Grid grid)
|
||||
{
|
||||
var label = grid.Children
|
||||
.OfType<TextBlock>()
|
||||
.FirstOrDefault(textBlock => textBlock.Classes.Contains("settings-nav-label"));
|
||||
|
||||
if (label is not null)
|
||||
{
|
||||
label.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectSettingsTab(string? tag, bool persistSelection)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tag))
|
||||
if (string.IsNullOrWhiteSpace(tag) || SettingsNavView is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedButton = GetSettingsNavItem(tag);
|
||||
if (selectedButton is null)
|
||||
if (GetSettingsNavItem(tag) is not { } selectedItem)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedSettingsTabTag = tag;
|
||||
foreach (var button in EnumerateSettingsNavItems())
|
||||
{
|
||||
var isSelected = ReferenceEquals(button, selectedButton);
|
||||
if (isSelected)
|
||||
{
|
||||
if (!button.Classes.Contains("nav-selected"))
|
||||
{
|
||||
button.Classes.Add("nav-selected");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
button.Classes.Remove("nav-selected");
|
||||
}
|
||||
}
|
||||
|
||||
SettingsNavView.SelectedItem = selectedItem;
|
||||
UpdateSettingsTabContent();
|
||||
|
||||
if (persistSelection)
|
||||
@@ -220,22 +372,31 @@ public partial class SettingsWindow
|
||||
|
||||
private void UpdateSettingsTabContent()
|
||||
{
|
||||
if (SettingsNavView is null || SettingsPageFrame is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tag = GetSelectedSettingsTabTag();
|
||||
UpdateCurrentSettingsPageHeader(tag);
|
||||
if (ResolveSettingsPageHost(tag) is { } pageHost)
|
||||
{
|
||||
if (!ReferenceEquals(SettingsPageFrame.Content, pageHost))
|
||||
{
|
||||
SettingsPageFrame.Content = pageHost;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppLogger.Warn("IndependentSettingsModule", $"PageHostMissing; Tag='{tag}'.");
|
||||
ShowIndependentModuleStatus(
|
||||
L("settings.shell.partial_warning_title", "部分内容未能加载"),
|
||||
$"No settings page host is registered for '{tag}'.",
|
||||
InfoBarSeverity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
WallpaperSettingsPanel.IsVisible = tag == "Wallpaper";
|
||||
GridSettingsPanel.IsVisible = tag == "Grid";
|
||||
ColorSettingsPanel.IsVisible = tag == "Color";
|
||||
StatusBarSettingsPanel.IsVisible = tag == "StatusBar";
|
||||
WeatherSettingsPanel.IsVisible = tag == "Weather";
|
||||
RegionSettingsPanel.IsVisible = tag == "Region";
|
||||
UpdateSettingsPanel.IsVisible = tag == "Update";
|
||||
AboutSettingsPanel.IsVisible = tag == "About";
|
||||
LauncherSettingsPanel.IsVisible = tag == "Launcher";
|
||||
PluginSettingsPanel.IsVisible = tag == "Plugins";
|
||||
PluginMarketSettingsPanel.IsVisible = tag == "PluginMarket";
|
||||
UpdatePluginSettingsPageVisibility(tag);
|
||||
|
||||
if (tag == "Launcher")
|
||||
if (tag == "General")
|
||||
{
|
||||
RenderLauncherHiddenItemsList();
|
||||
}
|
||||
@@ -250,11 +411,17 @@ public partial class SettingsWindow
|
||||
PluginMarketSettingsPanel.RefreshFromRuntime();
|
||||
}
|
||||
|
||||
if (tag == "Grid")
|
||||
if (tag == "Appearance")
|
||||
{
|
||||
UpdateWallpaperPreviewLayout();
|
||||
UpdateGridPreviewLayout();
|
||||
}
|
||||
|
||||
if (tag == "Components")
|
||||
{
|
||||
UpdateComponentsSettingsSummary();
|
||||
}
|
||||
|
||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||
SyncVideoWallpaperPreviewPlayback();
|
||||
}
|
||||
@@ -266,6 +433,7 @@ public partial class SettingsWindow
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Info("IndependentSettingsModule", $"PersistCompleted; Tag='{_selectedSettingsTabTag}'.");
|
||||
_appSettingsService.Save(BuildAppSettingsSnapshot());
|
||||
_launcherSettingsService.Save(BuildLauncherSettingsSnapshot());
|
||||
}
|
||||
@@ -281,7 +449,7 @@ public partial class SettingsWindow
|
||||
snapshot.WallpaperPath = _wallpaperPath;
|
||||
snapshot.WallpaperPlacement = GetPlacementDisplayName(GetSelectedWallpaperPlacement());
|
||||
snapshot.SettingsTabIndex = Math.Max(0, GetSettingsTabIndex());
|
||||
snapshot.SettingsTabTag = GetSelectedSettingsTabTag();
|
||||
snapshot.SettingsTabTag = NormalizeSettingsPageTag(_selectedSettingsTabTag);
|
||||
snapshot.LanguageCode = _languageCode;
|
||||
snapshot.TimeZoneId = _timeZoneService.CurrentTimeZone.Id;
|
||||
snapshot.WeatherLocationMode = ToWeatherLocationModeTag(_weatherLocationMode);
|
||||
@@ -326,6 +494,7 @@ public partial class SettingsWindow
|
||||
}
|
||||
|
||||
_persistSettingsDebounceTimer?.Dispose();
|
||||
AppLogger.Info("IndependentSettingsModule", $"PersistScheduled; DelayMs={Math.Max(0, delayMs)}; Tag='{_selectedSettingsTabTag}'.");
|
||||
_persistSettingsDebounceTimer = DispatcherTimer.RunOnce(() =>
|
||||
{
|
||||
_persistSettingsDebounceTimer = null;
|
||||
@@ -347,6 +516,22 @@ public partial class SettingsWindow
|
||||
: "Relaxed";
|
||||
}
|
||||
|
||||
private static string NormalizeSettingsPageTag(string? tag)
|
||||
{
|
||||
return tag switch
|
||||
{
|
||||
null or "" => "General",
|
||||
_ when string.Equals(tag, "Wallpaper", StringComparison.OrdinalIgnoreCase) => "Appearance",
|
||||
_ when string.Equals(tag, "Grid", StringComparison.OrdinalIgnoreCase) => "Appearance",
|
||||
_ when string.Equals(tag, "Color", StringComparison.OrdinalIgnoreCase) => "Appearance",
|
||||
_ when string.Equals(tag, "StatusBar", StringComparison.OrdinalIgnoreCase) => "Components",
|
||||
_ when string.Equals(tag, "Region", StringComparison.OrdinalIgnoreCase) => "General",
|
||||
_ when string.Equals(tag, "Weather", StringComparison.OrdinalIgnoreCase) => "General",
|
||||
_ when string.Equals(tag, "Launcher", StringComparison.OrdinalIgnoreCase) => "General",
|
||||
_ => tag
|
||||
};
|
||||
}
|
||||
|
||||
private static string NormalizeStatusBarSpacingMode(string? value)
|
||||
{
|
||||
return value switch
|
||||
@@ -590,32 +775,32 @@ public partial class SettingsWindow
|
||||
|
||||
private void InitializeSettingsIcons()
|
||||
{
|
||||
const IconVariant variant = IconVariant.Regular;
|
||||
const FluentIconVariant variant = FluentIconVariant.Regular;
|
||||
|
||||
WallpaperPlacementSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Wallpaper, IconVariant = variant };
|
||||
ThemeColorSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Color, IconVariant = variant };
|
||||
StatusBarClockSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Clock, IconVariant = variant };
|
||||
StatusBarSpacingSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.TextLineSpacing, IconVariant = variant };
|
||||
WeatherLocationSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.WeatherSunny, IconVariant = variant };
|
||||
WeatherPreviewSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.WeatherSunny, IconVariant = variant };
|
||||
WeatherAlertFilterSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Info, IconVariant = variant };
|
||||
WeatherIconPackSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Color, IconVariant = variant };
|
||||
WeatherNoTlsSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Globe, IconVariant = variant };
|
||||
LanguageSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Translate, IconVariant = variant };
|
||||
TimeZoneSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.GlobeClock, IconVariant = variant };
|
||||
UpdateOptionsSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.ArrowClockwiseDashesSettings, IconVariant = variant };
|
||||
UpdateActionsSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.ArrowDownload, IconVariant = variant };
|
||||
AboutStartupSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.Play, IconVariant = variant };
|
||||
PluginSystemSettingsExpander.IconSource = new SymbolIconSource { Symbol = Symbol.PuzzlePiece, IconVariant = variant };
|
||||
WallpaperPlacementSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Wallpaper, IconVariant = variant };
|
||||
ThemeColorSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Color, IconVariant = variant };
|
||||
StatusBarClockSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Clock, IconVariant = variant };
|
||||
StatusBarSpacingSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.TextLineSpacing, IconVariant = variant };
|
||||
WeatherLocationSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.WeatherSunny, IconVariant = variant };
|
||||
WeatherPreviewSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.WeatherSunny, IconVariant = variant };
|
||||
WeatherAlertFilterSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Info, IconVariant = variant };
|
||||
WeatherIconPackSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Color, IconVariant = variant };
|
||||
WeatherNoTlsSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Globe, IconVariant = variant };
|
||||
LanguageSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Translate, IconVariant = variant };
|
||||
TimeZoneSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.GlobeClock, IconVariant = variant };
|
||||
UpdateOptionsSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.ArrowClockwiseDashesSettings, IconVariant = variant };
|
||||
UpdateActionsSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.ArrowDownload, IconVariant = variant };
|
||||
AboutStartupSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.Play, IconVariant = variant };
|
||||
PluginSystemSettingsExpander.IconSource = new FluentSymbolIconSource { Symbol = FluentSymbol.PuzzlePiece, IconVariant = variant };
|
||||
UpdateThemeModeIcon();
|
||||
}
|
||||
|
||||
private void UpdateThemeModeIcon()
|
||||
{
|
||||
ThemeModeSettingsExpander.IconSource = new SymbolIconSource
|
||||
ThemeModeSettingsExpander.IconSource = new FluentSymbolIconSource
|
||||
{
|
||||
Symbol = _isNightMode ? Symbol.WeatherMoon : Symbol.WeatherSunny,
|
||||
IconVariant = IconVariant.Regular
|
||||
Symbol = _isNightMode ? FluentSymbol.WeatherMoon : FluentSymbol.WeatherSunny,
|
||||
IconVariant = FluentIconVariant.Regular
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -48,33 +48,50 @@ public partial class SettingsWindow
|
||||
|
||||
private void ApplyLocalization()
|
||||
{
|
||||
Title = L("settings.shell.title", "Application Settings");
|
||||
WindowTitleTextBlock.Text = L("settings.shell.title", "Application Settings");
|
||||
WindowSubtitleTextBlock.Text = L("settings.shell.subtitle", "LanMountainDesktop standalone preferences");
|
||||
Title = L("settings.shell.title", "Settings");
|
||||
WindowTitleTextBlock.Text = L("settings.shell.title", "Settings");
|
||||
WindowSubtitleTextBlock.Text = L("settings.shell.subtitle", "LanMountainDesktop independent settings module");
|
||||
WindowVersionBadgeTextBlock.Text = GetAppVersionText();
|
||||
WindowCodeNameBadgeTextBlock.Text = AppCodeName;
|
||||
SettingsSidebarTitleTextBlock.Text = L("settings.nav_header", "Settings");
|
||||
SettingsSidebarHintTextBlock.Text = L(
|
||||
"settings.shell.sidebar_hint",
|
||||
"Choose a category to adjust application behavior and desktop appearance.");
|
||||
SettingsPrimaryGroupTextBlock.Text = L("settings.nav.group_desktop", "Desktop");
|
||||
SettingsSecondaryGroupTextBlock.Text = L("settings.nav.group_system", "System");
|
||||
SettingsPluginGroupTextBlock.Text = L("settings.nav.group_extensions", "Extensions");
|
||||
"Use stable left navigation and a single right-side page host, following the ClassIsland settings rhythm.");
|
||||
SettingsSidebarFooterTextBlock.Text = L(
|
||||
"settings.shell.footer_hint",
|
||||
"Tray-opened settings are managed in this standalone window.");
|
||||
"Tray-opened settings are managed in this independent settings module.");
|
||||
InitializeSettingsNavigation();
|
||||
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Wallpaper"), L("settings.nav.wallpaper", "Wallpaper"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Grid"), L("settings.nav.grid", "Grid"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Color"), L("settings.nav.color", "Color"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("StatusBar"), L("settings.nav.status_bar", "Status Bar"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Weather"), L("settings.nav.weather", "Weather"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Region"), L("settings.nav.region", "Region"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Update"), L("settings.nav.update", "Update"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("About"), L("settings.nav.about", "About"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Launcher"), L("settings.nav.launcher", "App Launcher"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("Plugins"), L("settings.nav.plugins", "Plugins"));
|
||||
SetSettingsNavItemLabel(GetSettingsNavItem("PluginMarket"), L("settings.nav.plugin_market", "Plugin Market"));
|
||||
if (GeneralSettingsHubPanel is not null)
|
||||
{
|
||||
GeneralSettingsHubPanel.GeneralPageSubtitleTextBlock.Text = L("settings.page_desc.general", "Manage language, launcher, and weather behavior from the independent settings module.");
|
||||
GeneralSettingsHubPanel.GeneralRegionSectionTitleTextBlock.Text = L("settings.nav.region", "Region");
|
||||
GeneralSettingsHubPanel.GeneralRegionSectionHintTextBlock.Text = L("settings.general.region_hint", "Language and time zone settings affect the entire desktop shell.");
|
||||
GeneralSettingsHubPanel.GeneralLauncherSectionTitleTextBlock.Text = L("settings.nav.launcher", "App Launcher");
|
||||
GeneralSettingsHubPanel.GeneralLauncherSectionHintTextBlock.Text = L("settings.general.launcher_hint", "Restore hidden entries and adjust how the app launcher behaves.");
|
||||
GeneralSettingsHubPanel.GeneralWeatherSectionTitleTextBlock.Text = L("settings.nav.weather", "Weather");
|
||||
GeneralSettingsHubPanel.GeneralWeatherSectionHintTextBlock.Text = L("settings.general.weather_hint", "Configure shared weather source, location, and icon style for weather widgets.");
|
||||
}
|
||||
|
||||
if (AppearanceSettingsHubPanel is not null)
|
||||
{
|
||||
AppearanceSettingsHubPanel.AppearancePageSubtitleTextBlock.Text = L("settings.page_desc.appearance", "Personalize wallpaper, desktop grid, and accent colors in one place.");
|
||||
AppearanceSettingsHubPanel.AppearanceWallpaperSectionTitleTextBlock.Text = L("settings.nav.wallpaper", "Wallpaper");
|
||||
AppearanceSettingsHubPanel.AppearanceWallpaperSectionHintTextBlock.Text = L("settings.appearance.wallpaper_hint", "Use lightweight thumbnails and asset controls instead of heavy live preview.");
|
||||
AppearanceSettingsHubPanel.AppearanceGridSectionTitleTextBlock.Text = L("settings.nav.grid", "Grid");
|
||||
AppearanceSettingsHubPanel.AppearanceGridSectionHintTextBlock.Text = L("settings.appearance.grid_hint", "Tune grid density, spacing, and safe edge inset for the desktop canvas.");
|
||||
AppearanceSettingsHubPanel.AppearanceColorSectionTitleTextBlock.Text = L("settings.nav.color", "Color");
|
||||
AppearanceSettingsHubPanel.AppearanceColorSectionHintTextBlock.Text = L("settings.appearance.color_hint", "Choose theme mode and accent colors with Fluent-consistent swatches.");
|
||||
}
|
||||
|
||||
if (ComponentsSettingsHubPanel is not null)
|
||||
{
|
||||
ComponentsSettingsHubPanel.ComponentsPageSubtitleTextBlock.Text = L("settings.page_desc.components", "Review available desktop components and configure the status bar area.");
|
||||
ComponentsSettingsHubPanel.ComponentsSummarySectionTitleTextBlock.Text = L("settings.components.library_title", "Component Library");
|
||||
ComponentsSettingsHubPanel.ComponentsSummarySectionHintTextBlock.Text = L("settings.components.library_hint", "Built-in and plugin-contributed components available to the desktop editor.");
|
||||
ComponentsSettingsHubPanel.ComponentsStatusBarSectionTitleTextBlock.Text = L("settings.nav.status_bar", "Status Bar");
|
||||
ComponentsSettingsHubPanel.ComponentsStatusBarSectionHintTextBlock.Text = L("settings.components.status_bar_hint", "Clock and status-bar component spacing are managed here.");
|
||||
}
|
||||
|
||||
WallpaperPanelTitleTextBlock.Text = L("settings.wallpaper.title", "Personalize your wallpaper");
|
||||
WallpaperPlacementSettingsExpander.Header = L("settings.wallpaper.placement_label", "Placement");
|
||||
@@ -211,6 +228,31 @@ public partial class SettingsWindow
|
||||
ApplyUpdateLocalization();
|
||||
UpdateWallpaperDisplay();
|
||||
RenderLauncherHiddenItemsList();
|
||||
UpdateCurrentSettingsPageHeader(_selectedSettingsTabTag);
|
||||
}
|
||||
|
||||
private void UpdateCurrentSettingsPageHeader(string? tag)
|
||||
{
|
||||
if (CurrentSettingsPageTitleTextBlock is null || CurrentSettingsPageSubtitleTextBlock is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pageTag = string.IsNullOrWhiteSpace(tag) ? "General" : NormalizeSettingsPageTag(tag);
|
||||
CurrentSettingsPageTitleTextBlock.Text = _settingsPageDefinitions.TryGetValue(pageTag, out var definition)
|
||||
? definition.Title
|
||||
: L("settings.shell.title", "Settings");
|
||||
CurrentSettingsPageSubtitleTextBlock.Text = GetSettingsPageDescription(pageTag);
|
||||
}
|
||||
|
||||
private string GetSettingsPageDescription(string tag)
|
||||
{
|
||||
if (_settingsPageDefinitions.TryGetValue(tag, out var definition))
|
||||
{
|
||||
return definition.Description;
|
||||
}
|
||||
|
||||
return L("settings.shell.sidebar_hint", "Use stable left navigation and a single right-side page host, following the ClassIsland settings rhythm.");
|
||||
}
|
||||
|
||||
private void SetAppRenderModeComboItemContent(string tag, string content)
|
||||
|
||||
@@ -22,6 +22,42 @@ namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow
|
||||
{
|
||||
private bool TrySetWallpaperPreviewVideoVisibility(bool isVisible)
|
||||
{
|
||||
var previewImage = OptionalWallpaperPreviewVideoImage;
|
||||
if (previewImage is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
previewImage.IsVisible = isVisible;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TrySetWallpaperPreviewVideoSource(IImage? source)
|
||||
{
|
||||
var previewImage = OptionalWallpaperPreviewVideoImage;
|
||||
if (previewImage is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
previewImage.Source = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TrySetWallpaperPreviewViewportBackground(IBrush background)
|
||||
{
|
||||
var viewport = OptionalWallpaperPreviewViewport;
|
||||
if (viewport is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
viewport.Background = background;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnNightModeChecked(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressThemeToggleEvents)
|
||||
@@ -139,6 +175,12 @@ public partial class SettingsWindow
|
||||
|
||||
private void OnWallpaperPlacementSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_suppressWallpaperPlacementEvents)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedWallpaperPlacement = GetWallpaperPlacementFromIndex(WallpaperPlacementComboBox.SelectedIndex);
|
||||
ApplyWallpaperBrush();
|
||||
if (_wallpaperMediaType == WallpaperMediaType.Image && _wallpaperBitmap is not null)
|
||||
{
|
||||
@@ -157,9 +199,10 @@ public partial class SettingsWindow
|
||||
{
|
||||
if (_wallpaperMediaType == WallpaperMediaType.Video && !string.IsNullOrWhiteSpace(_wallpaperVideoPath))
|
||||
{
|
||||
DesktopWallpaperLayer.Background = Brushes.Transparent;
|
||||
WallpaperPreviewViewport.Background = GetThemeDefaultDesktopBackground();
|
||||
SyncVideoWallpaperPreviewPlayback();
|
||||
DesktopWallpaperLayer.Background = GetThemeDefaultDesktopBackground();
|
||||
TrySetWallpaperPreviewViewportBackground(GetThemeDefaultDesktopBackground());
|
||||
TrySetWallpaperPreviewVideoSource(null);
|
||||
TrySetWallpaperPreviewVideoVisibility(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,43 +211,24 @@ public partial class SettingsWindow
|
||||
{
|
||||
var fallbackBackground = GetThemeDefaultDesktopBackground();
|
||||
DesktopWallpaperLayer.Background = fallbackBackground;
|
||||
WallpaperPreviewViewport.Background = fallbackBackground;
|
||||
TrySetWallpaperPreviewViewportBackground(fallbackBackground);
|
||||
return;
|
||||
}
|
||||
|
||||
var placement = GetSelectedWallpaperPlacement();
|
||||
DesktopWallpaperLayer.Background = CreateWallpaperBrush(_wallpaperBitmap, placement, false);
|
||||
WallpaperPreviewViewport.Background = CreateWallpaperBrush(_wallpaperBitmap, placement, true);
|
||||
TrySetWallpaperPreviewViewportBackground(CreateWallpaperBrush(_wallpaperBitmap, placement, true));
|
||||
}
|
||||
|
||||
private void SyncVideoWallpaperPreviewPlayback()
|
||||
{
|
||||
var shouldPlay =
|
||||
_wallpaperMediaType == WallpaperMediaType.Video &&
|
||||
!string.IsNullOrWhiteSpace(_wallpaperVideoPath) &&
|
||||
WallpaperSettingsPanel.IsVisible;
|
||||
|
||||
if (!shouldPlay)
|
||||
if (_previewVideoWallpaperPlayer?.IsPlaying == true)
|
||||
{
|
||||
if (_previewVideoWallpaperPlayer?.IsPlaying == true)
|
||||
{
|
||||
StopPreviewVideoCapture(clearSnapshot: false);
|
||||
}
|
||||
|
||||
WallpaperPreviewVideoImage.IsVisible = WallpaperPreviewVideoImage.Source is not null && WallpaperSettingsPanel.IsVisible;
|
||||
return;
|
||||
StopPreviewVideoCapture(clearSnapshot: false);
|
||||
}
|
||||
|
||||
if (WallpaperPreviewVideoImage.Source is not null)
|
||||
{
|
||||
WallpaperPreviewVideoImage.IsVisible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_previewVideoWallpaperMedia is null || _previewVideoSnapshotPending)
|
||||
{
|
||||
PlayVideoWallpaper(_wallpaperVideoPath!);
|
||||
}
|
||||
TrySetWallpaperPreviewVideoSource(null);
|
||||
TrySetWallpaperPreviewVideoVisibility(false);
|
||||
}
|
||||
|
||||
private void UpdateWallpaperDisplay()
|
||||
@@ -217,17 +241,17 @@ public partial class SettingsWindow
|
||||
|
||||
if (_wallpaperMediaType == WallpaperMediaType.Video)
|
||||
{
|
||||
WallpaperPreviewViewport.Background = GetThemeDefaultDesktopBackground();
|
||||
TrySetWallpaperPreviewViewportBackground(GetThemeDefaultDesktopBackground());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_wallpaperBitmap is null)
|
||||
{
|
||||
WallpaperPreviewViewport.Background = GetThemeDefaultDesktopBackground();
|
||||
TrySetWallpaperPreviewViewportBackground(GetThemeDefaultDesktopBackground());
|
||||
return;
|
||||
}
|
||||
|
||||
WallpaperPreviewViewport.Background = CreateWallpaperBrush(_wallpaperBitmap, GetSelectedWallpaperPlacement(), true);
|
||||
TrySetWallpaperPreviewViewportBackground(CreateWallpaperBrush(_wallpaperBitmap, GetSelectedWallpaperPlacement(), true));
|
||||
}
|
||||
|
||||
private ImageBrush CreateWallpaperBrush(Bitmap bitmap, WallpaperPlacement placement, bool forPreview)
|
||||
@@ -272,7 +296,12 @@ public partial class SettingsWindow
|
||||
|
||||
private WallpaperPlacement GetSelectedWallpaperPlacement()
|
||||
{
|
||||
return WallpaperPlacementComboBox.SelectedIndex switch
|
||||
return _selectedWallpaperPlacement;
|
||||
}
|
||||
|
||||
private static WallpaperPlacement GetWallpaperPlacementFromIndex(int selectedIndex)
|
||||
{
|
||||
return selectedIndex switch
|
||||
{
|
||||
1 => WallpaperPlacement.Fit,
|
||||
2 => WallpaperPlacement.Stretch,
|
||||
@@ -497,7 +526,7 @@ public partial class SettingsWindow
|
||||
|
||||
private void StopPreviewVideoCapture(bool clearSnapshot)
|
||||
{
|
||||
WallpaperPreviewVideoImage.IsVisible = false;
|
||||
TrySetWallpaperPreviewVideoVisibility(false);
|
||||
_previewVideoWallpaperPlayer?.Stop();
|
||||
StopPreviewVideoFrameRefreshTimer();
|
||||
_previewVideoWallpaperMedia?.Dispose();
|
||||
@@ -517,8 +546,14 @@ public partial class SettingsWindow
|
||||
return false;
|
||||
}
|
||||
|
||||
var hostWidth = Math.Max(1, WallpaperPreviewViewport.Bounds.Width);
|
||||
var hostHeight = Math.Max(1, WallpaperPreviewViewport.Bounds.Height);
|
||||
var previewViewport = OptionalWallpaperPreviewViewport;
|
||||
if (previewViewport is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hostWidth = Math.Max(1, previewViewport.Bounds.Width);
|
||||
var hostHeight = Math.Max(1, previewViewport.Bounds.Height);
|
||||
var pixelWidth = Math.Max(1, (int)Math.Round(hostWidth * RenderScaling));
|
||||
var pixelHeight = Math.Max(1, (int)Math.Round(hostHeight * RenderScaling));
|
||||
const int maxPixelCount = 1280 * 720;
|
||||
@@ -570,7 +605,7 @@ public partial class SettingsWindow
|
||||
(uint)_previewVideoFrameWidth,
|
||||
(uint)_previewVideoFrameHeight,
|
||||
(uint)_previewVideoFramePitch);
|
||||
WallpaperPreviewVideoImage.Source = _previewVideoBitmap;
|
||||
TrySetWallpaperPreviewVideoSource(_previewVideoBitmap);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
@@ -649,24 +684,25 @@ public partial class SettingsWindow
|
||||
Marshal.Copy(_previewVideoStagingBuffer, sourceOffset, destinationPtr, bytesPerRow);
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(WallpaperPreviewVideoImage.Source, _previewVideoBitmap))
|
||||
var previewImage = OptionalWallpaperPreviewVideoImage;
|
||||
if (previewImage is not null && !ReferenceEquals(previewImage.Source, _previewVideoBitmap))
|
||||
{
|
||||
WallpaperPreviewVideoImage.Source = _previewVideoBitmap;
|
||||
previewImage.Source = _previewVideoBitmap;
|
||||
}
|
||||
|
||||
if (_previewVideoSnapshotPending)
|
||||
{
|
||||
_previewVideoSnapshotPending = false;
|
||||
WallpaperPreviewVideoImage.IsVisible = WallpaperSettingsPanel.IsVisible;
|
||||
TrySetWallpaperPreviewVideoVisibility(IsWallpaperSettingsPageVisible);
|
||||
StopPreviewVideoCapture(clearSnapshot: false);
|
||||
WallpaperPreviewVideoImage.IsVisible = WallpaperSettingsPanel.IsVisible;
|
||||
TrySetWallpaperPreviewVideoVisibility(IsWallpaperSettingsPageVisible);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleasePreviewVideoRendererResources()
|
||||
{
|
||||
Interlocked.Exchange(ref _previewVideoFrameDirtyFlag, 0);
|
||||
WallpaperPreviewVideoImage.Source = null;
|
||||
TrySetWallpaperPreviewVideoSource(null);
|
||||
_previewVideoBitmap?.Dispose();
|
||||
_previewVideoBitmap = null;
|
||||
_previewVideoStagingBuffer = null;
|
||||
@@ -715,7 +751,7 @@ public partial class SettingsWindow
|
||||
_previewVideoWallpaperMedia = new Media(_libVlc, new Uri(videoPath));
|
||||
_previewVideoWallpaperMedia.AddOption(":input-repeat=65535");
|
||||
_previewVideoSnapshotPending = true;
|
||||
WallpaperPreviewVideoImage.IsVisible = false;
|
||||
TrySetWallpaperPreviewVideoVisibility(false);
|
||||
_previewVideoWallpaperPlayer.Play(_previewVideoWallpaperMedia);
|
||||
StartPreviewVideoFrameRefreshTimer();
|
||||
}
|
||||
|
||||
@@ -1,312 +1,345 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
<local:IndependentSettingsModuleWindowBase xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:fi="using:FluentIcons.Avalonia"
|
||||
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
|
||||
xmlns:pages="using:LanMountainDesktop.Views.SettingsPages"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:comp="using:LanMountainDesktop.Views.Components"
|
||||
xmlns:local="using:LanMountainDesktop.Views"
|
||||
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
|
||||
x:Class="LanMountainDesktop.Views.SettingsWindow"
|
||||
x:Name="IndependentSettingsModuleWindow"
|
||||
Title="Settings"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
Width="1520"
|
||||
Height="960"
|
||||
MinWidth="1240"
|
||||
MinHeight="820"
|
||||
Width="1240"
|
||||
Height="860"
|
||||
MinWidth="980"
|
||||
MinHeight="680"
|
||||
ShowInTaskbar="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
|
||||
SystemDecorations="None"
|
||||
CanResize="True"
|
||||
UseLayoutRounding="True"
|
||||
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
windowing:AppWindow.AllowInteractionInTitleBar="True"
|
||||
FontFamily="Segoe UI Variable Text, {DynamicResource AppFontFamily}">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Border.settings-shell-card">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassPanelBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassPanelBorderBrush}" />
|
||||
<Style Selector="Border.independent-settings-shell">
|
||||
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="28" />
|
||||
<Setter Property="BoxShadow" Value="0 10 28 #12000000" />
|
||||
<Setter Property="CornerRadius" Value="16" />
|
||||
<Setter Property="BoxShadow" Value="0 12 36 #22000000" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.settings-shell-eyebrow">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<Style Selector="TextBlock.independent-settings-hint">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
<Setter Property="FontSize" Value="12.5" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.settings-shell-hint">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextSecondaryBrush}" />
|
||||
<Style Selector="Border.independent-settings-titlebar">
|
||||
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorSecondaryBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.settings-sidebar-host Button.settings-sidebar-item">
|
||||
<Style Selector="Button.independent-settings-titlebar-button">
|
||||
<Setter Property="Width" Value="46" />
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="18" />
|
||||
<Setter Property="Padding" Value="14,12" />
|
||||
<Setter Property="Margin" Value="0,0,0,8" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<BrushTransition Property="Background" Duration="{StaticResource FluttermotionToken.Duration.Fast}" Easing="0.22,1,0.36,1" />
|
||||
<BrushTransition Property="BorderBrush" Duration="{StaticResource FluttermotionToken.Duration.Fast}" Easing="0.22,1,0.36,1" />
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="{StaticResource FluttermotionToken.Duration.Fast}" Easing="0.22,1,0.36,1" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.settings-sidebar-host Button.settings-sidebar-item:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonHoverBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
|
||||
<Setter Property="RenderTransform" Value="scale(1.01)" />
|
||||
<Style Selector="Button.independent-settings-titlebar-button:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel.settings-sidebar-host Button.settings-sidebar-item.nav-selected">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveNavItemSelectedBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveAccentBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.settings-sidebar-icon-shell">
|
||||
<Setter Property="Width" Value="34" />
|
||||
<Setter Property="Height" Value="34" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.settings-sidebar-item.nav-selected Border.settings-sidebar-icon-shell">
|
||||
<Setter Property="Background" Value="{DynamicResource AdaptiveAccentBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveAccentBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.settings-nav-label">
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ic|SymbolIcon.settings-nav-icon">
|
||||
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.settings-sidebar-item.nav-selected ic|SymbolIcon.settings-nav-icon">
|
||||
<Style Selector="Button.independent-settings-titlebar-close:pointerover">
|
||||
<Setter Property="Background" Value="#CCB91C1C" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.independent-settings-pane-button">
|
||||
<Setter Property="Width" Value="40" />
|
||||
<Setter Property="Height" Value="40" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.independent-settings-pane-button:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ui|NavigationView#SettingsNavView">
|
||||
<Setter Property="PaneDisplayMode" Value="Auto" />
|
||||
<Setter Property="IsBackButtonVisible" Value="False" />
|
||||
<Setter Property="IsPaneToggleButtonVisible" Value="False" />
|
||||
<Setter Property="IsSettingsVisible" Value="False" />
|
||||
<Setter Property="OpenPaneLength" Value="283" />
|
||||
<Setter Property="CompactPaneLength" Value="0" />
|
||||
<Setter Property="AlwaysShowHeader" Value="False" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ui|NavigationViewItem">
|
||||
<Setter Property="Margin" Value="6,2" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ui|NavigationViewItemHeader">
|
||||
<Setter Property="Margin" Value="14,14,0,4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ui|InfoBar#IndependentSettingsStatusInfoBar">
|
||||
<Setter Property="Margin" Value="0,0,0,14" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.settings-page-shell">
|
||||
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseAltBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="16" />
|
||||
<Setter Property="Padding" Value="24" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Grid x:Name="DesktopHost">
|
||||
<Grid x:Name="DesktopHost"
|
||||
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Border x:Name="DesktopWallpaperLayer"
|
||||
Background="{DynamicResource AdaptiveSurfaceBaseBrush}" />
|
||||
IsVisible="False"
|
||||
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
|
||||
|
||||
<Grid x:Name="SettingsPage"
|
||||
Classes="settings-scope"
|
||||
IsVisible="True"
|
||||
Opacity="1"
|
||||
Margin="20">
|
||||
<Grid x:Name="SettingsContentPanel"
|
||||
RowDefinitions="Auto,*"
|
||||
RowSpacing="18">
|
||||
<Border Margin="12"
|
||||
Classes="independent-settings-shell">
|
||||
<Grid RowDefinitions="48,*">
|
||||
<Border Grid.Row="0"
|
||||
Classes="settings-shell-card"
|
||||
Padding="20,18">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
ColumnSpacing="18">
|
||||
<Border Width="52"
|
||||
Height="52"
|
||||
CornerRadius="18"
|
||||
Background="{DynamicResource AdaptiveAccentBrush}">
|
||||
<TextBlock Text="LMD"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Foreground="White"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
Classes="independent-settings-titlebar"
|
||||
PointerPressed="OnTitleBarPointerPressed"
|
||||
DoubleTapped="OnTitleBarDoubleTapped">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto"
|
||||
ColumnSpacing="10"
|
||||
Margin="10,0,8,0">
|
||||
<Button x:Name="SettingsPaneToggleButton"
|
||||
Grid.Column="0"
|
||||
Classes="independent-settings-pane-button"
|
||||
Click="OnSettingsPaneToggleButtonClick"
|
||||
ToolTip.Tip="Toggle navigation">
|
||||
<fi:SymbolIcon Symbol="Navigation" />
|
||||
</Button>
|
||||
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="3"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="WindowTitleTextBlock"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold"
|
||||
Text="Application Settings" />
|
||||
<TextBlock x:Name="WindowSubtitleTextBlock"
|
||||
Classes="settings-shell-hint"
|
||||
Text="LanMountainDesktop" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
VerticalAlignment="Center">
|
||||
<Border Classes="settings-shell-card"
|
||||
Padding="12,8"
|
||||
CornerRadius="18">
|
||||
<Border Width="28"
|
||||
Height="28"
|
||||
CornerRadius="9"
|
||||
Background="{DynamicResource AccentFillColorDefaultBrush}">
|
||||
<TextBlock Text="L"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<StackPanel Spacing="0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="WindowTitleTextBlock"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Text="Settings" />
|
||||
<TextBlock x:Name="WindowSubtitleTextBlock"
|
||||
Classes="independent-settings-hint"
|
||||
Text="LanMountainDesktop independent settings module" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="3"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right">
|
||||
<Border Classes="settings-page-shell"
|
||||
Padding="10,4"
|
||||
CornerRadius="12">
|
||||
<TextBlock x:Name="WindowVersionBadgeTextBlock"
|
||||
FontSize="14"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
FontWeight="SemiBold"
|
||||
Text="1.0.0" />
|
||||
</Border>
|
||||
<Border Classes="settings-shell-card"
|
||||
Padding="12,8"
|
||||
CornerRadius="18">
|
||||
<Border Classes="settings-page-shell"
|
||||
Padding="10,4"
|
||||
CornerRadius="12">
|
||||
<TextBlock x:Name="WindowCodeNameBadgeTextBlock"
|
||||
Classes="settings-shell-hint"
|
||||
Classes="independent-settings-hint"
|
||||
Text="Administrate" />
|
||||
</Border>
|
||||
<Button Classes="independent-settings-pane-button"
|
||||
ToolTip.Tip="More options">
|
||||
<fi:SymbolIcon Symbol="MoreHorizontal" />
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuItem Header="Open Logs Folder"
|
||||
Click="OnOpenLogsFolderClick">
|
||||
<MenuItem.Icon>
|
||||
<fi:SymbolIcon Symbol="FolderOpen" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Open App Folder"
|
||||
Click="OnOpenAppFolderClick">
|
||||
<MenuItem.Icon>
|
||||
<fi:SymbolIcon Symbol="FolderOpen" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="4"
|
||||
Orientation="Horizontal"
|
||||
Spacing="4"
|
||||
VerticalAlignment="Center">
|
||||
<Button Classes="independent-settings-titlebar-button"
|
||||
Click="OnMinimizeWindowClick">
|
||||
<fi:SymbolIcon Symbol="Subtract" />
|
||||
</Button>
|
||||
<Button Classes="independent-settings-titlebar-button"
|
||||
Click="OnToggleWindowStateClick">
|
||||
<fi:SymbolIcon x:Name="WindowStateToggleIcon"
|
||||
Symbol="Square" />
|
||||
</Button>
|
||||
<Button Classes="independent-settings-titlebar-button independent-settings-titlebar-close"
|
||||
Click="OnCloseWindowClick">
|
||||
<fi:SymbolIcon Symbol="Dismiss" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
ColumnDefinitions="300,20,*">
|
||||
<Border Grid.Column="0"
|
||||
Classes="settings-shell-card"
|
||||
Padding="18,18,18,16">
|
||||
<Grid RowDefinitions="Auto,*,Auto"
|
||||
RowSpacing="18">
|
||||
<StackPanel Spacing="6">
|
||||
<Grid Grid.Row="1">
|
||||
<ui:NavigationView x:Name="SettingsNavView"
|
||||
SelectionChanged="OnSettingsNavSelectionChanged">
|
||||
<ui:NavigationView.PaneHeader>
|
||||
<StackPanel Margin="10,10,10,12"
|
||||
Spacing="4">
|
||||
<TextBlock x:Name="SettingsSidebarTitleTextBlock"
|
||||
Classes="settings-shell-eyebrow"
|
||||
FontSize="15"
|
||||
FontWeight="SemiBold"
|
||||
Text="Settings" />
|
||||
<TextBlock x:Name="SettingsSidebarHintTextBlock"
|
||||
Classes="settings-shell-hint"
|
||||
Classes="independent-settings-hint"
|
||||
TextWrapping="Wrap"
|
||||
Text="Choose a category to adjust application behavior and desktop appearance." />
|
||||
Text="Use stable left navigation and a single right-side page host, following the ClassIsland settings rhythm." />
|
||||
</StackPanel>
|
||||
</ui:NavigationView.PaneHeader>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="20">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock x:Name="SettingsPrimaryGroupTextBlock"
|
||||
Classes="settings-shell-eyebrow"
|
||||
Text="Desktop" />
|
||||
<StackPanel x:Name="SettingsPrimaryNavHost"
|
||||
Classes="settings-sidebar-host"
|
||||
Spacing="0" />
|
||||
</StackPanel>
|
||||
|
||||
<Border Background="{DynamicResource SurfaceStrokeColorDefaultBrush}"
|
||||
Height="1" />
|
||||
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock x:Name="SettingsSecondaryGroupTextBlock"
|
||||
Classes="settings-shell-eyebrow"
|
||||
Text="System" />
|
||||
<StackPanel x:Name="SettingsSecondaryNavHost"
|
||||
Classes="settings-sidebar-host"
|
||||
Spacing="0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel x:Name="SettingsPluginNavSection"
|
||||
IsVisible="False"
|
||||
Spacing="10">
|
||||
<Border Background="{DynamicResource SurfaceStrokeColorDefaultBrush}"
|
||||
Height="1" />
|
||||
<TextBlock x:Name="SettingsPluginGroupTextBlock"
|
||||
Classes="settings-shell-eyebrow"
|
||||
Text="Extensions" />
|
||||
<StackPanel x:Name="SettingsPluginNavHost"
|
||||
Classes="settings-sidebar-host"
|
||||
Spacing="0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Border Grid.Row="2"
|
||||
Classes="settings-shell-card"
|
||||
Padding="14,12"
|
||||
CornerRadius="22">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="LanMountainDesktop"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock x:Name="SettingsSidebarFooterTextBlock"
|
||||
Classes="settings-shell-hint"
|
||||
TextWrapping="Wrap"
|
||||
Text="Tray-opened settings are managed in this standalone window." />
|
||||
</StackPanel>
|
||||
<ui:NavigationView.PaneFooter>
|
||||
<Border Margin="10,10,10,12"
|
||||
Classes="settings-page-shell"
|
||||
Padding="12">
|
||||
<TextBlock x:Name="SettingsSidebarFooterTextBlock"
|
||||
Classes="independent-settings-hint"
|
||||
TextWrapping="Wrap"
|
||||
Text="Both the tray entry and the in-app settings entry now open this same independent settings module." />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ui:NavigationView.PaneFooter>
|
||||
|
||||
<Grid Grid.Column="2"
|
||||
RowDefinitions="*,Auto"
|
||||
RowSpacing="14">
|
||||
<Border Grid.Row="0"
|
||||
Classes="settings-shell-card"
|
||||
Padding="0">
|
||||
<ScrollViewer x:Name="SettingsContentScrollViewer"
|
||||
Padding="30,28,30,30"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Grid x:Name="SettingsContentPagesHost">
|
||||
<pages:WallpaperSettingsPage x:Name="WallpaperSettingsPanel" IsVisible="True" />
|
||||
<pages:GridSettingsPage x:Name="GridSettingsPanel" IsVisible="False" />
|
||||
<pages:ColorSettingsPage x:Name="ColorSettingsPanel" IsVisible="False" />
|
||||
<pages:StatusBarSettingsPage x:Name="StatusBarSettingsPanel" IsVisible="False" />
|
||||
<pages:WeatherSettingsPage x:Name="WeatherSettingsPanel" IsVisible="False" />
|
||||
<pages:RegionSettingsPage x:Name="RegionSettingsPanel" IsVisible="False" />
|
||||
<pages:UpdateSettingsPage x:Name="UpdateSettingsPanel" IsVisible="False" />
|
||||
<pages:LauncherSettingsPage x:Name="LauncherSettingsPanel" IsVisible="False" />
|
||||
<pages:AboutSettingsPage x:Name="AboutSettingsPanel" IsVisible="False" />
|
||||
<pages:PluginSettingsPage x:Name="PluginSettingsPanel" IsVisible="False" />
|
||||
<pages:PluginMarketSettingsPage x:Name="PluginMarketSettingsPanel" IsVisible="False" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<ui:InfoBar x:Name="IndependentSettingsStatusInfoBar"
|
||||
Title="Independent settings module status"
|
||||
IsClosable="True"
|
||||
IsOpen="False"
|
||||
Severity="Warning" />
|
||||
|
||||
<Border x:Name="PendingRestartDock"
|
||||
Grid.Row="1"
|
||||
IsVisible="False"
|
||||
Classes="settings-shell-card"
|
||||
Padding="16,14"
|
||||
CornerRadius="24">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
ColumnSpacing="14">
|
||||
<Border Width="38"
|
||||
Height="38"
|
||||
CornerRadius="14"
|
||||
Background="{DynamicResource AdaptiveAccentBrush}">
|
||||
<fi:FluentIcon Icon="ArrowSync"
|
||||
IconVariant="Regular"
|
||||
FontSize="18"
|
||||
Foreground="White"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="2"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="PendingRestartDockTitleTextBlock"
|
||||
FontSize="14"
|
||||
<Grid Grid.Row="1"
|
||||
Margin="18,4,18,18"
|
||||
RowDefinitions="Auto,*,Auto">
|
||||
<StackPanel Spacing="4"
|
||||
Margin="4,0,0,16"
|
||||
MaxWidth="980"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock x:Name="CurrentSettingsPageTitleTextBlock"
|
||||
FontSize="28"
|
||||
FontWeight="SemiBold"
|
||||
Text="Restart required" />
|
||||
<TextBlock x:Name="PendingRestartDockDescriptionTextBlock"
|
||||
Text="General" />
|
||||
<TextBlock x:Name="CurrentSettingsPageSubtitleTextBlock"
|
||||
Classes="independent-settings-hint"
|
||||
MaxWidth="720"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Text="Your changes will apply after restarting the app." />
|
||||
Text="Configure this part of LanMountainDesktop in the independent settings module." />
|
||||
</StackPanel>
|
||||
<Button x:Name="PendingRestartDockButton"
|
||||
Grid.Column="2"
|
||||
Padding="16,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>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Classes="settings-page-shell"
|
||||
MaxWidth="980"
|
||||
HorizontalAlignment="Left">
|
||||
<ScrollViewer x:Name="SettingsContentScrollViewer"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ui:Frame x:Name="SettingsPageFrame"
|
||||
Margin="0" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="PendingRestartDock"
|
||||
Grid.Row="2"
|
||||
IsVisible="False"
|
||||
Margin="0,14,0,0"
|
||||
Classes="settings-page-shell"
|
||||
Padding="14,12"
|
||||
MaxWidth="980"
|
||||
HorizontalAlignment="Left">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto"
|
||||
ColumnSpacing="12">
|
||||
<Border Width="36"
|
||||
Height="36"
|
||||
CornerRadius="18"
|
||||
Background="{DynamicResource AccentFillColorDefaultBrush}">
|
||||
<fi:FluentIcon Icon="ArrowSync"
|
||||
IconVariant="Regular"
|
||||
FontSize="16"
|
||||
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="2"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="PendingRestartDockTitleTextBlock"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Text="Restart required" />
|
||||
<TextBlock x:Name="PendingRestartDockDescriptionTextBlock"
|
||||
Classes="independent-settings-hint"
|
||||
TextWrapping="Wrap"
|
||||
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>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ui:NavigationView>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid IsVisible="False">
|
||||
<Button x:Name="BackToWindowsButton" />
|
||||
@@ -324,4 +357,4 @@
|
||||
<Border x:Name="BottomTaskbarContainer" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
</local:IndependentSettingsModuleWindowBase>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Markup.Xaml;
|
||||
@@ -22,12 +25,13 @@ using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Theme;
|
||||
using LanMountainDesktop.Views.Components;
|
||||
using LanMountainDesktop.Views.SettingsPages;
|
||||
using LibVLCSharp.Shared;
|
||||
using Line = Avalonia.Controls.Shapes.Line;
|
||||
|
||||
namespace LanMountainDesktop.Views;
|
||||
|
||||
public partial class SettingsWindow : Window
|
||||
public partial class SettingsWindow : IndependentSettingsModuleWindowBase
|
||||
{
|
||||
private enum WallpaperPlacement
|
||||
{
|
||||
@@ -102,8 +106,23 @@ public partial class SettingsWindow : Window
|
||||
private readonly HashSet<string> _hiddenLauncherFolderPaths = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly HashSet<string> _hiddenLauncherAppPaths = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Stack<StartMenuFolderNode> _launcherFolderStack = [];
|
||||
private readonly Dictionary<string, Button> _settingsNavItems = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, Button> _pluginSettingsNavItems = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, NavigationViewItem> _settingsNavItems = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, NavigationViewItem> _pluginSettingsNavItems = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, IndependentSettingsPageDefinition> _settingsPageDefinitions = new(StringComparer.OrdinalIgnoreCase);
|
||||
private GeneralSettingsPage? GeneralSettingsHubPanel;
|
||||
private AppearanceSettingsPage? AppearanceSettingsHubPanel;
|
||||
private ComponentsSettingsPage? ComponentsSettingsHubPanel;
|
||||
private WallpaperSettingsPage? WallpaperSettingsPanel;
|
||||
private GridSettingsPage? GridSettingsPanel;
|
||||
private ColorSettingsPage? ColorSettingsPanel;
|
||||
private StatusBarSettingsPage? StatusBarSettingsPanel;
|
||||
private WeatherSettingsPage? WeatherSettingsPanel;
|
||||
private RegionSettingsPage? RegionSettingsPanel;
|
||||
private UpdateSettingsPage? UpdateSettingsPanel;
|
||||
private LauncherSettingsPage? LauncherSettingsPanel;
|
||||
private AboutSettingsPage? AboutSettingsPanel;
|
||||
private PluginSettingsPage? PluginSettingsPanel;
|
||||
private PluginMarketSettingsPage? PluginMarketSettingsPanel;
|
||||
|
||||
private StartMenuFolderNode _startMenuRoot = new("All Apps", string.Empty);
|
||||
private byte[]? _launcherFolderIconPngBytes;
|
||||
@@ -168,20 +187,28 @@ public partial class SettingsWindow : Window
|
||||
private bool _weatherNoTlsRequests;
|
||||
private bool _autoStartWithWindows;
|
||||
private string _weatherSearchKeyword = string.Empty;
|
||||
private string _selectedSettingsTabTag = "Wallpaper";
|
||||
private string _selectedSettingsTabTag = "General";
|
||||
private WallpaperPlacement _selectedWallpaperPlacement = WallpaperPlacement.Fill;
|
||||
private bool _isWeatherSearchInProgress;
|
||||
private bool _isWeatherPreviewInProgress;
|
||||
private bool _controlsBound;
|
||||
private bool _independentModuleInitializationCompleted;
|
||||
private bool _suppressWallpaperPlacementEvents;
|
||||
private bool _isIndependentSettingsModuleClosing;
|
||||
private bool _allowIndependentSettingsModuleRealClose;
|
||||
|
||||
public SettingsWindow()
|
||||
{
|
||||
_componentRegistry = DesktopComponentRegistryFactory.Create((Application.Current as App)?.PluginRuntimeService);
|
||||
InitializeComponent();
|
||||
InitializeSettingsPageHosts();
|
||||
InitializeSettingsNavigation();
|
||||
InitializePluginSettingsNavigation();
|
||||
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
|
||||
RequestedThemeVariant = Application.Current?.RequestedThemeVariant ?? ThemeVariant.Default;
|
||||
PendingRestartStateService.StateChanged += OnPendingRestartStateChanged;
|
||||
HookEvents();
|
||||
Closing += OnIndependentSettingsModuleClosing;
|
||||
Opened += OnWindowOpened;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
@@ -235,7 +262,27 @@ public partial class SettingsWindow : Window
|
||||
DownloadAndInstallUpdateButton.Click += OnDownloadAndInstallUpdateClick;
|
||||
AutoStartWithWindowsToggleSwitch.IsCheckedChanged += OnAutoStartWithWindowsToggled;
|
||||
AppRenderModeComboBox.SelectionChanged += OnAppRenderModeSelectionChanged;
|
||||
Opened += OnWindowOpened;
|
||||
}
|
||||
|
||||
private void EnsureIndependentModuleControlsBound()
|
||||
{
|
||||
if (_controlsBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Info("IndependentSettingsModule", "ControlsBindingStarted.");
|
||||
try
|
||||
{
|
||||
HookEvents();
|
||||
_controlsBound = true;
|
||||
AppLogger.Info("IndependentSettingsModule", "ControlsBindingCompleted.");
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
{
|
||||
AppLogger.Warn("IndependentSettingsModule", "ControlsBindingFailed.", ex);
|
||||
throw new InvalidOperationException("Failed to bind independent settings module controls.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
|
||||
@@ -273,69 +320,287 @@ public partial class SettingsWindow : Window
|
||||
private void OnWindowOpened(object? sender, EventArgs e)
|
||||
{
|
||||
Opened -= OnWindowOpened;
|
||||
_suppressSettingsPersistence = true;
|
||||
var snapshot = _appSettingsService.Load();
|
||||
var launcherSnapshot = _launcherSettingsService.Load();
|
||||
UpdateWindowChromeState();
|
||||
UiExceptionGuard.FireAndForgetGuarded(
|
||||
async () =>
|
||||
{
|
||||
EnsureIndependentModuleControlsBound();
|
||||
await InitializeIndependentSettingsModuleAsync();
|
||||
},
|
||||
"IndependentSettingsModule.Initialize",
|
||||
UiExceptionGuard.BuildContext(("Window", nameof(SettingsWindow))),
|
||||
ex =>
|
||||
{
|
||||
ShowIndependentModuleStatus(
|
||||
L("settings.shell.init_failed_title", "设置模块初始化失败"),
|
||||
ex.Message,
|
||||
InfoBarSeverity.Warning);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
_targetShortSideCells = Math.Clamp(
|
||||
snapshot.GridShortSideCells > 0 ? snapshot.GridShortSideCells : CalculateDefaultShortSideCellCountFromDpi(),
|
||||
MinShortSideCells,
|
||||
MaxShortSideCells);
|
||||
_gridSpacingPreset = NormalizeGridSpacingPreset(snapshot.GridSpacingPreset);
|
||||
_desktopEdgeInsetPercent = Math.Clamp(snapshot.DesktopEdgeInsetPercent, MinEdgeInsetPercent, MaxEdgeInsetPercent);
|
||||
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
|
||||
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
|
||||
GridSizeNumberBox.Value = _targetShortSideCells;
|
||||
GridSizeSlider.Value = _targetShortSideCells;
|
||||
GridSpacingPresetComboBox.SelectedIndex = string.Equals(_gridSpacingPreset, "Compact", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
||||
GridEdgeInsetSlider.Value = _desktopEdgeInsetPercent;
|
||||
GridEdgeInsetNumberBox.Value = _desktopEdgeInsetPercent;
|
||||
StatusBarSpacingModeComboBox.SelectedIndex = _statusBarSpacingMode switch
|
||||
private void OnIndependentSettingsModuleClosing(object? sender, WindowClosingEventArgs e)
|
||||
{
|
||||
AppLogger.Info(
|
||||
"IndependentSettingsModule",
|
||||
$"CloseRequested; AllowRealClose={_allowIndependentSettingsModuleRealClose}; Reason='{e.CloseReason}'.");
|
||||
|
||||
if (!_allowIndependentSettingsModuleRealClose &&
|
||||
e.CloseReason is not WindowCloseReason.ApplicationShutdown &&
|
||||
e.CloseReason is not WindowCloseReason.OSShutdown)
|
||||
{
|
||||
"Compact" => 0,
|
||||
"Custom" => 2,
|
||||
_ => 1
|
||||
};
|
||||
StatusBarSpacingSlider.Value = _statusBarCustomSpacingPercent;
|
||||
StatusBarSpacingNumberBox.Value = _statusBarCustomSpacingPercent;
|
||||
StatusBarSpacingCustomPanel.IsVisible = string.Equals(_statusBarSpacingMode, "Custom", StringComparison.OrdinalIgnoreCase);
|
||||
GridEdgeInsetNumberBox.ValueChanged += OnGridEdgeInsetNumberBoxChanged;
|
||||
StatusBarSpacingNumberBox.ValueChanged += OnStatusBarSpacingNumberBoxChanged;
|
||||
ApplyTaskbarSettings(snapshot);
|
||||
InitializeLocalization(snapshot.LanguageCode);
|
||||
InitializeWeatherSettings(snapshot);
|
||||
InitializeAutoStartWithWindowsSetting(snapshot);
|
||||
InitializeAppRenderModeSetting(snapshot);
|
||||
InitializeUpdateSettings(snapshot);
|
||||
InitializeLauncherVisibilitySettings(launcherSnapshot);
|
||||
InitializeSettingsIcons();
|
||||
ApplyLocalization();
|
||||
WallpaperPlacementComboBox.SelectedIndex = GetPlacementIndexFromSetting(snapshot.WallpaperPlacement);
|
||||
TryRestoreWallpaper(snapshot.WallpaperPath);
|
||||
RefreshColorPalettes();
|
||||
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
|
||||
{
|
||||
_selectedThemeColor = savedThemeColor;
|
||||
e.Cancel = true;
|
||||
PersistSettings();
|
||||
Hide();
|
||||
AppLogger.Info("IndependentSettingsModule", "WindowHiddenByClose.");
|
||||
return;
|
||||
}
|
||||
|
||||
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
||||
ApplyNightModeState(_isNightMode, refreshPalettes: false);
|
||||
EnsureSelectedThemeColor();
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = Lf("settings.color.theme_ready_format", "Theme color ready: {0}.", _selectedThemeColor);
|
||||
_defaultDesktopBackground = DesktopWallpaperLayer.Background;
|
||||
RestoreSettingsTabSelection(snapshot);
|
||||
UpdateSettingsTabContent();
|
||||
UpdateWallpaperDisplay();
|
||||
UpdateWallpaperPreviewLayout();
|
||||
UpdateGridPreviewLayout();
|
||||
InitializeTimeZoneSettings();
|
||||
_ = LoadLauncherEntriesAsync();
|
||||
_suppressSettingsPersistence = false;
|
||||
_isIndependentSettingsModuleClosing = true;
|
||||
}
|
||||
|
||||
private async Task InitializeIndependentSettingsModuleAsync()
|
||||
{
|
||||
if (_independentModuleInitializationCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Info("IndependentSettingsModule", "ModuleInitStarted; Stage='Opened'.");
|
||||
_suppressSettingsPersistence = true;
|
||||
try
|
||||
{
|
||||
ShowIndependentModuleStatus(string.Empty, string.Empty, InfoBarSeverity.Informational, isOpen: false);
|
||||
|
||||
var snapshot = new AppSettingsSnapshot();
|
||||
var launcherSnapshot = new LauncherSettingsSnapshot();
|
||||
|
||||
await RunInitializationStageAsync("SnapshotLoad", () =>
|
||||
{
|
||||
snapshot = _appSettingsService.Load();
|
||||
launcherSnapshot = _launcherSettingsService.Load();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
await RunInitializationStageAsync("BaseConfiguration", () =>
|
||||
{
|
||||
_targetShortSideCells = Math.Clamp(
|
||||
snapshot.GridShortSideCells > 0 ? snapshot.GridShortSideCells : CalculateDefaultShortSideCellCountFromDpi(),
|
||||
MinShortSideCells,
|
||||
MaxShortSideCells);
|
||||
_gridSpacingPreset = NormalizeGridSpacingPreset(snapshot.GridSpacingPreset);
|
||||
_desktopEdgeInsetPercent = Math.Clamp(snapshot.DesktopEdgeInsetPercent, MinEdgeInsetPercent, MaxEdgeInsetPercent);
|
||||
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
|
||||
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
|
||||
GridSizeNumberBox.Value = _targetShortSideCells;
|
||||
GridSizeSlider.Value = _targetShortSideCells;
|
||||
GridSpacingPresetComboBox.SelectedIndex = string.Equals(_gridSpacingPreset, "Compact", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
|
||||
GridEdgeInsetSlider.Value = _desktopEdgeInsetPercent;
|
||||
GridEdgeInsetNumberBox.Value = _desktopEdgeInsetPercent;
|
||||
StatusBarSpacingModeComboBox.SelectedIndex = _statusBarSpacingMode switch
|
||||
{
|
||||
"Compact" => 0,
|
||||
"Custom" => 2,
|
||||
_ => 1
|
||||
};
|
||||
StatusBarSpacingSlider.Value = _statusBarCustomSpacingPercent;
|
||||
StatusBarSpacingNumberBox.Value = _statusBarCustomSpacingPercent;
|
||||
StatusBarSpacingCustomPanel.IsVisible = string.Equals(_statusBarSpacingMode, "Custom", StringComparison.OrdinalIgnoreCase);
|
||||
GridEdgeInsetNumberBox.ValueChanged += OnGridEdgeInsetNumberBoxChanged;
|
||||
StatusBarSpacingNumberBox.ValueChanged += OnStatusBarSpacingNumberBoxChanged;
|
||||
ApplyTaskbarSettings(snapshot);
|
||||
InitializeLocalization(snapshot.LanguageCode);
|
||||
InitializeWeatherSettings(snapshot);
|
||||
InitializeAutoStartWithWindowsSetting(snapshot);
|
||||
InitializeAppRenderModeSetting(snapshot);
|
||||
InitializeUpdateSettings(snapshot);
|
||||
InitializeLauncherVisibilitySettings(launcherSnapshot);
|
||||
InitializeSettingsIcons();
|
||||
ApplyLocalization();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
await RunInitializationStageAsync("VisualState", () =>
|
||||
{
|
||||
_selectedWallpaperPlacement = GetWallpaperPlacementFromIndex(GetPlacementIndexFromSetting(snapshot.WallpaperPlacement));
|
||||
_suppressWallpaperPlacementEvents = true;
|
||||
WallpaperPlacementComboBox.SelectedIndex = GetPlacementIndexFromSetting(snapshot.WallpaperPlacement);
|
||||
_suppressWallpaperPlacementEvents = false;
|
||||
TryRestoreWallpaper(snapshot.WallpaperPath);
|
||||
RefreshColorPalettes();
|
||||
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
|
||||
{
|
||||
_selectedThemeColor = savedThemeColor;
|
||||
}
|
||||
|
||||
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
|
||||
ApplyNightModeState(_isNightMode, refreshPalettes: false);
|
||||
EnsureSelectedThemeColor();
|
||||
UpdateThemeColorSelectionState();
|
||||
ThemeColorStatusTextBlock.Text = Lf("settings.color.theme_ready_format", "Theme color ready: {0}.", _selectedThemeColor);
|
||||
_defaultDesktopBackground = DesktopWallpaperLayer.Background;
|
||||
RestoreSettingsTabSelection(snapshot);
|
||||
UpdateSettingsTabContent();
|
||||
UpdateWallpaperDisplay();
|
||||
UpdateWallpaperPreviewLayout();
|
||||
UpdateGridPreviewLayout();
|
||||
InitializeTimeZoneSettings();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
UiExceptionGuard.FireAndForgetGuarded(
|
||||
LoadLauncherEntriesAsync,
|
||||
"IndependentSettingsModule.LoadLauncherEntries",
|
||||
UiExceptionGuard.BuildContext(("Window", nameof(SettingsWindow))),
|
||||
ex =>
|
||||
{
|
||||
ShowIndependentModuleStatus(
|
||||
L("settings.shell.partial_warning_title", "部分内容未能载入"),
|
||||
ex.Message,
|
||||
InfoBarSeverity.Warning);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
_independentModuleInitializationCompleted = true;
|
||||
AppLogger.Info("IndependentSettingsModule", "ModuleInitCompleted.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressSettingsPersistence = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunInitializationStageAsync(string stage, Func<Task> action)
|
||||
{
|
||||
AppLogger.Info("IndependentSettingsModule", $"ModuleInitStarted; Stage='{stage}'.");
|
||||
try
|
||||
{
|
||||
await action();
|
||||
AppLogger.Info("IndependentSettingsModule", $"ModuleInitCompleted; Stage='{stage}'.");
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
{
|
||||
AppLogger.Warn("IndependentSettingsModule", $"ModuleInitFailed; Stage='{stage}'.", ex);
|
||||
ShowIndependentModuleStatus(
|
||||
L("settings.shell.partial_warning_title", "部分内容未能载入"),
|
||||
ex.Message,
|
||||
InfoBarSeverity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowIndependentModuleStatus(string title, string message, InfoBarSeverity severity, bool isOpen = true)
|
||||
{
|
||||
if (IndependentSettingsStatusInfoBar is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IndependentSettingsStatusInfoBar.Title = title;
|
||||
IndependentSettingsStatusInfoBar.Message = message;
|
||||
IndependentSettingsStatusInfoBar.Severity = severity;
|
||||
IndependentSettingsStatusInfoBar.IsOpen = isOpen;
|
||||
}
|
||||
|
||||
private void OnTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTitleBarDoubleTapped(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!CanResize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WindowState = WindowState == WindowState.Maximized
|
||||
? WindowState.Normal
|
||||
: WindowState.Maximized;
|
||||
UpdateWindowChromeState();
|
||||
}
|
||||
|
||||
private void OnMinimizeWindowClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowState = WindowState.Minimized;
|
||||
UpdateWindowChromeState();
|
||||
}
|
||||
|
||||
private void OnToggleWindowStateClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!CanResize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WindowState = WindowState == WindowState.Maximized
|
||||
? WindowState.Normal
|
||||
: WindowState.Maximized;
|
||||
UpdateWindowChromeState();
|
||||
}
|
||||
|
||||
private void UpdateWindowChromeState()
|
||||
{
|
||||
if (WindowStateToggleIcon is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WindowStateToggleIcon.Symbol = WindowState == WindowState.Maximized
|
||||
? FluentIcons.Common.Symbol.SquareMultiple
|
||||
: FluentIcons.Common.Symbol.Square;
|
||||
}
|
||||
|
||||
private void OnCloseWindowClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnSettingsPaneToggleButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SettingsNavView is not null)
|
||||
{
|
||||
SettingsNavView.IsPaneOpen = !SettingsNavView.IsPaneOpen;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenLogsFolderClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = AppLogger.LogDirectory,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
{
|
||||
ShowIndependentModuleStatus(
|
||||
L("settings.shell.partial_warning_title", "部分内容未能加载"),
|
||||
ex.Message,
|
||||
InfoBarSeverity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenAppFolderClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = Path.GetFullPath(".") ?? string.Empty,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
{
|
||||
ShowIndependentModuleStatus(
|
||||
L("settings.shell.partial_warning_title", "部分内容未能加载"),
|
||||
ex.Message,
|
||||
InfoBarSeverity.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,22 +145,29 @@ public sealed class PluginLoader
|
||||
{
|
||||
Directory.CreateDirectory(dataDirectory);
|
||||
ValidatePluginRuntimeAssets(manifest, assemblyPath, pluginDirectory);
|
||||
AppLogger.Info(
|
||||
"PluginLoader",
|
||||
$"LoadCore starting. PluginId='{manifest.Id}'; AssemblyPath='{assemblyPath}'; PluginDirectory='{pluginDirectory}'; DataDirectory='{dataDirectory}'.");
|
||||
|
||||
loadContext = new PluginLoadContext(assemblyPath, _options.SharedAssemblyNames);
|
||||
var assembly = loadContext.LoadFromAssemblyPath(assemblyPath);
|
||||
AppLogger.Info("PluginLoader", $"Assembly loaded. PluginId='{manifest.Id}'; Assembly='{assembly.FullName}'.");
|
||||
var pluginType = ResolvePluginType(assembly);
|
||||
plugin = CreatePluginInstance(pluginType);
|
||||
AppLogger.Info("PluginLoader", $"Plugin instance created. PluginId='{manifest.Id}'; PluginType='{pluginType.FullName}'.");
|
||||
runtimeContext = CreateRuntimeContext(manifest, pluginDirectory, dataDirectory, properties);
|
||||
var serviceCollection = CreateServiceCollection(runtimeContext, services);
|
||||
var hostBuilderContext = CreateHostBuilderContext(runtimeContext);
|
||||
|
||||
plugin.Initialize(hostBuilderContext, serviceCollection);
|
||||
AppLogger.Info("PluginLoader", $"Plugin Initialize completed. PluginId='{manifest.Id}'.");
|
||||
|
||||
pluginServices = serviceCollection.BuildServiceProvider(new ServiceProviderOptions
|
||||
{
|
||||
ValidateScopes = false,
|
||||
ValidateOnBuild = true
|
||||
});
|
||||
AppLogger.Info("PluginLoader", $"Service provider built. PluginId='{manifest.Id}'.");
|
||||
runtimeContext.SetServices(pluginServices);
|
||||
|
||||
var settingsPages = pluginServices
|
||||
@@ -174,8 +181,12 @@ public sealed class PluginLoader
|
||||
.ThenBy(component => component.DisplayName, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var exportedServices = ResolveExports(manifest, pluginServices);
|
||||
AppLogger.Info(
|
||||
"PluginLoader",
|
||||
$"Plugin contributions resolved. PluginId='{manifest.Id}'; SettingsPages={settingsPages.Length}; Widgets={desktopComponents.Length}; Exports={exportedServices.Count}.");
|
||||
hostedServices = pluginServices.GetServices<IHostedService>().ToArray();
|
||||
StartHostedServices(hostedServices);
|
||||
AppLogger.Info("PluginLoader", $"Hosted services started. PluginId='{manifest.Id}'; HostedServices={hostedServices.Count}.");
|
||||
|
||||
var loadedPlugin = new LoadedPlugin(
|
||||
manifest,
|
||||
@@ -375,6 +386,7 @@ public sealed class PluginLoader
|
||||
{
|
||||
foreach (var hostedService in hostedServices)
|
||||
{
|
||||
AppLogger.Info("PluginLoader", $"Starting hosted service '{hostedService.GetType().FullName}'.");
|
||||
hostedService.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ internal sealed class AirAppMarketIndexService : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await File.ReadAllTextAsync(localIndexPath, cancellationToken);
|
||||
var json = await File.ReadAllTextAsync(localIndexPath, cancellationToken).ConfigureAwait(false);
|
||||
var document = AirAppMarketIndexDocument.Load(json, localIndexPath);
|
||||
_cacheService.SaveIndexJson(json);
|
||||
return new AirAppMarketLoadResult(
|
||||
@@ -61,8 +61,8 @@ internal sealed class AirAppMarketIndexService : IDisposable
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(
|
||||
AirAppMarketDefaults.DefaultIndexUrl,
|
||||
cancellationToken);
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var document = AirAppMarketIndexDocument.Load(json, AirAppMarketDefaults.DefaultIndexUrl);
|
||||
|
||||
@@ -31,14 +31,8 @@ internal static class AirAppMarketDefaults
|
||||
|
||||
public static string? TryGetWorkspaceIndexPath()
|
||||
{
|
||||
var repositoryRoot = TryGetWorkspaceRepositoryRoot("LanAirApp");
|
||||
if (repositoryRoot is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var candidatePath = Path.Combine(repositoryRoot, "airappmarket", "index.json");
|
||||
return File.Exists(candidatePath) ? candidatePath : null;
|
||||
var relativePath = Path.Combine("airappmarket", "index.json");
|
||||
return TryResolveWorkspacePath("LanAirApp", relativePath);
|
||||
}
|
||||
|
||||
public static bool TryResolveWorkspaceFile(string url, out string localPath)
|
||||
@@ -57,14 +51,8 @@ internal static class AirAppMarketDefaults
|
||||
return false;
|
||||
}
|
||||
|
||||
var repositoryRoot = TryGetWorkspaceRepositoryRoot(repositoryName);
|
||||
if (repositoryRoot is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var candidatePath = Path.GetFullPath(Path.Combine(repositoryRoot, relativePath));
|
||||
if (!File.Exists(candidatePath))
|
||||
var candidatePath = TryResolveWorkspacePath(repositoryName, relativePath);
|
||||
if (candidatePath is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -99,7 +87,7 @@ internal static class AirAppMarketDefaults
|
||||
return !string.IsNullOrWhiteSpace(owner) && !string.IsNullOrWhiteSpace(repositoryName);
|
||||
}
|
||||
|
||||
private static string? TryGetWorkspaceRepositoryRoot(string repositoryName)
|
||||
private static string? TryResolveWorkspacePath(string repositoryName, string relativePath)
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current is not null)
|
||||
@@ -107,7 +95,11 @@ internal static class AirAppMarketDefaults
|
||||
var candidate = Path.Combine(current.FullName, repositoryName);
|
||||
if (Directory.Exists(candidate))
|
||||
{
|
||||
return candidate;
|
||||
var candidatePath = Path.GetFullPath(Path.Combine(candidate, relativePath));
|
||||
if (File.Exists(candidatePath))
|
||||
{
|
||||
return candidatePath;
|
||||
}
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="960"
|
||||
d:DesignHeight="1000"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.PluginMarketSettingsPage">
|
||||
|
||||
<StackPanel x:Name="PluginMarketPanel"
|
||||
MaxWidth="920"
|
||||
Spacing="16">
|
||||
<TextBlock x:Name="PluginMarketPanelTitleTextBlock"
|
||||
IsVisible="False"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Plugin Market" />
|
||||
|
||||
<TextBlock x:Name="PluginMarketPanelSubtitleTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Browse plugins from the official LanAirApp source and stage installs." />
|
||||
Text="Browse plugins from the official LanAirApp source, review package details, and stage installations safely." />
|
||||
|
||||
<ContentControl x:Name="PluginMarketContentHost" />
|
||||
<Border Classes="settings-expander-shell"
|
||||
Padding="16,14">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock x:Name="PluginMarketSectionTitleTextBlock"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Official Source" />
|
||||
<TextBlock x:Name="PluginMarketSectionHintTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="The content below is loaded from the official market source. If network loading fails, the module will keep the page alive and show a recoverable error state instead of crashing." />
|
||||
<ContentControl x:Name="PluginMarketContentHost" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
Directory.CreateDirectory(PluginsDirectory);
|
||||
ApplyPendingPluginDeletions();
|
||||
UnloadInstalledPlugins();
|
||||
AppLogger.Info("PluginRuntime", $"Loading installed plugins from '{PluginsDirectory}'.");
|
||||
|
||||
var disabledPluginIds = GetDisabledPluginIds();
|
||||
var settingsSnapshot = _appSettingsService.Load();
|
||||
@@ -81,6 +82,9 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
var discoveryFailures = new List<PluginLoadResult>();
|
||||
var candidates = DiscoverCandidates(discoveryFailures);
|
||||
_loadResults.AddRange(discoveryFailures);
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"Plugin discovery completed. Candidates={candidates.Count}; DiscoveryFailures={discoveryFailures.Count}; PluginsDirectory='{PluginsDirectory}'.");
|
||||
|
||||
var selectedPluginIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var candidate in candidates)
|
||||
@@ -93,6 +97,7 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
new InvalidOperationException(
|
||||
$"Duplicate plugin id '{candidate.Manifest.Id}' was found. Source '{candidate.SourcePath}' was ignored because a higher-priority source was already selected."));
|
||||
_loadResults.Add(duplicateFailure);
|
||||
LogPluginFailure("CatalogSelection", duplicateFailure, treatAsError: false);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -113,7 +118,13 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"Preparing shared contracts. PluginId='{candidate.Manifest.Id}'; SourcePath='{candidate.SourcePath}'; SourceKind='{candidate.SourceKind}'.");
|
||||
RegisterSharedContractsForLoad(candidate.Manifest);
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"Shared contracts ready. PluginId='{candidate.Manifest.Id}'; SourcePath='{candidate.SourcePath}'.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -128,10 +139,13 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
ex.Message,
|
||||
0,
|
||||
0));
|
||||
Debug.WriteLine($"[PluginRuntime] Failed to prepare dependencies for '{candidate.Manifest.Id}': {ex}");
|
||||
LogPluginFailure("DependencyPrepare", dependencyFailure, treatAsError: false);
|
||||
continue;
|
||||
}
|
||||
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"Starting plugin load. PluginId='{candidate.Manifest.Id}'; SourcePath='{candidate.SourcePath}'; SourceKind='{candidate.SourceKind}'.");
|
||||
var loadResult = candidate.SourceKind switch
|
||||
{
|
||||
PluginCatalogSourceKind.Package => _loader.LoadFromPackage(
|
||||
@@ -160,6 +174,9 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
null,
|
||||
loadResult.LoadedPlugin.SettingsPages.Count,
|
||||
loadResult.LoadedPlugin.DesktopComponents.Count));
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"Plugin loaded. PluginId='{loadResult.LoadedPlugin.Manifest.Id}'; SourcePath='{loadResult.SourcePath}'; ManifestVersion='{loadResult.LoadedPlugin.Manifest.Version ?? "<unknown>"}'; ApiVersion='{loadResult.LoadedPlugin.Manifest.ApiVersion ?? "<unknown>"}'; SourceKind='{candidate.SourceKind}'; SettingsPages={loadResult.LoadedPlugin.SettingsPages.Count}; Widgets={loadResult.LoadedPlugin.DesktopComponents.Count}.");
|
||||
Debug.WriteLine($"[PluginRuntime] Loaded '{loadResult.Manifest?.Id}' from '{loadResult.SourcePath}'.");
|
||||
continue;
|
||||
}
|
||||
@@ -173,11 +190,15 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
loadResult.Error?.Message,
|
||||
0,
|
||||
0));
|
||||
LogPluginFailure("Load", loadResult, treatAsError: true);
|
||||
Debug.WriteLine($"[PluginRuntime] Failed to load plugin from '{loadResult.SourcePath}': {loadResult.Error}");
|
||||
}
|
||||
|
||||
if (_catalog.Count == 0 && discoveryFailures.Count == 0)
|
||||
{
|
||||
AppLogger.Info(
|
||||
"PluginRuntime",
|
||||
$"No plugin packages or loose manifests were discovered under '{PluginsDirectory}'.");
|
||||
Debug.WriteLine($"[PluginRuntime] No .laapp packages or loose plugin manifests found under '{PluginsDirectory}'.");
|
||||
}
|
||||
}
|
||||
@@ -392,7 +413,9 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failures.Add(PluginLoadResult.Failure(packagePath, null, ex));
|
||||
var failure = PluginLoadResult.Failure(packagePath, null, ex);
|
||||
failures.Add(failure);
|
||||
LogPluginFailure("ManifestValidation", failure, treatAsError: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +428,9 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failures.Add(PluginLoadResult.Failure(manifestPath, null, ex));
|
||||
var failure = PluginLoadResult.Failure(manifestPath, null, ex);
|
||||
failures.Add(failure);
|
||||
LogPluginFailure("ManifestValidation", failure, treatAsError: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,6 +742,21 @@ public sealed class PluginRuntimeService : IDisposable
|
||||
return Path.Combine(PluginsDirectory, PendingDeletionFileName);
|
||||
}
|
||||
|
||||
private static void LogPluginFailure(string stage, PluginLoadResult result, bool treatAsError)
|
||||
{
|
||||
var manifest = result.Manifest;
|
||||
var message =
|
||||
$"Plugin load issue. Stage='{stage}'; PluginId='{manifest?.Id ?? "<unknown>"}'; SourcePath='{result.SourcePath}'; ManifestVersion='{manifest?.Version ?? "<unknown>"}'; ApiVersion='{manifest?.ApiVersion ?? "<unknown>"}'; Error='{result.Error?.Message ?? "<none>"}'.";
|
||||
|
||||
if (treatAsError)
|
||||
{
|
||||
AppLogger.Error("PluginRuntime", message, result.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Warn("PluginRuntime", message, result.Error);
|
||||
}
|
||||
|
||||
private void RemovePluginFromSnapshot(string pluginId)
|
||||
{
|
||||
var snapshot = _appSettingsService.Load();
|
||||
|
||||
@@ -7,31 +7,40 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1000"
|
||||
x:Class="LanMountainDesktop.Views.SettingsPages.PluginSettingsPage">
|
||||
|
||||
<StackPanel x:Name="PluginSettingsPanel" Spacing="16">
|
||||
<StackPanel x:Name="PluginSettingsPanel"
|
||||
MaxWidth="920"
|
||||
Spacing="16">
|
||||
<TextBlock x:Name="PluginSettingsPanelTitleTextBlock"
|
||||
IsVisible="False"
|
||||
FontSize="24"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Text="Plugins" />
|
||||
|
||||
<TextBlock x:Name="PluginSettingsPanelSubtitleTextBlock"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Manage installed plugins, local package import, and runtime availability from one place." />
|
||||
|
||||
<Border Classes="settings-expander-shell">
|
||||
<ui:SettingsExpander x:Name="PluginSystemSettingsExpander"
|
||||
Header="Plugin Runtime"
|
||||
Description="Manage plugin loading and backend isolation."
|
||||
IsExpanded="True">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource Glyph="" FontFamily="{StaticResource SymbolThemeFontFamily}" />
|
||||
<fi:SymbolIconSource Symbol="PuzzlePiece"
|
||||
IconVariant="Regular" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock x:Name="PluginSystemDescriptionTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="This page will host installed plugin management, permission review, and sandboxed backend runtime controls." />
|
||||
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
|
||||
<Border Background="{DynamicResource LayerFillColorDefaultBrush}"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
|
||||
Padding="14">
|
||||
<TextBlock x:Name="PluginSystemStatusTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Plugin management UI is not connected yet. Next step is wiring the loader, permissions, and worker isolation state into this panel." />
|
||||
</Border>
|
||||
@@ -47,16 +56,17 @@
|
||||
Description="Manage installed plugins here."
|
||||
IsExpanded="True">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource Glyph="" FontFamily="{StaticResource SymbolThemeFontFamily}" />
|
||||
<fi:SymbolIconSource Symbol="Apps"
|
||||
IconVariant="Regular" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock x:Name="PluginRestartHintTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Plugin enable state changes take effect after restarting the app." />
|
||||
<TextBlock x:Name="PluginCatalogEmptyTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Text="No plugins found."
|
||||
IsVisible="False" />
|
||||
</StackPanel>
|
||||
@@ -70,7 +80,8 @@
|
||||
Description="Open a .laapp package and stage it into the local plugin directory."
|
||||
IsExpanded="False">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<ui:FontIconSource Glyph="" FontFamily="{StaticResource SymbolThemeFontFamily}" />
|
||||
<fi:SymbolIconSource Symbol="ArrowUpload"
|
||||
IconVariant="Regular" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<StackPanel Spacing="10">
|
||||
@@ -79,11 +90,11 @@
|
||||
Click="OnInstallPluginPackageClick"
|
||||
Content="Open .laapp package" />
|
||||
<TextBlock x:Name="PluginPackageImportHintTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
Text="Open a .laapp package to install it into the local plugin directory." />
|
||||
<TextBlock x:Name="PluginPackageImportStatusTextBlock"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
TextWrapping="Wrap"
|
||||
IsVisible="False" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Runtime.Loader;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Views.SettingsPages;
|
||||
|
||||
namespace LanMountainDesktop.Plugins;
|
||||
@@ -48,6 +49,9 @@ internal sealed class PluginSharedContractManager : IDisposable
|
||||
}
|
||||
|
||||
var document = LoadIndex(cancellationToken);
|
||||
AppLogger.Info(
|
||||
"PluginSharedContracts",
|
||||
$"Shared contract index loaded for plugin '{manifest.Id}'. SourceContracts={document.Contracts.Count}.");
|
||||
foreach (var reference in manifest.SharedContracts)
|
||||
{
|
||||
EnsureInstalled(document, reference, cancellationToken);
|
||||
@@ -64,9 +68,19 @@ internal sealed class PluginSharedContractManager : IDisposable
|
||||
}
|
||||
|
||||
var assemblyNames = new List<string>(manifest.SharedContracts.Count);
|
||||
AirAppMarketIndexDocument? document = null;
|
||||
foreach (var reference in manifest.SharedContracts)
|
||||
{
|
||||
var assemblyPath = GetInstalledAssemblyPath(reference);
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
document ??= LoadIndex(cancellationToken);
|
||||
AppLogger.Info(
|
||||
"PluginSharedContracts",
|
||||
$"Installing missing shared contract during plugin load. PluginId='{manifest.Id}'; ContractId='{reference.Id}'; Version='{reference.Version}'; Destination='{assemblyPath}'.");
|
||||
EnsureInstalled(document, reference, cancellationToken);
|
||||
}
|
||||
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
@@ -115,10 +129,12 @@ internal sealed class PluginSharedContractManager : IDisposable
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
|
||||
|
||||
var temporaryPath = destinationPath + ".download";
|
||||
var resolvedSource = entry.DownloadUrl;
|
||||
try
|
||||
{
|
||||
if (AirAppMarketDefaults.TryResolveWorkspaceFile(entry.DownloadUrl, out var localSourcePath))
|
||||
{
|
||||
resolvedSource = localSourcePath;
|
||||
File.Copy(localSourcePath, temporaryPath, overwrite: true);
|
||||
}
|
||||
else
|
||||
@@ -136,6 +152,9 @@ internal sealed class PluginSharedContractManager : IDisposable
|
||||
|
||||
ValidateInstalledFile(temporaryPath, entry);
|
||||
File.Move(temporaryPath, destinationPath, overwrite: true);
|
||||
AppLogger.Info(
|
||||
"PluginSharedContracts",
|
||||
$"Installed shared contract. ContractId='{reference.Id}'; Version='{reference.Version}'; Source='{resolvedSource}'; Destination='{destinationPath}'.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -145,6 +164,7 @@ internal sealed class PluginSharedContractManager : IDisposable
|
||||
|
||||
private AirAppMarketIndexDocument LoadIndex(CancellationToken cancellationToken)
|
||||
{
|
||||
AppLogger.Info("PluginSharedContracts", "Loading market index for shared contract resolution.");
|
||||
var result = _indexService.LoadAsync(cancellationToken).GetAwaiter().GetResult();
|
||||
if (!result.Success || result.Document is null)
|
||||
{
|
||||
@@ -152,6 +172,10 @@ internal sealed class PluginSharedContractManager : IDisposable
|
||||
$"Failed to load market index for shared contract resolution: {result.ErrorMessage ?? "Unknown error"}");
|
||||
}
|
||||
|
||||
AppLogger.Info(
|
||||
"PluginSharedContracts",
|
||||
$"Market index ready. Source='{result.Source}'; Location='{result.SourceLocation}'; Warning='{result.WarningMessage ?? string.Empty}'.");
|
||||
|
||||
return result.Document;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using FluentIcons.Common;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Services;
|
||||
|
||||
@@ -17,11 +17,12 @@ public partial class SettingsWindow
|
||||
|
||||
private void InitializePluginSettingsNavigation()
|
||||
{
|
||||
if (_pluginSettingsPageHosts.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_pluginSettingsPageHosts.Clear();
|
||||
_pluginSettingsNavItems.Clear();
|
||||
}
|
||||
|
||||
private void RegisterPluginSettingsDefinitions()
|
||||
{
|
||||
var runtime = (Application.Current as App)?.PluginRuntimeService;
|
||||
var contributions = runtime?.SettingsPages
|
||||
.OrderBy(contribution => contribution.Registration.SortOrder)
|
||||
@@ -31,7 +32,6 @@ public partial class SettingsWindow
|
||||
|
||||
if (contributions is not { Length: > 0 })
|
||||
{
|
||||
SettingsPluginNavSection.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,23 +39,21 @@ public partial class SettingsWindow
|
||||
.GroupBy(contribution => contribution.Plugin.Manifest.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var contribution in contributions)
|
||||
for (var i = 0; i < contributions.Length; i++)
|
||||
{
|
||||
var contribution = contributions[i];
|
||||
var tag = BuildPluginSettingsTag(contribution);
|
||||
var navigationTitle = BuildPluginSettingsNavigationTitle(contribution, pageCountsByPluginId);
|
||||
var navItem = CreateSettingsNavItem(tag, Symbol.PuzzlePiece, navigationTitle);
|
||||
ToolTip.SetTip(navItem, $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}");
|
||||
_pluginSettingsPageHosts[tag] = CreatePluginSettingsPageHost(contribution);
|
||||
|
||||
SettingsPluginNavHost.Children.Add(navItem);
|
||||
_pluginSettingsNavItems[tag] = navItem;
|
||||
|
||||
var pageHost = CreatePluginSettingsPageHost(contribution);
|
||||
pageHost.IsVisible = false;
|
||||
SettingsContentPagesHost.Children.Add(pageHost);
|
||||
_pluginSettingsPageHosts[tag] = pageHost;
|
||||
RegisterSettingsPageDefinition(new IndependentSettingsPageDefinition(
|
||||
tag,
|
||||
BuildPluginSettingsNavigationTitle(contribution, pageCountsByPluginId),
|
||||
BuildPluginSettingsPageDescription(contribution),
|
||||
FluentIcons.Common.Symbol.PuzzlePiece,
|
||||
IndependentSettingsPageCategory.External,
|
||||
200 + i,
|
||||
$"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}"));
|
||||
}
|
||||
|
||||
SettingsPluginNavSection.IsVisible = SettingsPluginNavHost.Children.Count > 0;
|
||||
}
|
||||
|
||||
private static string BuildPluginSettingsTag(PluginSettingsPageContribution contribution)
|
||||
@@ -72,6 +70,15 @@ public partial class SettingsWindow
|
||||
: contribution.Plugin.Manifest.Name;
|
||||
}
|
||||
|
||||
private string BuildPluginSettingsPageDescription(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
return Lf(
|
||||
"settings.page_desc.plugin_contributed_format",
|
||||
"Settings page '{0}' is provided by plugin '{1}'.",
|
||||
contribution.Registration.Title,
|
||||
contribution.Plugin.Manifest.Name);
|
||||
}
|
||||
|
||||
private Control CreatePluginSettingsPageHost(PluginSettingsPageContribution contribution)
|
||||
{
|
||||
Control content;
|
||||
@@ -87,6 +94,7 @@ public partial class SettingsWindow
|
||||
return new StackPanel
|
||||
{
|
||||
Spacing = 16,
|
||||
MaxWidth = 920,
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
@@ -94,12 +102,12 @@ public partial class SettingsWindow
|
||||
Text = contribution.Registration.Title,
|
||||
FontSize = 24,
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush")
|
||||
Foreground = GetThemeBrush("TextFillColorPrimaryBrush")
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = contribution.Plugin.Manifest.Name,
|
||||
Foreground = GetThemeBrush("AdaptiveTextSecondaryBrush")
|
||||
Foreground = GetThemeBrush("TextFillColorSecondaryBrush")
|
||||
},
|
||||
content
|
||||
}
|
||||
@@ -123,58 +131,32 @@ public partial class SettingsWindow
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdatePluginSettingsPageVisibility(string? selectedTag)
|
||||
{
|
||||
foreach (var pair in _pluginSettingsPageHosts)
|
||||
{
|
||||
pair.Value.IsVisible = string.Equals(pair.Key, selectedTag, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RefreshPluginSettingsNavigation()
|
||||
{
|
||||
foreach (var pair in _pluginSettingsPageHosts.ToArray())
|
||||
{
|
||||
if (_pluginSettingsNavItems.TryGetValue(pair.Key, out var navItem))
|
||||
{
|
||||
SettingsPluginNavHost.Children.Remove(navItem);
|
||||
}
|
||||
|
||||
SettingsContentPagesHost.Children.Remove(pair.Value);
|
||||
}
|
||||
|
||||
_pluginSettingsPageHosts.Clear();
|
||||
_pluginSettingsNavItems.Clear();
|
||||
SettingsPluginNavSection.IsVisible = false;
|
||||
InitializePluginSettingsNavigation();
|
||||
|
||||
if (GetSettingsNavItem(_selectedSettingsTabTag) is null)
|
||||
{
|
||||
SelectSettingsTab("Plugins", persistSelection: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectSettingsTab(_selectedSettingsTabTag, persistSelection: false);
|
||||
}
|
||||
var preferredTag = NormalizeSettingsPageTag(_selectedSettingsTabTag);
|
||||
InitializeSettingsNavigation();
|
||||
SelectSettingsTab(
|
||||
_settingsPageDefinitions.ContainsKey(preferredTag) ? preferredTag : "Plugins",
|
||||
persistSelection: false);
|
||||
PluginSettingsPanel?.RefreshFromRuntime();
|
||||
}
|
||||
|
||||
private string? GetSelectedSettingsTabTag()
|
||||
{
|
||||
return _selectedSettingsTabTag;
|
||||
return NormalizeSettingsPageTag(_selectedSettingsTabTag);
|
||||
}
|
||||
|
||||
private int ResolveSelectedSettingsTabIndex()
|
||||
{
|
||||
var selectedTag = GetSelectedSettingsTabTag();
|
||||
if (string.IsNullOrWhiteSpace(selectedTag))
|
||||
if (SettingsNavView?.MenuItems is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var buttons = EnumerateSettingsNavItems().ToList();
|
||||
for (var i = 0; i < buttons.Count; i++)
|
||||
var items = SettingsNavView.MenuItems.OfType<NavigationViewItem>().ToList();
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (string.Equals(buttons[i].Tag?.ToString(), selectedTag, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(items[i].Tag?.ToString(), NormalizeSettingsPageTag(_selectedSettingsTabTag), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
@@ -185,21 +167,32 @@ public partial class SettingsWindow
|
||||
|
||||
private void RestoreSettingsTabSelection(AppSettingsSnapshot snapshot)
|
||||
{
|
||||
var buttons = EnumerateSettingsNavItems().ToList();
|
||||
if (buttons.Count == 0)
|
||||
if (SettingsNavView?.MenuItems is null || SettingsNavView.MenuItems.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.SettingsTabTag) &&
|
||||
GetSettingsNavItem(snapshot.SettingsTabTag) is not null)
|
||||
var items = SettingsNavView.MenuItems.OfType<NavigationViewItem>().ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
SelectSettingsTab(snapshot.SettingsTabTag, persistSelection: false);
|
||||
return;
|
||||
}
|
||||
|
||||
var safeIndex = Math.Clamp(snapshot.SettingsTabIndex, 0, Math.Max(0, buttons.Count - 1));
|
||||
var button = buttons[safeIndex];
|
||||
SelectSettingsTab(button.Tag?.ToString() ?? "Wallpaper", persistSelection: false);
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.SettingsTabTag))
|
||||
{
|
||||
var normalizedTag = NormalizeSettingsPageTag(snapshot.SettingsTabTag);
|
||||
var taggedItem = items
|
||||
.FirstOrDefault(item => string.Equals(item.Tag?.ToString(), normalizedTag, StringComparison.OrdinalIgnoreCase));
|
||||
if (taggedItem is not null)
|
||||
{
|
||||
_selectedSettingsTabTag = normalizedTag;
|
||||
SettingsNavView.SelectedItem = taggedItem;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var safeIndex = Math.Clamp(snapshot.SettingsTabIndex, 0, Math.Max(0, items.Count - 1));
|
||||
_selectedSettingsTabTag = items[safeIndex].Tag?.ToString() ?? _selectedSettingsTabTag;
|
||||
SettingsNavView.SelectedItem = items[safeIndex];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user