diff --git a/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs b/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs index 48a3fa8..5aebe23 100644 --- a/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs +++ b/LanMountainDesktop.Tests/MaterialColorSettingsPageViewModelTests.cs @@ -65,6 +65,22 @@ public sealed class MaterialColorSettingsPageViewModelTests Assert.Equal(1, facade.ThemeSaveCount); } + [Fact] + public void UserSelection_SystemMaterialModeRequestsRestart() + { + var facade = new FakeSettingsFacade(CreateThemeState(ThemeAppearanceValues.MaterialNone)); + var materialService = new FakeMaterialColorService(CreateSnapshot(ThemeAppearanceValues.MaterialNone)); + var viewModel = new MaterialColorSettingsPageViewModel(facade, materialService); + string? restartReason = null; + viewModel.RestartRequested += reason => restartReason = reason; + + viewModel.SelectedSystemMaterialMode = viewModel.SystemMaterialModes.Single(option => + option.Value == ThemeAppearanceValues.MaterialMica); + + Assert.Equal(viewModel.SystemMaterialRestartMessage, restartReason); + Assert.False(string.IsNullOrWhiteSpace(restartReason)); + } + private static ThemeAppearanceSettingsState CreateThemeState(string materialMode) { return new ThemeAppearanceSettingsState( diff --git a/LanMountainDesktop.Tests/SystemChromeModeTests.cs b/LanMountainDesktop.Tests/SystemChromeModeTests.cs new file mode 100644 index 0000000..b89ae86 --- /dev/null +++ b/LanMountainDesktop.Tests/SystemChromeModeTests.cs @@ -0,0 +1,103 @@ +using Xunit; + +namespace LanMountainDesktop.Tests; + +public sealed class SystemChromeModeTests +{ + [Fact] + public void SettingsWindow_SystemChromeUsesNativeDecorations() + { + var source = ReadRepositoryFile("LanMountainDesktop", "Views", "SettingsWindow.axaml.cs"); + var applyChromeMode = ExtractMethodSource(source, "ApplyChromeMode"); + var onLoaded = ExtractMethodSource(source, "OnLoaded"); + + Assert.Contains("_useSystemChrome = useSystemChrome || OperatingSystem.IsMacOS();", applyChromeMode); + Assert.Contains("WindowDecorations = WindowDecorations.Full;", applyChromeMode); + Assert.Contains("ExtendClientAreaToDecorationsHint = !_useSystemChrome;", applyChromeMode); + Assert.Contains("ExtendClientAreaTitleBarHeightHint = _useSystemChrome ? 0d : CustomTitleBarHeight;", applyChromeMode); + Assert.Contains("TitleBar.ExtendsContentIntoTitleBar = !_useSystemChrome;", applyChromeMode); + Assert.Contains("WindowTitleBarHost.IsVisible = false;", applyChromeMode); + Assert.Contains("WindowTitleBarHost.IsVisible = true;", applyChromeMode); + Assert.DoesNotContain("TitleBar.ExtendsContentIntoTitleBar = true;", onLoaded); + } + + [Fact] + public void ComponentEditorWindow_SystemChromeUsesNativeDecorations() + { + var source = ReadRepositoryFile("LanMountainDesktop", "Views", "ComponentEditorWindow.axaml.cs"); + var applyChromeMode = ExtractMethodSource(source, "ApplyChromeMode"); + + Assert.Contains("var preferSystemChrome = useSystemChrome || OperatingSystem.IsMacOS();", applyChromeMode); + Assert.Contains("WindowDecorations = WindowDecorations.Full;", applyChromeMode); + Assert.Contains("ExtendClientAreaToDecorationsHint = false;", applyChromeMode); + Assert.Contains("ExtendClientAreaTitleBarHeightHint = 0d;", applyChromeMode); + Assert.Contains("CustomTitleBarHost.IsVisible = false;", applyChromeMode); + Assert.Contains("WindowDecorations = WindowDecorations.BorderOnly;", applyChromeMode); + Assert.Contains("ExtendClientAreaToDecorationsHint = true;", applyChromeMode); + Assert.Contains("CustomTitleBarHost.IsVisible = true;", applyChromeMode); + } + + [Fact] + public void SavingSystemChromeSynchronizesWindowsPatcherState() + { + var source = ReadRepositoryFile("LanMountainDesktop", "Services", "Settings", "SettingsDomainServices.cs"); + + Assert.Contains("if (OperatingSystem.IsWindows())", source); + Assert.Contains("LanMountainDesktop.Platform.Windows.ChromePatchState.UseSystemChrome = state.UseSystemChrome;", source); + } + + private static string ReadRepositoryFile(params string[] segments) + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory is not null) + { + var candidate = Path.Combine(new[] { directory.FullName }.Concat(segments).ToArray()); + if (File.Exists(candidate)) + { + return File.ReadAllText(candidate); + } + + if (File.Exists(Path.Combine(directory.FullName, "LanMountainDesktop.slnx"))) + { + break; + } + + directory = directory.Parent; + } + + throw new FileNotFoundException($"Could not locate repository file '{Path.Combine(segments)}'."); + } + + private static string ExtractMethodSource(string source, string methodName) + { + var methodIndex = source.IndexOf($"private void {methodName}(", StringComparison.Ordinal); + if (methodIndex < 0) + { + methodIndex = source.IndexOf($"public void {methodName}(", StringComparison.Ordinal); + } + + Assert.True(methodIndex >= 0, $"Could not locate method '{methodName}'."); + + var braceIndex = source.IndexOf('{', methodIndex); + Assert.True(braceIndex >= 0, $"Could not locate method body for '{methodName}'."); + + var depth = 0; + for (var i = braceIndex; i < source.Length; i++) + { + if (source[i] == '{') + { + depth++; + } + else if (source[i] == '}') + { + depth--; + if (depth == 0) + { + return source.Substring(methodIndex, i - methodIndex + 1); + } + } + } + + throw new InvalidOperationException($"Could not extract method '{methodName}'."); + } +} diff --git a/LanMountainDesktop/Localization/en-US.json b/LanMountainDesktop/Localization/en-US.json index a95e5e4..931cdd8 100644 --- a/LanMountainDesktop/Localization/en-US.json +++ b/LanMountainDesktop/Localization/en-US.json @@ -394,8 +394,6 @@ "settings.appearance.theme_color_preview.app": "Currently previewing colors extracted from the app wallpaper.", "settings.appearance.theme_color_preview.system": "Currently previewing colors extracted from the system wallpaper.", "settings.appearance.theme_color_preview.fallback": "No usable wallpaper was found. The app is using a fallback accent.", - "settings.appearance.corner_radius.label": "Global corner radius style", - "settings.appearance.corner_radius.description": "Select a fixed corner radius style inspired by Xiaomi HyperOS.", "component.color_scheme.follow_system": "Follow system color scheme", "component.color_scheme.native": "Use component custom color scheme", "component.settings.color_scheme": "Color Scheme", @@ -406,7 +404,7 @@ "settings.appearance.system_material_desc.switchable": "Apply the selected material to windows, Dock, status bar, and component hosts.", "settings.appearance.system_material_desc.fixed": "Your current system only exposes the material modes listed here.", "settings.appearance.system_material_desc.auto": "Auto prefers Mica on Windows 11, Acrylic on Windows 10, and falls back to no material when unavailable.", - "settings.appearance.restart_message": "Theme source and system material changes require restarting the app.", + "settings.appearance.restart_message": "Window chrome changes require restarting the app.", "settings.appearance.preview.primary": "Primary", "settings.appearance.preview.secondary": "Secondary", "settings.appearance.preview.tertiary": "Tertiary", @@ -442,6 +440,7 @@ "settings.material_color.wallpaper_seed.label": "Seed", "settings.material_color.system_material.label": "System material", "settings.material_color.system_material.description": "Apply the selected material mode to windows and host surfaces.", + "settings.material_color.system_material.restart_message": "System material changes require restarting the app.", "settings.material_color.native_events.label": "Native wallpaper change events", "settings.material_color.native_events.description": "Use OS wallpaper notifications first and keep polling as fallback.", "settings.material_color.native_events.active": "Native wallpaper events active", diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json index 7dced0d..ea0a31f 100644 --- a/LanMountainDesktop/Localization/zh-CN.json +++ b/LanMountainDesktop/Localization/zh-CN.json @@ -394,8 +394,6 @@ "settings.appearance.theme_color_preview.app": "当前正在预览从应用壁纸提取的颜色。", "settings.appearance.theme_color_preview.system": "当前正在预览从系统壁纸提取的颜色。", "settings.appearance.theme_color_preview.fallback": "没有可用壁纸,当前使用回退强调色。", - "settings.appearance.corner_radius.label": "全局圆角样式", - "settings.appearance.corner_radius.description": "选择固定的全局圆角样式,受 HyperOS 启发。", "component.color_scheme.follow_system": "跟随系统配色", "component.color_scheme.native": "使用组件自定义配色", "component.settings.color_scheme": "配色方案", @@ -406,7 +404,7 @@ "settings.appearance.system_material_desc.switchable": "将所选材质应用到窗口、Dock、状态栏和组件宿主背板。", "settings.appearance.system_material_desc.fixed": "当前系统仅提供这里列出的材质模式。", "settings.appearance.system_material_desc.auto": "自动模式会在 Windows 11 优先使用 Mica,在 Windows 10 优先使用 Acrylic,不可用时回退到无材质。", - "settings.appearance.restart_message": "主题色来源和系统材质更改需要重启应用。", + "settings.appearance.restart_message": "窗口边框模式更改需要重启应用。", "settings.appearance.preview.primary": "主色", "settings.appearance.preview.secondary": "次色", "settings.appearance.preview.tertiary": "三次色", @@ -442,6 +440,7 @@ "settings.material_color.wallpaper_seed.label": "种子色", "settings.material_color.system_material.label": "系统材质", "settings.material_color.system_material.description": "将所选材质模式应用到窗口和宿主表面。", + "settings.material_color.system_material.restart_message": "系统材质更改需要重启应用。", "settings.material_color.native_events.label": "原生壁纸变更事件", "settings.material_color.native_events.description": "优先使用操作系统壁纸通知,并保持轮询作为回退。", "settings.material_color.native_events.active": "原生壁纸事件已激活", diff --git a/LanMountainDesktop/Services/PendingRestartStateService.cs b/LanMountainDesktop/Services/PendingRestartStateService.cs index b392d60..9c37fb4 100644 --- a/LanMountainDesktop/Services/PendingRestartStateService.cs +++ b/LanMountainDesktop/Services/PendingRestartStateService.cs @@ -8,6 +8,7 @@ public static class PendingRestartStateService public const string RenderModeReason = "RenderMode"; public const string PluginCatalogReason = "PluginCatalog"; public const string SettingsWindowReason = "SettingsWindow"; + public const string SystemMaterialReason = "SystemMaterial"; private static readonly object Gate = new(); private static readonly HashSet PendingReasons = new(StringComparer.OrdinalIgnoreCase); diff --git a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs index 3ca2a0a..5e2ba6f 100644 --- a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs +++ b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs @@ -315,6 +315,10 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService { snapshot.UseSystemChrome = state.UseSystemChrome; changedKeys.Add(nameof(AppSettingsSnapshot.UseSystemChrome)); + if (OperatingSystem.IsWindows()) + { + LanMountainDesktop.Platform.Windows.ChromePatchState.UseSystemChrome = state.UseSystemChrome; + } } if (!string.Equals(GlobalAppearanceSettings.NormalizeCornerRadiusStyle(snapshot.CornerRadiusStyle), normalizedCornerRadiusStyle, StringComparison.OrdinalIgnoreCase)) diff --git a/LanMountainDesktop/Services/Settings/SettingsWindowService.cs b/LanMountainDesktop/Services/Settings/SettingsWindowService.cs index f18d9cb..24285f2 100644 --- a/LanMountainDesktop/Services/Settings/SettingsWindowService.cs +++ b/LanMountainDesktop/Services/Settings/SettingsWindowService.cs @@ -299,6 +299,7 @@ internal sealed class SettingsWindowService : ISettingsWindowService ? ThemeVariant.Dark : ThemeVariant.Light; appearanceThemeService.ApplyThemeResources(window.Resources); + window.ApplyFluentCornerRadius(); } private void ApplyThemeVariantAndResources(SettingsWindow window) diff --git a/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs b/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs index 2d5dc7e..b4c208a 100644 --- a/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs +++ b/LanMountainDesktop/ViewModels/MaterialColorSettingsPageViewModel.cs @@ -83,6 +83,8 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase _materialColorService.MaterialColorChanged += OnMaterialColorChanged; } + public event Action? RestartRequested; + public IReadOnlyList ColorModes { get; } public IReadOnlyList WallpaperColorSources { get; } @@ -176,6 +178,9 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase [ObservableProperty] private string _systemMaterialDescription = string.Empty; + [ObservableProperty] + private string _systemMaterialRestartMessage = string.Empty; + [ObservableProperty] private string _nativeWallpaperEventsLabel = string.Empty; @@ -313,7 +318,14 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase return; } + var currentMode = ThemeAppearanceValues.NormalizeSystemMaterialMode(_settingsFacade.Theme.Get().SystemMaterialMode); + var requestedMode = ThemeAppearanceValues.NormalizeSystemMaterialMode(value.Value); SaveTheme(); + if (!string.Equals(currentMode, requestedMode, StringComparison.OrdinalIgnoreCase)) + { + PendingRestartStateService.SetPending(PendingRestartStateService.SystemMaterialReason, true); + RestartRequested?.Invoke(SystemMaterialRestartMessage); + } } partial void OnSelectedRefreshIntervalChanged(SelectionOption value) @@ -577,6 +589,9 @@ public sealed partial class MaterialColorSettingsPageViewModel : ViewModelBase WallpaperSeedLabel = L("settings.material_color.wallpaper_seed.label", "Seed"); SystemMaterialLabel = L("settings.material_color.system_material.label", "System material"); SystemMaterialDescription = L("settings.material_color.system_material.description", "Apply the selected material mode to windows and host surfaces."); + SystemMaterialRestartMessage = L( + "settings.material_color.system_material.restart_message", + "System material changes require restarting the app."); NativeWallpaperEventsLabel = L("settings.material_color.native_events.label", "Native wallpaper change events"); NativeWallpaperEventsDescription = L("settings.material_color.native_events.description", "Use OS wallpaper notifications first and keep polling as fallback."); RefreshIntervalLabel = L("settings.material_color.refresh_interval.label", "Polling interval"); diff --git a/LanMountainDesktop/ViewModels/SettingsViewModels.cs b/LanMountainDesktop/ViewModels/SettingsViewModels.cs index e3c5081..c4e0214 100644 --- a/LanMountainDesktop/ViewModels/SettingsViewModels.cs +++ b/LanMountainDesktop/ViewModels/SettingsViewModels.cs @@ -1032,27 +1032,12 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase [ObservableProperty] private string _useSystemChromeLabel = string.Empty; - [ObservableProperty] - private string _cornerRadiusStyleLabel = string.Empty; - - [ObservableProperty] - private string _cornerRadiusStyleDescription = string.Empty; - [ObservableProperty] private string _themeHeader = string.Empty; [ObservableProperty] private string _appearanceRestartMessage = string.Empty; - [ObservableProperty] - private string _cornerRadiusStyle = GlobalAppearanceSettings.DefaultCornerRadiusStyle; - - [ObservableProperty] - private IReadOnlyList _cornerRadiusStyleOptions = []; - - [ObservableProperty] - private SelectionOption? _selectedCornerRadiusStyle; - public void Load() { var theme = _settingsFacade.Theme.Get(); @@ -1101,17 +1086,6 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase PersistCurrentState(restartRequired: true); } - partial void OnSelectedCornerRadiusStyleChanged(SelectionOption? value) - { - if (_isInitializing || value is null) - { - return; - } - - CornerRadiusStyle = value.Value; - PersistCurrentState(restartRequired: false); - } - private void RefreshLocalizedText() { ThemeHeader = L("settings.appearance.theme_header", "Theme"); @@ -1121,25 +1095,15 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase ThemeModeDarkText = L("settings.appearance.theme_mode.dark", "Dark"); ThemeModeFollowSystemText = L("settings.appearance.theme_mode.follow_system", "Follow system"); UseSystemChromeLabel = L("settings.color.use_system_chrome_toggle", "Use system window chrome"); - CornerRadiusStyleLabel = L("settings.appearance.corner_radius.label", "Global corner radius style"); - CornerRadiusStyleDescription = L("settings.appearance.corner_radius.description", "Select a fixed corner radius style inspired by Xiaomi HyperOS."); AppearanceRestartMessage = L( "settings.appearance.restart_message", "Window chrome changes require restarting the app."); - - CornerRadiusStyleOptions = GlobalAppearanceSettings.AllCornerRadiusStyles - .Select(style => new SelectionOption(style, L($"settings.appearance.corner_radius.style_{style.ToLower()}", style))) - .ToList(); } private void ApplySavedState(ThemeAppearanceSettingsState theme) { IsNightMode = theme.IsNightMode; UseSystemChrome = theme.UseSystemChrome; - CornerRadiusStyle = GlobalAppearanceSettings.NormalizeCornerRadiusStyle(theme.CornerRadiusStyle); - SelectedCornerRadiusStyle = CornerRadiusStyleOptions.FirstOrDefault(option => - string.Equals(option.Value, CornerRadiusStyle, StringComparison.OrdinalIgnoreCase)) - ?? CornerRadiusStyleOptions.FirstOrDefault(o => o.Value == GlobalAppearanceSettings.DefaultCornerRadiusStyle); var savedThemeMode = NormalizeThemeMode(theme.ThemeMode); SelectedThemeMode = ThemeModeOptions.FirstOrDefault(option => @@ -1201,7 +1165,6 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase { IsNightMode = IsNightMode, UseSystemChrome = UseSystemChrome, - CornerRadiusStyle = GlobalAppearanceSettings.NormalizeCornerRadiusStyle(CornerRadiusStyle), ThemeMode = SelectedThemeMode?.Value ?? ThemeAppearanceValues.ThemeModeLight }; } diff --git a/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs b/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs index 5e8d6f1..f2f064a 100644 --- a/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs +++ b/LanMountainDesktop/Views/ComponentEditorWindow.axaml.cs @@ -59,14 +59,16 @@ public partial class ComponentEditorWindow : Window var preferSystemChrome = useSystemChrome || OperatingSystem.IsMacOS(); if (preferSystemChrome) { - ExtendClientAreaToDecorationsHint = true; WindowDecorations = WindowDecorations.Full; + ExtendClientAreaToDecorationsHint = false; + ExtendClientAreaTitleBarHeightHint = 0d; CustomTitleBarHost.IsVisible = false; return; } WindowDecorations = WindowDecorations.BorderOnly; ExtendClientAreaToDecorationsHint = true; + ExtendClientAreaTitleBarHeightHint = 0d; CustomTitleBarHost.IsVisible = true; } diff --git a/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml index 4a32da1..ca069e5 100644 --- a/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/AppearanceSettingsPage.axaml @@ -38,23 +38,6 @@ - - - - - - - - - - - - - - diff --git a/LanMountainDesktop/Views/SettingsPages/MaterialColorSettingsPage.axaml.cs b/LanMountainDesktop/Views/SettingsPages/MaterialColorSettingsPage.axaml.cs index e9799ba..8c15619 100644 --- a/LanMountainDesktop/Views/SettingsPages/MaterialColorSettingsPage.axaml.cs +++ b/LanMountainDesktop/Views/SettingsPages/MaterialColorSettingsPage.axaml.cs @@ -27,12 +27,18 @@ public partial class MaterialColorSettingsPage : SettingsPageBase public MaterialColorSettingsPage(MaterialColorSettingsPageViewModel viewModel) { ViewModel = viewModel; + ViewModel.RestartRequested += OnRestartRequested; DataContext = ViewModel; InitializeComponent(); } public MaterialColorSettingsPageViewModel ViewModel { get; } + private void OnRestartRequested(string reason) + { + RequestRestart(reason); + } + private void OnWallpaperSeedCandidateClick(object? sender, RoutedEventArgs e) { _ = e; diff --git a/LanMountainDesktop/Views/SettingsWindow.axaml.cs b/LanMountainDesktop/Views/SettingsWindow.axaml.cs index d8cb3cd..b5e59d3 100644 --- a/LanMountainDesktop/Views/SettingsWindow.axaml.cs +++ b/LanMountainDesktop/Views/SettingsWindow.axaml.cs @@ -15,6 +15,7 @@ using LanMountainDesktop.PluginSdk; using LanMountainDesktop.Services; using LanMountainDesktop.Services.Settings; using LanMountainDesktop.Settings.Core; +using LanMountainDesktop.Shared.Contracts; using LanMountainDesktop.ViewModels; using Symbol = FluentIcons.Common.Symbol; @@ -33,6 +34,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext private const double MinPaneOpenLength = 260d; private const double MaxPaneOpenLength = 288d; private const double BaseNarrowThreshold = 800d; + private const double CustomTitleBarHeight = 48d; private const string PaneToggleItemTag = "__pane_toggle__"; private readonly ISettingsPageRegistry _pageRegistry; @@ -89,14 +91,14 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext private void OnLoaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { - TitleBar.Height = 48; - TitleBar.ExtendsContentIntoTitleBar = true; + TitleBar.Height = CustomTitleBarHeight; - // SecRandom MainWindow:标题栏按钮悬停/按下/非活动色,与系统 caption 更一致 + // Match the native caption button feedback used by SecRandom MainWindow. TitleBar.ButtonHoverBackgroundColor = Color.FromArgb(23, 0, 0, 0); TitleBar.ButtonPressedBackgroundColor = Color.FromArgb(52, 0, 0, 0); TitleBar.ButtonInactiveForegroundColor = Colors.Gray; + ApplyChromeMode(_useSystemChrome); SyncPendingRestartState(); SyncTitleText(); UpdateChromeMetrics(); @@ -186,8 +188,11 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext { _useSystemChrome = useSystemChrome || OperatingSystem.IsMacOS(); - ExtendClientAreaToDecorationsHint = true; WindowDecorations = WindowDecorations.Full; + ExtendClientAreaToDecorationsHint = !_useSystemChrome; + ExtendClientAreaTitleBarHeightHint = _useSystemChrome ? 0d : CustomTitleBarHeight; + TitleBar.ExtendsContentIntoTitleBar = !_useSystemChrome; + TitleBar.Height = CustomTitleBarHeight; if (_useSystemChrome) { @@ -564,6 +569,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext CloseButtonText = ViewModel.RestartDialogCloseText, DefaultButton = FAContentDialogButton.Primary }; + ApplyFluentCornerRadius(dialog.Resources); var result = await dialog.ShowAsync(this); if (result == FAContentDialogResult.Primary) @@ -802,27 +808,40 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext } /// - /// Override global corner radius tokens on the settings window root grid + /// Override inherited corner radius tokens on the settings window root grid /// so all child controls use Microsoft Fluent Design System values, - /// independent of the user's global corner radius preference. + /// independent of the user's component corner radius preference. /// - private void ApplyFluentCornerRadius() + public void ApplyFluentCornerRadius() { - if (RootGrid is null) - { - return; - } - var tokens = AppearanceCornerRadiusTokenFactory.Create( GlobalAppearanceSettings.CornerRadiusStyleFluent); - RootGrid.Resources["DesignCornerRadiusMicro"] = tokens.Micro; - RootGrid.Resources["DesignCornerRadiusXs"] = tokens.Xs; - RootGrid.Resources["DesignCornerRadiusSm"] = tokens.Sm; - RootGrid.Resources["DesignCornerRadiusMd"] = tokens.Md; - RootGrid.Resources["DesignCornerRadiusLg"] = tokens.Lg; - RootGrid.Resources["DesignCornerRadiusXl"] = tokens.Xl; - RootGrid.Resources["DesignCornerRadiusIsland"] = tokens.Island; - RootGrid.Resources["DesignCornerRadiusComponent"] = tokens.Component; + ApplyFluentCornerRadius(Resources, tokens); + if (RootGrid is not null) + { + ApplyFluentCornerRadius(RootGrid.Resources, tokens); + } + } + + private static void ApplyFluentCornerRadius(IResourceDictionary resources) + { + var tokens = AppearanceCornerRadiusTokenFactory.Create( + GlobalAppearanceSettings.CornerRadiusStyleFluent); + ApplyFluentCornerRadius(resources, tokens); + } + + private static void ApplyFluentCornerRadius( + IResourceDictionary resources, + AppearanceCornerRadiusTokens tokens) + { + resources["DesignCornerRadiusMicro"] = tokens.Micro; + resources["DesignCornerRadiusXs"] = tokens.Xs; + resources["DesignCornerRadiusSm"] = tokens.Sm; + resources["DesignCornerRadiusMd"] = tokens.Md; + resources["DesignCornerRadiusLg"] = tokens.Lg; + resources["DesignCornerRadiusXl"] = tokens.Xl; + resources["DesignCornerRadiusIsland"] = tokens.Island; + resources["DesignCornerRadiusComponent"] = tokens.Component; } private void OnTitleBarDragZonePointerPressed(object? sender, PointerPressedEventArgs e) @@ -973,7 +992,7 @@ public partial class SettingsWindow : FAAppWindow, ISettingsPageHostContext var height = Bounds.Height > 1 ? Bounds.Height : Math.Max(Height, MinHeight); var layoutScale = Math.Clamp(Math.Min(width / 1120d, height / 760d), 0.90, 1.18); - const double titleBarHeight = 48d; + const double titleBarHeight = CustomTitleBarHeight; var titleFontSize = Math.Clamp(12d * layoutScale, 11d, 14d); var titleBarIconSize = Math.Clamp(16d * layoutScale, 15d, 20d); var drawerTitleFontSize = Math.Clamp(16d * layoutScale, 14d, 20d); diff --git a/ago --name-only --oneline b/ago --name-only --oneline index 18ab95a..cc617f8 100644 --- a/ago --name-only --oneline +++ b/ago --name-only --oneline @@ -107,3 +107,327 @@ MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + ESC-j * Forward one file line (or _N file lines). + ESC-k * Backward one file line (or _N file lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + ESC-b * Backward one window, but don't stop at beginning-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ^O^N ^On * Search forward for (_N-th) OSC8 hyperlink. + ^O^P ^Op * Search backward for (_N-th) OSC8 hyperlink. + ^O^L ^Ol Jump to the currently selected OSC8 hyperlink. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + Search is case-sensitive unless changed with -i or -I. + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^S _n Search for match in _n-th parenthesized subpattern. + ^W WRAP search if no match found. + ^L Enter next character literally into pattern. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-m_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + ^O^O Open the currently selected OSC8 hyperlink. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + #_c_o_m_m_a_n_d Execute the shell command, expanded like a prompt. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k _f_i_l_e ... --lesskey-file=_f_i_l_e + Use a compiled lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n ......... --line-numbers + Suppress line numbers in prompts and messages. + -N ......... --LINE-NUMBERS + Display line number at start of each line. + -o [_f_i_l_e] .. --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] .. --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p _p_a_t_t_e_r_n . --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t _t_a_g .... --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces, tabs and carriage returns. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + + --exit-follow-on-close + Exit F command on a pipe when writer closes pipe. + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --form-feed + Stop scrolling when a form feed character is reached. + --header=[_L[,_C[,_N]]] + Use _L lines (starting at line _N) and _C columns as headers. + --incsearch + Search file as each pattern character is typed in. + --intr=[_C] + Use _C instead of ^X to interrupt a read. + --lesskey-context=_t_e_x_t + Use lesskey source file contents. + --lesskey-src=_f_i_l_e + Use a lesskey source file. + --line-num-width=[_N] + Set the width of the -N line number field to _N characters. + --match-shift=[_N] + Show at least _N characters to the left of a search match. + --modelines=[_N] + Read _N lines from the input file and look for vim modelines. + --mouse + Enable mouse input. + --no-edit-warn + Don't warn when using v command on a file opened via LESSOPEN. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --no-number-headers + Don't give line numbers to header lines. + --no-paste + Ignore pasted input. + --no-search-header-lines + Searches do not include header lines. + --no-search-header-columns + Searches do not include header columns. + --no-search-headers + Searches do not include header lines or columns. + --no-vbell + Disable the terminal's visual bell. + --redraw-on-quit + Redraw final screen when quitting. + --rscroll=[_C] + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --search-options=[EFKNRW-] + Set default options for every search. + --show-preproc-errors + Display a message if preprocessor exits with an error status. + --proc-backspace + Process backspaces for bold/underline. + --PROC-BACKSPACE + Treat backspaces as control characters. + --proc-return + Delete carriage returns before newline. + --PROC-RETURN + Treat carriage returns as control characters. + --proc-tab + Expand tabs to spaces. + --PROC-TAB + Treat tabs as control characters. + --status-col-width=[_N] + Set the width of the -J status column to _N characters. + --status-line + Highlight or color the entire line containing a mark. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=[_N] + Each click of the mouse wheel moves _N lines. + --wordwrap + Wrap lines at spaces. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. diff --git a/ago --stat --format=short b/ago --stat --format=short index 5a89a77..18ab95a 100644 --- a/ago --stat --format=short +++ b/ago --stat --format=short @@ -42,3 +42,68 @@ ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. n * Repeat previous search (for _N-th occurrence). N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ^O^N ^On * Search forward for (_N-th) OSC8 hyperlink. + ^O^P ^Op * Search backward for (_N-th) OSC8 hyperlink. + ^O^L ^Ol Jump to the currently selected OSC8 hyperlink. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + Search is case-sensitive unless changed with -i or -I. + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^S _n Search for match in _n-th parenthesized subpattern. + ^W WRAP search if no match found. + ^L Enter next character literally into pattern. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-m_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + ^O^O Open the currently selected OSC8 hyperlink. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS +