mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
340 lines
12 KiB
C#
340 lines
12 KiB
C#
using System;
|
|
using System.Linq;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Media;
|
|
using Avalonia.Styling;
|
|
using Avalonia.Threading;
|
|
using LanMountainDesktop.Models;
|
|
using LanMountainDesktop.PluginSdk;
|
|
using LanMountainDesktop.Services;
|
|
using LanMountainDesktop.Theme;
|
|
using LanMountainDesktop.ViewModels;
|
|
using LanMountainDesktop.Views;
|
|
|
|
namespace LanMountainDesktop.Services.Settings;
|
|
|
|
public enum SettingsWindowAnchorTarget
|
|
{
|
|
DesktopDockTrailingEdge = 0
|
|
}
|
|
|
|
public enum SettingsWindowFallbackMode
|
|
{
|
|
None = 0,
|
|
ScreenBottomRight = 1
|
|
}
|
|
|
|
public readonly record struct SettingsWindowOpenRequest(
|
|
string Source,
|
|
Window? Owner = null,
|
|
string? PageId = null,
|
|
SettingsWindowAnchorTarget AnchorTarget = SettingsWindowAnchorTarget.DesktopDockTrailingEdge,
|
|
SettingsWindowFallbackMode FallbackMode = SettingsWindowFallbackMode.ScreenBottomRight);
|
|
|
|
public interface ISettingsWindowAnchorProvider
|
|
{
|
|
bool TryGetSettingsWindowAnchorBounds(out PixelRect anchorBounds);
|
|
}
|
|
|
|
public interface ISettingsWindowService
|
|
{
|
|
bool IsOpen { get; }
|
|
|
|
event EventHandler? StateChanged;
|
|
|
|
void Open(SettingsWindowOpenRequest request);
|
|
|
|
void Close();
|
|
|
|
void Toggle(SettingsWindowOpenRequest request);
|
|
}
|
|
|
|
internal sealed class SettingsWindowService : ISettingsWindowService
|
|
{
|
|
private readonly ISettingsPageRegistry _pageRegistry;
|
|
private readonly IHostApplicationLifecycle _hostApplicationLifecycle;
|
|
private readonly ISettingsFacadeService _settingsFacade;
|
|
private readonly IAppearanceThemeService _appearanceThemeService;
|
|
private readonly LocalizationService _localizationService;
|
|
private SettingsWindowViewModel _viewModel = null!;
|
|
private SettingsWindow? _window;
|
|
|
|
public SettingsWindowService(
|
|
ISettingsPageRegistry pageRegistry,
|
|
IHostApplicationLifecycle hostApplicationLifecycle,
|
|
ISettingsFacadeService settingsFacade)
|
|
{
|
|
_pageRegistry = pageRegistry;
|
|
_hostApplicationLifecycle = hostApplicationLifecycle;
|
|
_settingsFacade = settingsFacade;
|
|
_appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
|
|
_localizationService = new();
|
|
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
|
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
|
}
|
|
|
|
private string L(string key)
|
|
{
|
|
var regionState = _settingsFacade.Region.Get();
|
|
var languageCode = regionState.LanguageCode ?? "zh-CN";
|
|
return _localizationService.GetString(languageCode, key, key);
|
|
}
|
|
|
|
public bool IsOpen => _window is { IsVisible: true };
|
|
public event EventHandler? StateChanged;
|
|
|
|
public void Open(SettingsWindowOpenRequest request)
|
|
{
|
|
_pageRegistry.Rebuild();
|
|
_window ??= CreateWindow();
|
|
var appearanceSnapshot = _appearanceThemeService.GetCurrent();
|
|
_window.ApplyChromeMode(appearanceSnapshot.UseSystemChrome);
|
|
ApplyTheme(_window);
|
|
_window.ReloadPages(request.PageId);
|
|
PositionWindow(_window, request);
|
|
|
|
if (!_window.IsVisible)
|
|
{
|
|
if (request.Owner is not null && request.Owner.IsVisible)
|
|
{
|
|
_window.Show(request.Owner);
|
|
}
|
|
else
|
|
{
|
|
_window.Show();
|
|
}
|
|
|
|
NotifyStateChanged();
|
|
PositionWindowLater(_window, request);
|
|
return;
|
|
}
|
|
|
|
_window.Activate();
|
|
PositionWindowLater(_window, request);
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
_window?.Close();
|
|
}
|
|
|
|
public void Toggle(SettingsWindowOpenRequest request)
|
|
{
|
|
if (IsOpen)
|
|
{
|
|
Close();
|
|
return;
|
|
}
|
|
|
|
Open(request);
|
|
}
|
|
|
|
private SettingsWindow CreateWindow()
|
|
{
|
|
var regionState = _settingsFacade.Region.Get();
|
|
var languageCode = regionState.LanguageCode ?? "zh-CN";
|
|
|
|
_viewModel = new SettingsWindowViewModel(_localizationService, languageCode).Initialize();
|
|
|
|
var appearanceSnapshot = _appearanceThemeService.GetCurrent();
|
|
var useSystemChrome = appearanceSnapshot.UseSystemChrome;
|
|
|
|
var window = new SettingsWindow(
|
|
_viewModel,
|
|
_pageRegistry,
|
|
_hostApplicationLifecycle,
|
|
useSystemChrome);
|
|
ApplyTheme(window);
|
|
window.ShowInTaskbar = false;
|
|
window.Closed += (_, _) =>
|
|
{
|
|
_window = null;
|
|
NotifyStateChanged();
|
|
};
|
|
return window;
|
|
}
|
|
|
|
private void PositionWindowLater(SettingsWindow window, SettingsWindowOpenRequest request)
|
|
{
|
|
Dispatcher.UIThread.Post(
|
|
() =>
|
|
{
|
|
if (!window.IsVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PositionWindow(window, request);
|
|
},
|
|
DispatcherPriority.Background);
|
|
}
|
|
|
|
private static void PositionWindow(SettingsWindow window, SettingsWindowOpenRequest request)
|
|
{
|
|
if (request.AnchorTarget == SettingsWindowAnchorTarget.DesktopDockTrailingEdge &&
|
|
request.Owner is ISettingsWindowAnchorProvider anchorProvider &&
|
|
anchorProvider.TryGetSettingsWindowAnchorBounds(out var anchorBounds))
|
|
{
|
|
PositionWindowAboveAnchor(window, anchorBounds, request);
|
|
return;
|
|
}
|
|
|
|
if (request.FallbackMode == SettingsWindowFallbackMode.ScreenBottomRight)
|
|
{
|
|
PositionWindowNearScreenBottomRight(window, request);
|
|
}
|
|
}
|
|
|
|
private static void PositionWindowAboveAnchor(Window window, PixelRect anchorBounds, SettingsWindowOpenRequest request)
|
|
{
|
|
var workingArea = GetWorkingArea(window, request);
|
|
|
|
if (anchorBounds.Width <= 0 || anchorBounds.Height <= 0 ||
|
|
anchorBounds.Right < workingArea.X || anchorBounds.Y > workingArea.Bottom)
|
|
{
|
|
PositionWindowNearScreenBottomRight(window, request);
|
|
return;
|
|
}
|
|
|
|
var scale = window.RenderScaling > 0 ? window.RenderScaling : 1d;
|
|
var width = ResolveWindowWidth(window, scale);
|
|
var height = ResolveWindowHeight(window, scale);
|
|
var inset = (int)Math.Round(24 * scale);
|
|
var gap = (int)Math.Round(16 * scale);
|
|
|
|
var x = anchorBounds.Right - width - inset;
|
|
var y = anchorBounds.Y - height - gap;
|
|
x = Math.Clamp(x, workingArea.X + inset, Math.Max(workingArea.X + inset, workingArea.Right - width - inset));
|
|
y = Math.Clamp(y, workingArea.Y + inset, Math.Max(workingArea.Y + inset, workingArea.Bottom - height - inset));
|
|
window.Position = new PixelPoint(x, y);
|
|
}
|
|
|
|
private static void PositionWindowNearScreenBottomRight(Window window, SettingsWindowOpenRequest request)
|
|
{
|
|
var workingArea = GetWorkingArea(window, request);
|
|
var scale = window.RenderScaling > 0 ? window.RenderScaling : 1d;
|
|
var width = ResolveWindowWidth(window, scale);
|
|
var height = ResolveWindowHeight(window, scale);
|
|
var inset = (int)Math.Round(24 * scale);
|
|
|
|
var x = Math.Max(workingArea.X + inset, workingArea.Right - width - inset);
|
|
var y = Math.Max(workingArea.Y + inset, workingArea.Bottom - height - inset);
|
|
window.Position = new PixelPoint(x, y);
|
|
}
|
|
|
|
private static PixelRect GetWorkingArea(Window window, SettingsWindowOpenRequest request)
|
|
{
|
|
if (request.Owner is not null && request.Owner.Screens?.ScreenFromWindow(request.Owner) is { } ownerScreen)
|
|
{
|
|
return ownerScreen.WorkingArea;
|
|
}
|
|
|
|
if (window.Screens?.ScreenFromWindow(window) is { } windowScreen)
|
|
{
|
|
return windowScreen.WorkingArea;
|
|
}
|
|
|
|
return window.Screens?.Primary?.WorkingArea
|
|
?? new PixelRect(
|
|
0,
|
|
0,
|
|
Math.Max(1280, ResolveWindowWidth(window, 1d) + 96),
|
|
Math.Max(720, ResolveWindowHeight(window, 1d) + 96));
|
|
}
|
|
|
|
private static int ResolveWindowWidth(Window window, double scale)
|
|
{
|
|
var widthDip = window.Bounds.Width > 1 ? window.Bounds.Width : Math.Max(window.Width, window.MinWidth);
|
|
return Math.Max(320, (int)Math.Round(widthDip * scale));
|
|
}
|
|
|
|
private static int ResolveWindowHeight(Window window, double scale)
|
|
{
|
|
var heightDip = window.Bounds.Height > 1 ? window.Bounds.Height : Math.Max(window.Height, window.MinHeight);
|
|
return Math.Max(240, (int)Math.Round(heightDip * scale));
|
|
}
|
|
|
|
private void NotifyStateChanged()
|
|
{
|
|
StateChanged?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
|
|
{
|
|
_ = sender;
|
|
|
|
if (e.Scope != SettingsScope.App)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
if (_window is null || _viewModel is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var changedKeys = e.ChangedKeys?.ToArray();
|
|
var refreshAll = changedKeys is null || changedKeys.Length == 0;
|
|
var languageChanged = refreshAll || changedKeys.Contains(nameof(AppSettingsSnapshot.LanguageCode), StringComparer.OrdinalIgnoreCase);
|
|
var liveAppearance = _appearanceThemeService.GetCurrent();
|
|
var themeChanged =
|
|
refreshAll ||
|
|
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
|
|
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeSeedMonet, StringComparison.OrdinalIgnoreCase) &&
|
|
changedKeys.Contains(nameof(AppSettingsSnapshot.ThemeColor), StringComparer.OrdinalIgnoreCase)) ||
|
|
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeWallpaperMonet, StringComparison.OrdinalIgnoreCase) &&
|
|
(changedKeys.Contains(nameof(AppSettingsSnapshot.WallpaperPath), StringComparer.OrdinalIgnoreCase) ||
|
|
changedKeys.Contains(nameof(AppSettingsSnapshot.WallpaperType), StringComparer.OrdinalIgnoreCase) ||
|
|
changedKeys.Contains(nameof(AppSettingsSnapshot.WallpaperColor), StringComparer.OrdinalIgnoreCase))) ||
|
|
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase);
|
|
|
|
if (languageChanged)
|
|
{
|
|
var regionState = _settingsFacade.Region.Get();
|
|
// 清除本地化缓存,强制重新加载语言文件
|
|
_localizationService.ClearCache();
|
|
_viewModel.RefreshLanguage(regionState.LanguageCode);
|
|
_pageRegistry.Rebuild();
|
|
_window.ReloadPages(_viewModel.CurrentPageId);
|
|
_window.RefreshShellText();
|
|
}
|
|
|
|
if (themeChanged)
|
|
{
|
|
var appearanceSnapshot = _appearanceThemeService.GetCurrent();
|
|
_window.ApplyChromeMode(appearanceSnapshot.UseSystemChrome);
|
|
ApplyTheme(_window);
|
|
}
|
|
}, DispatcherPriority.Background);
|
|
}
|
|
|
|
private void ApplyTheme(SettingsWindow window)
|
|
{
|
|
var appearanceSnapshot = _appearanceThemeService.GetCurrent();
|
|
window.RequestedThemeVariant = appearanceSnapshot.IsNightMode
|
|
? ThemeVariant.Dark
|
|
: ThemeVariant.Light;
|
|
_appearanceThemeService.ApplyThemeResources(window.Resources);
|
|
_appearanceThemeService.ApplyWindowMaterial(window, MaterialSurfaceRole.SettingsWindowBackground);
|
|
}
|
|
|
|
private void OnAppearanceThemeChanged(object? sender, AppearanceThemeSnapshot e)
|
|
{
|
|
_ = sender;
|
|
_ = e;
|
|
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
if (_window is null || _viewModel is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ApplyTheme(_window);
|
|
}, DispatcherPriority.Background);
|
|
}
|
|
}
|