Refactor settings window UI and theming

Improve theming and layout for the Settings window and related services.

- MaterialSurfaceService: add special material parameters for SettingsWindowBackground (lower alpha, no blur) and avoid hot-switching real backdrops for non-settings windows.
- GlassEffectService: add AdaptiveSettingsWindowTintBrush + ResolveSettingsWindowTintAlpha to provide optional content tinting tied to system material mode.
- SettingsWindowService: refactor theme application into ApplyThemeVariantAndResources, ensure settings window material is applied at show/activate times, and tidy theme/resource application flow.
- SettingsWindow.axaml / .axaml.cs: restructure title bar (separate Grid.Row=0 border) and FANavigationView host, add pane-footer toggle button for :minimal layout, use dynamic corner radius resource, and update toggle/visibility/icon logic and responsive layout code.
- SettingsPages: remove some IconText usages and adjust margins; use DesignCornerRadiusLg for update card corner radius.
- Add NuGet.Config to set local globalPackagesFolder and ignore .nuget/packages in .gitignore.

These changes aim to improve visuals, avoid backdrop overdraw, and make the settings window behavior consistent across themes and layouts.
This commit is contained in:
lincube
2026-05-04 03:19:25 +08:00
parent 3a8516334a
commit 6a30bc6fce
9 changed files with 205 additions and 125 deletions

3
.gitignore vendored
View File

@@ -6,6 +6,9 @@
# dotenv files
.env
# Local NuGet global packages (NuGet.Config globalPackagesFolder)
.nuget/packages/
# User-specific files
*.rsuser
*.suo

View File

@@ -353,6 +353,26 @@ internal sealed class MaterialSurfaceService : IMaterialSurfaceService
MaterialSurfaceRole role,
bool isNightMode)
{
// Settings 根层(如 RootGrid叠在 Transparent + Mica/Acrylic 上:过高 alpha 会完全盖住系统 backdrop。
// 保持非 None 下较低 alphaNone 仍用不透明白底等价。BlurRadius=0由 DWM 提供模糊)。
if (role == MaterialSurfaceRole.SettingsWindowBackground)
{
return materialMode switch
{
ThemeAppearanceValues.MaterialAcrylic => (
0.20,
0.14,
isNightMode ? (byte)0x8E : (byte)0x96,
0),
ThemeAppearanceValues.MaterialMica => (
0.14,
0.08,
isNightMode ? (byte)0x9E : (byte)0xA6,
0),
_ => (0.08, 0.05, (byte)0xFF, 0)
};
}
var isOverlay = role is MaterialSurfaceRole.DockBackground or MaterialSurfaceRole.StatusBarBackground or MaterialSurfaceRole.OverlayPanel;
return materialMode switch
{
@@ -491,7 +511,8 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
// Avoid hot-switching real backdrops on already-visible windows. This has been
// a stability hotspot when users flip theme source/material at runtime.
if (window.IsVisible)
// SettingsWindowBackground 是唯一需要材质与资源同步热切换的宿主角色;其它窗口仍保持「仅创建时」应用以降低风险。
if (window.IsVisible && role != MaterialSurfaceRole.SettingsWindowBackground)
{
return;
}

View File

@@ -69,6 +69,15 @@ public static class GlassEffectService
resources["AdaptiveWindowBackgroundBrush"] = new SolidColorBrush(windowSurface.BackgroundColor);
resources["AdaptiveWindowBorderBrush"] = new SolidColorBrush(windowSurface.BorderColor);
resources["AdaptiveSettingsWindowBackgroundBrush"] = new SolidColorBrush(settingsWindowSurface.BackgroundColor);
// 可选:叠在内容区上的可读性 tint半透明不改变 AdaptiveSettingsWindowBackgroundBrush 的语义权重,供 P1 绑定内容层。
var settingsTintBase = settingsWindowSurface.BackgroundColor;
var settingsTintAlpha = ResolveSettingsWindowTintAlpha(context);
resources["AdaptiveSettingsWindowTintBrush"] = new SolidColorBrush(
Color.FromArgb(
settingsTintAlpha,
settingsTintBase.R,
settingsTintBase.G,
settingsTintBase.B));
resources["AdaptiveSettingsWindowBorderBrush"] = new SolidColorBrush(settingsWindowSurface.BorderColor);
resources["AdaptiveDockBackgroundBrush"] = new SolidColorBrush(dockSurface.BackgroundColor);
resources["AdaptiveDockBorderBrush"] = new SolidColorBrush(dockSurface.BorderColor);
@@ -100,4 +109,16 @@ public static class GlassEffectService
resources["AdaptiveDesktopComponentHostOpacity"] = desktopComponentSurface.Opacity;
resources["AdaptiveStatusBarComponentHostOpacity"] = statusBarComponentSurface.Opacity;
}
/// <summary>可选内容叠层 alpha与设置窗表面色相一致None 为 0 避免重复染色。</summary>
private static byte ResolveSettingsWindowTintAlpha(ThemeColorContext context)
{
var mode = ThemeAppearanceValues.NormalizeSystemMaterialMode(context.SystemMaterialMode);
return mode switch
{
ThemeAppearanceValues.MaterialAcrylic => context.IsNightMode ? (byte)0x58 : (byte)0x4C,
ThemeAppearanceValues.MaterialMica => context.IsNightMode ? (byte)0x50 : (byte)0x44,
_ => (byte)0x00
};
}
}

View File

@@ -71,7 +71,7 @@ internal sealed class SettingsWindowService : ISettingsWindowService
_window ??= CreateWindow();
var appearanceSnapshot = _appearanceThemeService.GetCurrent();
_window.ApplyChromeMode(appearanceSnapshot.UseSystemChrome);
ApplyTheme(_window);
ApplyThemeVariantAndResources(_window);
var targetPageId = request.PageId ?? _window.ViewModel.CurrentPageId;
_window.ReloadPages(targetPageId);
@@ -79,6 +79,7 @@ internal sealed class SettingsWindowService : ISettingsWindowService
if (!_window.IsVisible)
{
CenterWindow(_window, request);
_appearanceThemeService.ApplyWindowMaterial(_window, MaterialSurfaceRole.SettingsWindowBackground);
_window.Show();
NotifyStateChanged();
CenterWindowLater(_window, request);
@@ -90,6 +91,7 @@ internal sealed class SettingsWindowService : ISettingsWindowService
_window.WindowState = WindowState.Normal;
}
_appearanceThemeService.ApplyWindowMaterial(_window, MaterialSurfaceRole.SettingsWindowBackground);
_window.Activate();
}
@@ -113,7 +115,6 @@ internal sealed class SettingsWindowService : ISettingsWindowService
_pageRegistry,
_hostApplicationLifecycle,
useSystemChrome);
ApplyTheme(window);
window.ShowInTaskbar = true;
window.Closed += (_, _) =>
{
@@ -285,13 +286,23 @@ internal sealed class SettingsWindowService : ISettingsWindowService
}, DispatcherPriority.Background);
}
private void ApplyTheme(SettingsWindow window)
private static void ApplyThemeVariantAndResources(SettingsWindow window, IAppearanceThemeService appearanceThemeService)
{
var appearanceSnapshot = _appearanceThemeService.GetCurrent();
var appearanceSnapshot = appearanceThemeService.GetCurrent();
window.RequestedThemeVariant = appearanceSnapshot.IsNightMode
? ThemeVariant.Dark
: ThemeVariant.Light;
_appearanceThemeService.ApplyThemeResources(window.Resources);
appearanceThemeService.ApplyThemeResources(window.Resources);
}
private void ApplyThemeVariantAndResources(SettingsWindow window)
{
ApplyThemeVariantAndResources(window, _appearanceThemeService);
}
private void ApplyTheme(SettingsWindow window)
{
ApplyThemeVariantAndResources(window, _appearanceThemeService);
_appearanceThemeService.ApplyWindowMaterial(window, MaterialSurfaceRole.SettingsWindowBackground);
}

View File

@@ -1,7 +1,6 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:symbol="using:FluentIcons.Common"
@@ -52,10 +51,6 @@
</Grid>
</Border>
<controls:IconText Icon="Apps"
Text="{Binding AppearanceHeader}"
Margin="0,0,0,4" />
<ui:FASettingsExpander Classes="settings-expander-card"
Header="{Binding AppearanceHeader}"
Description="{Binding AppearanceDescription}"
@@ -76,11 +71,8 @@
</ui:FASettingsExpanderItem>
</ui:FASettingsExpander>
<controls:IconText Icon="Apps"
Text="{Binding HiddenHeader}"
Margin="0,24,0,4" />
<ui:FASettingsExpander Classes="settings-expander-card"
Margin="0,24,0,14"
Header="{Binding HiddenHeader}"
Description="{Binding HiddenDescription}"
IsExpanded="True">

View File

@@ -10,7 +10,7 @@
<Style Selector="Border.update-status-card">
<Setter Property="Padding" Value="24" />
<Setter Property="Margin" Value="0,0,0,18" />
<Setter Property="CornerRadius" Value="24" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusLg}" />
<Setter Property="Background" Value="{DynamicResource AdaptiveSurfaceRaisedBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassPanelBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />

View File

@@ -40,10 +40,108 @@
Classes="settings-scope"
Background="{DynamicResource AdaptiveSettingsWindowBackgroundBrush}"
RowDefinitions="Auto,*">
<!-- 顶栏布局对齐 SecRandom折叠/品牌/标题)+ 中(透明拖窗区)+ 右(重启 / Windows caption 占位) -->
<Border x:Name="WindowTitleBarHost"
Height="48"
<!-- ClassIsland SettingsWindowNew声明顺序为先 FANavigationViewGrid.Row=1再顶栏 BorderGrid.Row=0Row 仍为 0=标题栏、1=导航宿主,最终叠放不变。 -->
<ui:FANavigationView x:Name="RootNavigationView"
Grid.Row="1"
Margin="0"
Background="Transparent"
PaneDisplayMode="Auto"
OpenPaneLength="283"
IsSettingsVisible="False"
IsBackButtonVisible="False"
SelectionChanged="OnNavigationSelectionChanged">
<ui:FANavigationView.PaneFooter>
<!-- 仅在 :minimalIsPaneToggleButtonVisible=False时由代码显示与模板内 pane-toggle-button 一致,不用顶栏备胎 -->
<StackPanel Orientation="Vertical">
<Button x:Name="PaneFooterToggleButton"
Classes="pane-toggle-button"
Margin="0,-8,0,0"
MinWidth="40"
Width="48"
VerticalAlignment="Bottom"
Click="OnPaneFooterToggleClick">
<Grid>
<fi:FluentIcon x:Name="PaneFooterToggleButtonIcon"
Icon="Navigation"
IconVariant="Regular"
FontSize="16"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</Grid>
</Button>
</StackPanel>
</ui:FANavigationView.PaneFooter>
<ui:FANavigationView.Styles>
<Style Selector="ui|FANavigationView#RootNavigationView:minimal">
<Setter Property="IsPaneToggleButtonVisible" Value="False"/>
</Style>
</ui:FANavigationView.Styles>
<ui:FANavigationView.Resources>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
</ui:FANavigationView.Resources>
<Grid x:Name="SettingsContentGrid"
ColumnDefinitions="*,Auto"
ColumnSpacing="20"
Margin="12,0,16,16">
<Grid Grid.Column="0"
RowDefinitions="Auto,*">
<Grid x:Name="PageTitleContainer"
Grid.Row="0"
Classes="page-title-container"
IsVisible="{Binding IsPageTitleVisible}">
<TextBlock x:Name="PageTitleTextBlock"
Classes="page-title-text"
Text="{Binding CurrentPageTitle}" />
</Grid>
<ui:FAFrame x:Name="ContentFrame"
Grid.Row="1" />
</Grid>
<Border x:Name="DrawerBorder"
Grid.Column="1"
Width="296"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="20"
BoxShadow="0 8 24 #14000000"
IsVisible="{Binding IsDrawerOpen}">
<Grid RowDefinitions="Auto,*"
RowSpacing="16">
<Grid ColumnDefinitions="*,Auto">
<TextBlock x:Name="DrawerTitleTextBlock"
FontSize="16"
FontWeight="SemiBold"
Text="{Binding DrawerTitle}" />
<Button Grid.Column="1"
Width="32"
Height="32"
Padding="0"
Background="Transparent"
BorderThickness="0"
Click="OnCloseDrawerClick">
<fi:FluentIcon Icon="Dismiss"
IconVariant="Regular" />
</Button>
</Grid>
<ContentControl x:Name="DrawerContentHost"
Grid.Row="1" />
</Grid>
</Border>
</Grid>
</ui:FANavigationView>
<Border x:Name="WindowTitleBarHost"
Grid.Row="0"
Height="48"
Background="{DynamicResource SolidBackgroundFillColorBaseBrush}"
BorderBrush="{DynamicResource AdaptiveSettingsWindowBorderBrush}"
BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="Auto,*,Auto"
@@ -53,26 +151,11 @@
Margin="8,0,8,0"
Spacing="8"
VerticalAlignment="Center">
<Button x:Name="TogglePaneButton"
Classes="pane-toggle-button"
Margin="-7,-8,-8,-8"
MinWidth="40"
Width="48"
VerticalAlignment="Center"
IsVisible="{Binding !#RootNavigationView.IsPaneToggleButtonVisible}"
Click="OnTogglePaneButtonClick">
<Grid>
<fi:FluentIcon x:Name="TogglePaneButtonIcon"
Icon="Navigation"
IconVariant="Regular"
FontSize="16" />
</Grid>
</Button>
<fi:FluentIcon x:Name="WindowBrandIcon"
Margin="0,0,0,0"
Icon="Settings"
IconVariant="Filled"
FontSize="18"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsHitTestVisible="False"
VerticalAlignment="Center" />
@@ -81,6 +164,7 @@
FontWeight="SemiBold"
Margin="8,0,0,0"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
IsHitTestVisible="False"
Text="{Binding Title}" />
</StackPanel>
@@ -119,80 +203,5 @@
</StackPanel>
</Grid>
</Border>
<ui:FANavigationView x:Name="RootNavigationView"
Grid.Row="1"
Margin="0"
Background="Transparent"
PaneDisplayMode="Auto"
OpenPaneLength="283"
IsSettingsVisible="False"
IsBackButtonVisible="False"
SelectionChanged="OnNavigationSelectionChanged">
<ui:FANavigationView.Styles>
<Style Selector="ui|FANavigationView#RootNavigationView:minimal">
<Setter Property="IsPaneToggleButtonVisible" Value="False"/>
</Style>
</ui:FANavigationView.Styles>
<ui:FANavigationView.Resources>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
</ui:FANavigationView.Resources>
<Grid x:Name="SettingsContentGrid"
ColumnDefinitions="*,Auto"
ColumnSpacing="20"
Margin="12,0,16,16">
<Grid Grid.Column="0"
RowDefinitions="Auto,*">
<Grid x:Name="PageTitleContainer"
Grid.Row="0"
Classes="page-title-container"
IsVisible="{Binding IsPageTitleVisible}">
<TextBlock x:Name="PageTitleTextBlock"
Classes="page-title-text"
Text="{Binding CurrentPageTitle}" />
</Grid>
<ui:FAFrame x:Name="ContentFrame"
Grid.Row="1" />
</Grid>
<Border x:Name="DrawerBorder"
Grid.Column="1"
Width="296"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="16"
Padding="20"
BoxShadow="0 8 24 #14000000"
IsVisible="{Binding IsDrawerOpen}">
<Grid RowDefinitions="Auto,*"
RowSpacing="16">
<Grid ColumnDefinitions="*,Auto">
<TextBlock x:Name="DrawerTitleTextBlock"
FontSize="16"
FontWeight="SemiBold"
Text="{Binding DrawerTitle}" />
<Button Grid.Column="1"
Width="32"
Height="32"
Padding="0"
Background="Transparent"
BorderThickness="0"
Click="OnCloseDrawerClick">
<fi:FluentIcon Icon="Dismiss"
IconVariant="Regular" />
</Button>
</Grid>
<ContentControl x:Name="DrawerContentHost"
Grid.Row="1" />
</Grid>
</Border>
</Grid>
</ui:FANavigationView>
</Grid>
</faWindowing:FAAppWindow>

View File

@@ -87,7 +87,8 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
SyncPendingRestartState();
SyncTitleText();
UpdateChromeMetrics();
UpdatePaneToggleIcon();
UpdatePaneFooterToggleVisibility();
UpdatePaneFooterToggleIcon();
UpdateResponsiveLayout();
RequestResponsiveLayoutRefresh();
}
@@ -104,6 +105,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
CloseDrawer();
RebuildNavigationItems();
NavigateTo(pageId ?? ViewModel.Pages.FirstOrDefault()?.PageId);
UpdatePaneFooterToggleVisibility();
}
public void RebuildAndNavigateToDevPage()
@@ -266,6 +268,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
ViewModel.IsPageTitleVisible = !descriptor.HidePageTitle;
TrySelectNavigationItem(descriptor.PageId);
SyncTitleText();
UpdatePaneFooterToggleVisibility();
UpdateResponsiveLayout();
RequestResponsiveLayoutRefresh();
if (!string.Equals(previousPageId, descriptor.PageId, StringComparison.OrdinalIgnoreCase))
@@ -523,7 +526,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
}
}
private void OnTogglePaneButtonClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
private void OnPaneFooterToggleClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_ = sender;
_ = e;
@@ -533,7 +536,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
}
RootNavigationView.IsPaneOpen = !RootNavigationView.IsPaneOpen;
UpdatePaneToggleIcon();
UpdatePaneFooterToggleIcon();
UpdateResponsiveLayout();
RequestResponsiveLayoutRefresh();
}
@@ -544,13 +547,33 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
if (e.Property == FANavigationView.IsPaneOpenProperty ||
e.Property == FANavigationView.OpenPaneLengthProperty ||
e.Property == FANavigationView.PaneDisplayModeProperty)
e.Property == FANavigationView.PaneDisplayModeProperty ||
e.Property == FANavigationView.IsPaneToggleButtonVisibleProperty)
{
UpdatePaneToggleIcon();
if (e.Property == FANavigationView.IsPaneToggleButtonVisibleProperty)
{
UpdatePaneFooterToggleVisibility();
}
UpdatePaneFooterToggleIcon();
RequestResponsiveLayoutRefresh();
}
}
/// <summary>
/// 仅在 <c>:minimal</c><see cref="FANavigationView.IsPaneToggleButtonVisible"/> 为 false时显示侧栏底部备胎按钮。
/// 根 DataContext 为 ViewModel 时,对 <c>#RootNavigationView</c> 的绑定易失效,故用代码同步可见性。
/// </summary>
private void UpdatePaneFooterToggleVisibility()
{
if (PaneFooterToggleButton is null || RootNavigationView is null)
{
return;
}
PaneFooterToggleButton.IsVisible = !RootNavigationView.IsPaneToggleButtonVisible;
}
private void RequestResponsiveLayoutRefresh()
{
if (_isResponsiveRefreshPending)
@@ -580,14 +603,14 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
: compactPaneWidth;
}
private void UpdatePaneToggleIcon()
private void UpdatePaneFooterToggleIcon()
{
if (TogglePaneButtonIcon is null || RootNavigationView is null)
if (PaneFooterToggleButtonIcon is null || RootNavigationView is null)
{
return;
}
TogglePaneButtonIcon.Icon = RootNavigationView.IsPaneOpen
PaneFooterToggleButtonIcon.Icon = RootNavigationView.IsPaneOpen
? FluentIcons.Common.Icon.LineHorizontal3
: FluentIcons.Common.Icon.Navigation;
}
@@ -604,8 +627,6 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
}
if (WindowTitleBarHost is null ||
TogglePaneButton is null ||
TogglePaneButtonIcon is null ||
WindowBrandIcon is null ||
WindowTitleTextBlock is null ||
RestartNowButton is null ||
@@ -622,8 +643,6 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
var layoutScale = Math.Clamp(Math.Min(width / 1120d, height / 760d), 0.90, 1.18);
const double titleBarHeight = 48d;
var titleBarButtonWidth = Math.Clamp(40d * layoutScale, 36d, 48d);
var titleBarButtonHeight = Math.Clamp(32d * layoutScale, 30d, 38d);
var titleFontSize = Math.Clamp(12d * layoutScale, 11d, 14d);
var titleBarIconSize = Math.Clamp(16d * layoutScale, 15d, 20d);
var drawerTitleFontSize = Math.Clamp(16d * layoutScale, 14d, 20d);
@@ -634,9 +653,6 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext
WindowTitleBarHost.Height = titleBarHeight;
TogglePaneButton.Width = titleBarButtonWidth;
TogglePaneButton.Height = titleBarButtonHeight;
TogglePaneButtonIcon.FontSize = titleBarIconSize;
WindowBrandIcon.FontSize = titleBarIconSize + 2;
WindowTitleTextBlock.FontSize = titleFontSize;

7
NuGet.Config Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<!-- 相对本文件:包落在仓库目录下,与仓库同盘(本机 D:),避免依赖 C:\Temp\cursor-sandbox-cache 等易损路径 -->
<add key="globalPackagesFolder" value=".nuget/packages" />
</config>
</configuration>