diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..327b321
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+ 1.0.0
+ net10.0
+ enable
+ enable
+
+
diff --git a/LanMountainDesktop.Appearance/AppearanceCornerRadiusTokenFactory.cs b/LanMountainDesktop.Appearance/AppearanceCornerRadiusTokenFactory.cs
new file mode 100644
index 0000000..a0a758b
--- /dev/null
+++ b/LanMountainDesktop.Appearance/AppearanceCornerRadiusTokenFactory.cs
@@ -0,0 +1,27 @@
+using Avalonia;
+using LanMountainDesktop.Settings.Core;
+using LanMountainDesktop.Shared.Contracts;
+
+namespace LanMountainDesktop.Appearance;
+
+public static class AppearanceCornerRadiusTokenFactory
+{
+ public static AppearanceCornerRadiusTokens Create(double scale)
+ {
+ var normalizedScale = GlobalAppearanceSettings.NormalizeCornerRadiusScale(scale);
+ return new AppearanceCornerRadiusTokens(
+ Radius(6, normalizedScale),
+ Radius(10, normalizedScale),
+ Radius(14, normalizedScale),
+ Radius(18, normalizedScale),
+ Radius(24, normalizedScale),
+ Radius(30, normalizedScale),
+ Radius(36, normalizedScale));
+ }
+
+ private static CornerRadius Radius(double value, double scale)
+ {
+ var scaled = Math.Round(value * scale * 2, MidpointRounding.AwayFromZero) / 2d;
+ return new CornerRadius(scaled);
+ }
+}
diff --git a/LanMountainDesktop.Appearance/LanMountainDesktop.Appearance.csproj b/LanMountainDesktop.Appearance/LanMountainDesktop.Appearance.csproj
new file mode 100644
index 0000000..e202a92
--- /dev/null
+++ b/LanMountainDesktop.Appearance/LanMountainDesktop.Appearance.csproj
@@ -0,0 +1,13 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop.DesktopComponents.Runtime/LanMountainDesktop.DesktopComponents.Runtime.csproj b/LanMountainDesktop.DesktopComponents.Runtime/LanMountainDesktop.DesktopComponents.Runtime.csproj
new file mode 100644
index 0000000..14d25b6
--- /dev/null
+++ b/LanMountainDesktop.DesktopComponents.Runtime/LanMountainDesktop.DesktopComponents.Runtime.csproj
@@ -0,0 +1,14 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop.DesktopHost/DesktopBootstrap.cs b/LanMountainDesktop.DesktopHost/DesktopBootstrap.cs
new file mode 100644
index 0000000..6d2e2c3
--- /dev/null
+++ b/LanMountainDesktop.DesktopHost/DesktopBootstrap.cs
@@ -0,0 +1,27 @@
+using System;
+using Avalonia;
+
+namespace LanMountainDesktop.DesktopHost;
+
+public static class DesktopBootstrap
+{
+ public static void InitializeStartupServices(Action initializeDeviceId, Action initializeCrashReporting, Action initializeUserBehaviorAnalytics, Action scheduleStartupCleanup)
+ {
+ ArgumentNullException.ThrowIfNull(initializeDeviceId);
+ ArgumentNullException.ThrowIfNull(initializeCrashReporting);
+ ArgumentNullException.ThrowIfNull(initializeUserBehaviorAnalytics);
+ ArgumentNullException.ThrowIfNull(scheduleStartupCleanup);
+
+ initializeDeviceId();
+ initializeCrashReporting();
+ initializeUserBehaviorAnalytics();
+ scheduleStartupCleanup();
+ }
+
+ public static void InitializeApplication(Application application, Action initializeShell)
+ {
+ ArgumentNullException.ThrowIfNull(application);
+ ArgumentNullException.ThrowIfNull(initializeShell);
+ initializeShell();
+ }
+}
diff --git a/LanMountainDesktop.DesktopHost/DesktopShellHost.cs b/LanMountainDesktop.DesktopHost/DesktopShellHost.cs
new file mode 100644
index 0000000..2dc756d
--- /dev/null
+++ b/LanMountainDesktop.DesktopHost/DesktopShellHost.cs
@@ -0,0 +1,55 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using LanMountainDesktop.Host.Abstractions;
+
+namespace LanMountainDesktop.DesktopHost;
+
+public sealed class DesktopShellHost : IDesktopShellHost
+{
+ private readonly Action _initializePluginRuntime;
+ private readonly Action _initializeTrayIcon;
+ private readonly Action _createAndAssignMainWindow;
+ private readonly Action _performExitCleanup;
+ private readonly Action _startActivationListener;
+ private readonly Action _startWeatherRefresh;
+
+ public DesktopShellHost(
+ Action initializePluginRuntime,
+ Action initializeTrayIcon,
+ Action createAndAssignMainWindow,
+ Action performExitCleanup,
+ Action startActivationListener,
+ Action startWeatherRefresh)
+ {
+ _initializePluginRuntime = initializePluginRuntime;
+ _initializeTrayIcon = initializeTrayIcon;
+ _createAndAssignMainWindow = createAndAssignMainWindow;
+ _performExitCleanup = performExitCleanup;
+ _startActivationListener = startActivationListener;
+ _startWeatherRefresh = startWeatherRefresh;
+ }
+
+ public void Initialize()
+ {
+ throw new InvalidOperationException("An application instance is required to initialize the desktop shell.");
+ }
+
+ public void Initialize(Application application)
+ {
+ ArgumentNullException.ThrowIfNull(application);
+
+ _initializePluginRuntime();
+ _initializeTrayIcon();
+
+ if (application.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.Exit += (_, _) => _performExitCleanup();
+ _createAndAssignMainWindow(desktop);
+ _startActivationListener();
+ }
+
+ _startWeatherRefresh();
+ }
+}
diff --git a/LanMountainDesktop.DesktopHost/DesktopStartupCoordinator.cs b/LanMountainDesktop.DesktopHost/DesktopStartupCoordinator.cs
new file mode 100644
index 0000000..efc42dc
--- /dev/null
+++ b/LanMountainDesktop.DesktopHost/DesktopStartupCoordinator.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace LanMountainDesktop.DesktopHost;
+
+public sealed class DesktopStartupCoordinator
+{
+ private readonly Action _restoreWorkspaceState;
+
+ public DesktopStartupCoordinator(Action restoreWorkspaceState)
+ {
+ _restoreWorkspaceState = restoreWorkspaceState ?? throw new ArgumentNullException(nameof(restoreWorkspaceState));
+ }
+
+ public void Restore() => _restoreWorkspaceState();
+}
diff --git a/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj b/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj
new file mode 100644
index 0000000..acb8b49
--- /dev/null
+++ b/LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj
@@ -0,0 +1,18 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LanMountainDesktop.DesktopHost/SettingsWindowHost.cs b/LanMountainDesktop.DesktopHost/SettingsWindowHost.cs
new file mode 100644
index 0000000..e4c2200
--- /dev/null
+++ b/LanMountainDesktop.DesktopHost/SettingsWindowHost.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace LanMountainDesktop.DesktopHost;
+
+public sealed class SettingsWindowHost
+{
+ private readonly Action _openSettingsWindow;
+
+ public SettingsWindowHost(Action openSettingsWindow)
+ {
+ _openSettingsWindow = openSettingsWindow ?? throw new ArgumentNullException(nameof(openSettingsWindow));
+ }
+
+ public void Open(string source, string? pageId = null)
+ {
+ _openSettingsWindow(source, pageId);
+ }
+}
diff --git a/LanMountainDesktop.DesktopHost/ShutdownCoordinator.cs b/LanMountainDesktop.DesktopHost/ShutdownCoordinator.cs
new file mode 100644
index 0000000..a7826ec
--- /dev/null
+++ b/LanMountainDesktop.DesktopHost/ShutdownCoordinator.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace LanMountainDesktop.DesktopHost;
+
+public sealed class ShutdownCoordinator
+{
+ private readonly Action _prepareForShutdown;
+ private readonly Action _resetShutdownIntent;
+
+ public ShutdownCoordinator(Action prepareForShutdown, Action resetShutdownIntent)
+ {
+ _prepareForShutdown = prepareForShutdown ?? throw new ArgumentNullException(nameof(prepareForShutdown));
+ _resetShutdownIntent = resetShutdownIntent ?? throw new ArgumentNullException(nameof(resetShutdownIntent));
+ }
+
+ public void Prepare(bool isRestart, string source) => _prepareForShutdown(isRestart, source);
+
+ public void Reset(string source) => _resetShutdownIntent(source);
+}
diff --git a/LanMountainDesktop.Host.Abstractions/ComponentChromeContext.cs b/LanMountainDesktop.Host.Abstractions/ComponentChromeContext.cs
new file mode 100644
index 0000000..110a0bd
--- /dev/null
+++ b/LanMountainDesktop.Host.Abstractions/ComponentChromeContext.cs
@@ -0,0 +1,12 @@
+using LanMountainDesktop.PluginSdk;
+using LanMountainDesktop.Shared.Contracts;
+
+namespace LanMountainDesktop.Host.Abstractions;
+
+public sealed record ComponentChromeContext(
+ string ComponentId,
+ string? PlacementId,
+ double CellSize,
+ double GlobalCornerRadiusScale,
+ AppearanceCornerRadiusTokens CornerRadiusTokens,
+ SettingsScope Scope = SettingsScope.App);
diff --git a/LanMountainDesktop.Host.Abstractions/IDesktopShellHost.cs b/LanMountainDesktop.Host.Abstractions/IDesktopShellHost.cs
new file mode 100644
index 0000000..cf5743a
--- /dev/null
+++ b/LanMountainDesktop.Host.Abstractions/IDesktopShellHost.cs
@@ -0,0 +1,6 @@
+namespace LanMountainDesktop.Host.Abstractions;
+
+public interface IDesktopShellHost
+{
+ void Initialize();
+}
diff --git a/LanMountainDesktop.Host.Abstractions/LanMountainDesktop.Host.Abstractions.csproj b/LanMountainDesktop.Host.Abstractions/LanMountainDesktop.Host.Abstractions.csproj
new file mode 100644
index 0000000..d88590a
--- /dev/null
+++ b/LanMountainDesktop.Host.Abstractions/LanMountainDesktop.Host.Abstractions.csproj
@@ -0,0 +1,10 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
diff --git a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj
index c7dcfce..d3dc722 100644
--- a/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj
+++ b/LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj
@@ -12,6 +12,7 @@
+
diff --git a/LanMountainDesktop.PluginSdk/PluginDesktopComponentContext.cs b/LanMountainDesktop.PluginSdk/PluginDesktopComponentContext.cs
index 4181e66..26010eb 100644
--- a/LanMountainDesktop.PluginSdk/PluginDesktopComponentContext.cs
+++ b/LanMountainDesktop.PluginSdk/PluginDesktopComponentContext.cs
@@ -1,3 +1,5 @@
+using LanMountainDesktop.Shared.Contracts;
+
namespace LanMountainDesktop.PluginSdk;
public sealed class PluginDesktopComponentContext
@@ -11,6 +13,8 @@ public sealed class PluginDesktopComponentContext
string componentId,
string? placementId,
double cellSize,
+ double globalCornerRadiusScale,
+ AppearanceCornerRadiusTokens cornerRadiusTokens,
IPluginSettingsService? pluginSettings = null)
{
ArgumentNullException.ThrowIfNull(manifest);
@@ -19,6 +23,7 @@ public sealed class PluginDesktopComponentContext
ArgumentException.ThrowIfNullOrWhiteSpace(componentId);
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(properties);
+ ArgumentNullException.ThrowIfNull(cornerRadiusTokens);
Manifest = manifest;
PluginDirectory = pluginDirectory;
@@ -28,6 +33,8 @@ public sealed class PluginDesktopComponentContext
ComponentId = componentId.Trim();
PlacementId = string.IsNullOrWhiteSpace(placementId) ? null : placementId.Trim();
CellSize = Math.Max(1, cellSize);
+ GlobalCornerRadiusScale = Math.Max(0.1d, globalCornerRadiusScale);
+ CornerRadiusTokens = cornerRadiusTokens;
PluginSettings = pluginSettings;
}
@@ -47,8 +54,22 @@ public sealed class PluginDesktopComponentContext
public double CellSize { get; }
+ public double GlobalCornerRadiusScale { get; }
+
+ public AppearanceCornerRadiusTokens CornerRadiusTokens { get; }
+
public IPluginSettingsService? PluginSettings { get; }
+ public double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null)
+ {
+ var scaled = Math.Max(0d, baseRadius) * GlobalCornerRadiusScale;
+ var scaledMin = minimum.HasValue ? minimum.Value * GlobalCornerRadiusScale : scaled;
+ var scaledMax = maximum.HasValue ? maximum.Value * GlobalCornerRadiusScale : scaled;
+ return minimum.HasValue || maximum.HasValue
+ ? Math.Clamp(scaled, scaledMin, scaledMax)
+ : scaled;
+ }
+
public T? GetService()
{
return (T?)Services.GetService(typeof(T));
diff --git a/LanMountainDesktop.Settings.Core/GlobalAppearanceSettings.cs b/LanMountainDesktop.Settings.Core/GlobalAppearanceSettings.cs
new file mode 100644
index 0000000..185312d
--- /dev/null
+++ b/LanMountainDesktop.Settings.Core/GlobalAppearanceSettings.cs
@@ -0,0 +1,20 @@
+namespace LanMountainDesktop.Settings.Core;
+
+public static class GlobalAppearanceSettings
+{
+ public const double DefaultCornerRadiusScale = 1.0;
+ public const double MinimumCornerRadiusScale = 0.70;
+ public const double MaximumCornerRadiusScale = 1.40;
+ public const double CornerRadiusScaleStep = 0.05;
+
+ public static double NormalizeCornerRadiusScale(double value)
+ {
+ if (double.IsNaN(value) || double.IsInfinity(value))
+ {
+ return DefaultCornerRadiusScale;
+ }
+
+ var clamped = Math.Clamp(value, MinimumCornerRadiusScale, MaximumCornerRadiusScale);
+ return Math.Round(clamped / CornerRadiusScaleStep, MidpointRounding.AwayFromZero) * CornerRadiusScaleStep;
+ }
+}
diff --git a/LanMountainDesktop.Settings.Core/LanMountainDesktop.Settings.Core.csproj b/LanMountainDesktop.Settings.Core/LanMountainDesktop.Settings.Core.csproj
new file mode 100644
index 0000000..05a9866
--- /dev/null
+++ b/LanMountainDesktop.Settings.Core/LanMountainDesktop.Settings.Core.csproj
@@ -0,0 +1,11 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
diff --git a/LanMountainDesktop.Shared.Contracts/AppearanceCornerRadiusTokens.cs b/LanMountainDesktop.Shared.Contracts/AppearanceCornerRadiusTokens.cs
new file mode 100644
index 0000000..6f33088
--- /dev/null
+++ b/LanMountainDesktop.Shared.Contracts/AppearanceCornerRadiusTokens.cs
@@ -0,0 +1,12 @@
+using Avalonia;
+
+namespace LanMountainDesktop.Shared.Contracts;
+
+public sealed record AppearanceCornerRadiusTokens(
+ CornerRadius Micro,
+ CornerRadius Xs,
+ CornerRadius Sm,
+ CornerRadius Md,
+ CornerRadius Lg,
+ CornerRadius Xl,
+ CornerRadius Island);
diff --git a/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj
new file mode 100644
index 0000000..d23bea9
--- /dev/null
+++ b/LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj
@@ -0,0 +1,10 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
diff --git a/LanMountainDesktop.slnx b/LanMountainDesktop.slnx
index 20053cf..77087f6 100644
--- a/LanMountainDesktop.slnx
+++ b/LanMountainDesktop.slnx
@@ -1,5 +1,11 @@
+
+
+
+
+
+
diff --git a/LanMountainDesktop/App.axaml.cs b/LanMountainDesktop/App.axaml.cs
index 4f3e62d..0a8076f 100644
--- a/LanMountainDesktop/App.axaml.cs
+++ b/LanMountainDesktop/App.axaml.cs
@@ -15,6 +15,7 @@ using Avalonia.Styling;
using Avalonia.Threading;
using AvaloniaWebView;
using LanMountainDesktop.ComponentSystem;
+using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
@@ -61,6 +62,7 @@ public partial class App : Application
private MainWindow? _mainWindow;
private bool _mainWindowClosed;
private bool _uiUnhandledExceptionHooked;
+ private DesktopShellHost? _desktopShellHost;
internal static SingleInstanceService? CurrentSingleInstanceService { get; set; }
internal static (UserBehaviorAnalyticsService?, CrashReportService?) AnalyticsServices { get; set; }
@@ -116,28 +118,32 @@ public partial class App : Application
AppLogger.Info("App", "Framework initialization completed.");
RegisterUiUnhandledExceptionGuard();
LinuxDesktopEntryInstaller.EnsureInstalled();
- InitializePluginRuntime();
- InitializeTrayIcon();
+ DesktopBootstrap.InitializeApplication(this, InitializeDesktopShell);
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- {
- // Avoid duplicate validations from both Avalonia and the CommunityToolkit.
- // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
- DisableAvaloniaDataAnnotationValidation();
- desktop.ShutdownMode = Avalonia.Controls.ShutdownMode.OnExplicitShutdown;
- desktop.Exit += (_, _) =>
+ base.OnFrameworkInitializationCompleted();
+ }
+
+ private void InitializeDesktopShell()
+ {
+ _desktopShellHost ??= new DesktopShellHost(
+ InitializePluginRuntime,
+ InitializeTrayIcon,
+ desktop =>
+ {
+ // Avoid duplicate validations from both Avalonia and the CommunityToolkit.
+ // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
+ DisableAvaloniaDataAnnotationValidation();
+ desktop.ShutdownMode = Avalonia.Controls.ShutdownMode.OnExplicitShutdown;
+ CreateAndAssignMainWindow(desktop, "FrameworkInitialization");
+ },
+ () =>
{
AppLogger.Info("App", "Desktop lifetime exit triggered.");
PerformExitCleanup();
- };
-
- CreateAndAssignMainWindow(desktop, "FrameworkInitialization");
- CurrentSingleInstanceService?.StartActivationListener(ActivateMainWindow);
- }
-
- StartWeatherLocationRefreshIfNeeded();
-
- base.OnFrameworkInitializationCompleted();
+ },
+ () => CurrentSingleInstanceService?.StartActivationListener(ActivateMainWindow),
+ StartWeatherLocationRefreshIfNeeded);
+ _desktopShellHost.Initialize(this);
}
private void OnTrayExitClick(object? sender, EventArgs e)
@@ -493,6 +499,7 @@ public partial class App : Application
refreshAll ||
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase) ||
+ changedKeys.Contains(nameof(AppSettingsSnapshot.GlobalCornerRadiusScale), 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) &&
diff --git a/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs b/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs
index a05fabe..168bde4 100644
--- a/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs
+++ b/LanMountainDesktop/ComponentSystem/DesktopComponentRuntimeContext.cs
@@ -1,3 +1,4 @@
+using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -10,5 +11,6 @@ public sealed record DesktopComponentRuntimeContext(
ISettingsFacadeService SettingsFacade,
ISettingsService SettingsService,
IAppearanceThemeService AppearanceTheme,
+ ComponentChromeContext Chrome,
IComponentSettingsAccessor ComponentSettingsAccessor,
IComponentInstanceSettingsStore ComponentSettingsStore);
diff --git a/LanMountainDesktop/ComponentSystem/IComponentChromeContextAware.cs b/LanMountainDesktop/ComponentSystem/IComponentChromeContextAware.cs
new file mode 100644
index 0000000..4f3ef7c
--- /dev/null
+++ b/LanMountainDesktop/ComponentSystem/IComponentChromeContextAware.cs
@@ -0,0 +1,8 @@
+using LanMountainDesktop.Host.Abstractions;
+
+namespace LanMountainDesktop.ComponentSystem;
+
+public interface IComponentChromeContextAware
+{
+ void SetComponentChromeContext(ComponentChromeContext context);
+}
diff --git a/LanMountainDesktop/LanMountainDesktop.csproj b/LanMountainDesktop/LanMountainDesktop.csproj
index 06e054d..f677fb0 100644
--- a/LanMountainDesktop/LanMountainDesktop.csproj
+++ b/LanMountainDesktop/LanMountainDesktop.csproj
@@ -29,6 +29,12 @@
+
+
+
+
+
+
diff --git a/LanMountainDesktop/Localization/en-US.json b/LanMountainDesktop/Localization/en-US.json
index e87ef9c..556be17 100644
--- a/LanMountainDesktop/Localization/en-US.json
+++ b/LanMountainDesktop/Localization/en-US.json
@@ -25,7 +25,7 @@
"settings.nav.group_system": "System",
"settings.nav.group_extensions": "Extensions",
"settings.nav.wallpaper": "Wallpaper",
- "settings.nav.grid": "Grid",
+ "settings.nav.grid": "Components",
"settings.nav.color": "Color",
"settings.nav.status_bar": "Status Bar",
"settings.nav.weather": "Weather",
@@ -303,8 +303,17 @@
"settings.status_bar.clock_format.hm": "Hour:Minute",
"settings.status_bar.clock_format.hms": "Hour:Minute:Second",
"settings.components.title": "Components",
- "settings.components.description": "Adjust desktop grid density and widget placement.",
- "settings.components.grid_header": "Grid Layout",
+ "settings.components.description": "Adjust component layout and corner design.",
+ "settings.components.grid_header": "Grid Settings",
+ "settings.components.header": "Grid Settings",
+ "settings.components.short_side_label": "Short Side Cells",
+ "settings.components.edge_inset_label": "Screen Inset",
+ "settings.components.spacing_label": "Component Spacing",
+ "settings.components.spacing_compact": "Compact",
+ "settings.components.spacing_relaxed": "Relaxed",
+ "settings.components.corner_radius.header": "Corner Design",
+ "settings.components.corner_radius.label": "Component Corner Radius",
+ "settings.components.corner_radius.description": "Adjust the shared corner radius used by component containers, and expand the internal safe area with it.",
"settings.update.title": "Update",
"settings.update.current_version_label": "Current Version",
"settings.update.latest_version_label": "Latest Release",
diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json
index 82bacad..bf5fdf0 100644
--- a/LanMountainDesktop/Localization/zh-CN.json
+++ b/LanMountainDesktop/Localization/zh-CN.json
@@ -25,7 +25,7 @@
"settings.nav.group_system": "系统",
"settings.nav.group_extensions": "扩展",
"settings.nav.wallpaper": "壁纸",
- "settings.nav.grid": "网格",
+ "settings.nav.grid": "组件",
"settings.nav.color": "颜色",
"settings.nav.status_bar": "状态栏",
"settings.nav.weather": "天气",
@@ -301,9 +301,18 @@
"settings.status_bar.clock_format_label": "时钟格式",
"settings.status_bar.clock_format.hm": "时:分",
"settings.status_bar.clock_format.hms": "时:分:秒",
- "settings.components.title": "网格",
- "settings.components.description": "调整桌面网格与布局。",
- "settings.components.grid_header": "网格布局",
+ "settings.components.title": "组件",
+ "settings.components.description": "调整组件布局与圆角设计。",
+ "settings.components.grid_header": "网格设置",
+ "settings.components.header": "网格设置",
+ "settings.components.short_side_label": "短边格数",
+ "settings.components.edge_inset_label": "屏幕边距",
+ "settings.components.spacing_label": "组件间距",
+ "settings.components.spacing_compact": "紧凑",
+ "settings.components.spacing_relaxed": "宽松",
+ "settings.components.corner_radius.header": "圆角设计",
+ "settings.components.corner_radius.label": "组件圆角",
+ "settings.components.corner_radius.description": "统一调整组件容器圆角,并随圆角增大同步扩展内部安全区。",
"settings.update.title": "更新",
"settings.update.current_version_label": "当前版本",
"settings.update.latest_version_label": "最新发布",
diff --git a/LanMountainDesktop/Models/AppSettingsSnapshot.cs b/LanMountainDesktop/Models/AppSettingsSnapshot.cs
index f28c8f9..e691e59 100644
--- a/LanMountainDesktop/Models/AppSettingsSnapshot.cs
+++ b/LanMountainDesktop/Models/AppSettingsSnapshot.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using LanMountainDesktop.Settings.Core;
namespace LanMountainDesktop.Models;
@@ -16,6 +17,8 @@ public sealed class AppSettingsSnapshot
public bool UseSystemChrome { get; set; }
+ public double GlobalCornerRadiusScale { get; set; } = GlobalAppearanceSettings.DefaultCornerRadiusScale;
+
public string ThemeColorMode { get; set; } = "default_neutral";
public string SystemMaterialMode { get; set; } = "none";
diff --git a/LanMountainDesktop/Program.cs b/LanMountainDesktop/Program.cs
index a8eb726..a6d31d9 100644
--- a/LanMountainDesktop/Program.cs
+++ b/LanMountainDesktop/Program.cs
@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.WebView.Desktop;
+using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -20,9 +21,11 @@ sealed class Program
{
AppLogger.Initialize();
RegisterGlobalExceptionLogging();
- InitializeDeviceId();
- InitializeCrashReporting();
- InitializeUserBehaviorAnalytics();
+ DesktopBootstrap.InitializeStartupServices(
+ InitializeDeviceId,
+ InitializeCrashReporting,
+ InitializeUserBehaviorAnalytics,
+ ScheduleWhiteboardNoteStartupCleanup);
var restartParentProcessId = AppRestartService.TryGetRestartParentProcessId(args);
using var singleInstance = AcquireSingleInstance(restartParentProcessId);
@@ -43,7 +46,6 @@ sealed class Program
var diagnostics = StartupDiagnosticsService.Run(args);
StartupDiagnosticsService.ShowLegacyExecutableWarningIfNeeded(diagnostics);
- ScheduleWhiteboardNoteStartupCleanup();
try
{
diff --git a/LanMountainDesktop/Services/AppearanceThemeService.cs b/LanMountainDesktop/Services/AppearanceThemeService.cs
index 7c6b9e2..295b849 100644
--- a/LanMountainDesktop/Services/AppearanceThemeService.cs
+++ b/LanMountainDesktop/Services/AppearanceThemeService.cs
@@ -11,9 +11,12 @@ using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.Media.Imaging;
+using LanMountainDesktop.Appearance;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
+using LanMountainDesktop.Settings.Core;
+using LanMountainDesktop.Shared.Contracts;
using LanMountainDesktop.Theme;
using Microsoft.Win32;
@@ -41,6 +44,8 @@ public sealed record AppearanceThemeSnapshot(
string ThemeColorMode,
string? UserThemeColor,
string? SelectedWallpaperSeed,
+ double GlobalCornerRadiusScale,
+ AppearanceCornerRadiusTokens CornerRadiusTokens,
string ResolvedSeedSource,
MonetPalette MonetPalette,
Color AccentColor,
@@ -464,6 +469,13 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
var context = CreateThemeContext(snapshot);
ThemeColorSystemService.ApplyThemeResources(resources, context);
GlassEffectService.ApplyGlassResources(resources, context);
+ resources["DesignCornerRadiusMicro"] = snapshot.CornerRadiusTokens.Micro;
+ resources["DesignCornerRadiusXs"] = snapshot.CornerRadiusTokens.Xs;
+ resources["DesignCornerRadiusSm"] = snapshot.CornerRadiusTokens.Sm;
+ resources["DesignCornerRadiusMd"] = snapshot.CornerRadiusTokens.Md;
+ resources["DesignCornerRadiusLg"] = snapshot.CornerRadiusTokens.Lg;
+ resources["DesignCornerRadiusXl"] = snapshot.CornerRadiusTokens.Xl;
+ resources["DesignCornerRadiusIsland"] = snapshot.CornerRadiusTokens.Island;
}
public AppearanceMaterialSurface GetMaterialSurface(MaterialSurfaceRole role)
@@ -538,6 +550,7 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
if (!refreshAll &&
!changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) &&
!changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase) &&
+ !changedKeys.Contains(nameof(AppSettingsSnapshot.GlobalCornerRadiusScale), StringComparer.OrdinalIgnoreCase) &&
!(respondsToThemeColor &&
changedKeys.Contains(nameof(AppSettingsSnapshot.ThemeColor), StringComparer.OrdinalIgnoreCase)) &&
!(respondsToWallpaper &&
@@ -559,6 +572,8 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
bool queueWallpaperPaletteBuild)
{
var availableModes = _windowMaterialService.GetAvailableModes();
+ var globalCornerRadiusScale = GlobalAppearanceSettings.NormalizeCornerRadiusScale(themeState.GlobalCornerRadiusScale);
+ var cornerRadiusTokens = AppearanceCornerRadiusTokenFactory.Create(globalCornerRadiusScale);
MonetPalette palette;
IReadOnlyList wallpaperSeedCandidates;
Color effectiveSeedColor;
@@ -598,6 +613,8 @@ internal sealed class AppearanceThemeService : IAppearanceThemeService, IDisposa
themeColorMode,
themeState.ThemeColor,
selectedWallpaperSeed,
+ globalCornerRadiusScale,
+ cornerRadiusTokens,
resolvedSeedSource,
palette,
ResolveAccentColor(themeColorMode, themeState.ThemeColor, palette),
diff --git a/LanMountainDesktop/Services/DesktopComponentRegistryFactory.cs b/LanMountainDesktop/Services/DesktopComponentRegistryFactory.cs
index 81b63ac..20198ef 100644
--- a/LanMountainDesktop/Services/DesktopComponentRegistryFactory.cs
+++ b/LanMountainDesktop/Services/DesktopComponentRegistryFactory.cs
@@ -122,6 +122,7 @@ public static class DesktopComponentRegistryFactory
var pluginSettings = new PluginScopedSettingsService(
contribution.Plugin.Manifest.Id,
settingsService);
+ var appearanceSnapshot = HostAppearanceThemeProvider.GetOrCreate().GetCurrent();
var pluginContext = new PluginDesktopComponentContext(
contribution.Plugin.Manifest,
contribution.Plugin.Context.PluginDirectory,
@@ -131,6 +132,8 @@ public static class DesktopComponentRegistryFactory
contribution.Registration.ComponentId,
context.PlacementId,
context.CellSize,
+ appearanceSnapshot.GlobalCornerRadiusScale,
+ appearanceSnapshot.CornerRadiusTokens,
pluginSettings);
return contribution.Registration.ControlFactory(contribution.Plugin.Services, pluginContext);
diff --git a/LanMountainDesktop/Services/IComponentLibraryService.cs b/LanMountainDesktop/Services/IComponentLibraryService.cs
index fc08ed4..c2fa8c7 100644
--- a/LanMountainDesktop/Services/IComponentLibraryService.cs
+++ b/LanMountainDesktop/Services/IComponentLibraryService.cs
@@ -20,6 +20,7 @@ public sealed record ComponentLibraryCategoryEntry(
public sealed record ComponentLibraryCreateContext(
double CellSize,
+ double GlobalCornerRadiusScale,
TimeZoneService TimeZoneService,
IWeatherInfoService WeatherInfoService,
IRecommendationInfoService RecommendationInfoService,
diff --git a/LanMountainDesktop/Services/Settings/SettingsCatalogService.cs b/LanMountainDesktop/Services/Settings/SettingsCatalogService.cs
index acd27ff..2e2dada 100644
--- a/LanMountainDesktop/Services/Settings/SettingsCatalogService.cs
+++ b/LanMountainDesktop/Services/Settings/SettingsCatalogService.cs
@@ -17,7 +17,7 @@ internal sealed class SettingsCatalogService : ISettingsCatalog
[
new SettingsSectionDefinition("general", SettingsCategories.General, SettingsScope.App, "settings.general.title", iconKey: "Settings", sortOrder: 0),
new SettingsSectionDefinition("appearance", SettingsCategories.Appearance, SettingsScope.App, "settings.appearance.title", iconKey: "DesignIdeas", sortOrder: 10),
- new SettingsSectionDefinition("components", SettingsCategories.Components, SettingsScope.ComponentInstance, "settings.components.title", iconKey: "GridDots", sortOrder: 20),
+ new SettingsSectionDefinition("components", SettingsCategories.Components, SettingsScope.ComponentInstance, "settings.components.title", iconKey: "Apps", sortOrder: 20),
new SettingsSectionDefinition("plugins", SettingsCategories.Plugins, SettingsScope.Plugin, "settings.plugins.title", iconKey: "PuzzlePiece", sortOrder: 30),
new SettingsSectionDefinition("about", SettingsCategories.About, SettingsScope.App, "settings.about.title", iconKey: "Info", sortOrder: 40)
]);
diff --git a/LanMountainDesktop/Services/Settings/SettingsContracts.cs b/LanMountainDesktop/Services/Settings/SettingsContracts.cs
index 7a82e8f..53d30d9 100644
--- a/LanMountainDesktop/Services/Settings/SettingsContracts.cs
+++ b/LanMountainDesktop/Services/Settings/SettingsContracts.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
+using LanMountainDesktop.Settings.Core;
namespace LanMountainDesktop.Services.Settings;
@@ -20,6 +21,7 @@ public sealed record ThemeAppearanceSettingsState(
bool IsNightMode,
string? ThemeColor,
bool UseSystemChrome,
+ double GlobalCornerRadiusScale = GlobalAppearanceSettings.DefaultCornerRadiusScale,
string ThemeColorMode = ThemeAppearanceValues.ColorModeDefaultNeutral,
string SystemMaterialMode = ThemeAppearanceValues.MaterialNone,
string? SelectedWallpaperSeed = null);
diff --git a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs
index 6ef48d2..0105f7c 100644
--- a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs
+++ b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs
@@ -10,6 +10,7 @@ using Avalonia.Media.Imaging;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
+using LanMountainDesktop.Settings.Core;
using LanMountainDesktop.Services.PluginMarket;
namespace LanMountainDesktop.Services.Settings;
@@ -242,6 +243,7 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
snapshot.IsNightMode ?? false,
snapshot.ThemeColor,
snapshot.UseSystemChrome,
+ GlobalAppearanceSettings.NormalizeCornerRadiusScale(snapshot.GlobalCornerRadiusScale),
ThemeAppearanceValues.NormalizeThemeColorMode(snapshot.ThemeColorMode, snapshot.ThemeColor),
ThemeAppearanceValues.NormalizeSystemMaterialMode(snapshot.SystemMaterialMode),
snapshot.SelectedWallpaperSeed);
@@ -252,6 +254,7 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
var snapshot = _settingsService.Load();
var changedKeys = new List();
var normalizedThemeColor = string.IsNullOrWhiteSpace(state.ThemeColor) ? null : state.ThemeColor;
+ var normalizedCornerRadiusScale = GlobalAppearanceSettings.NormalizeCornerRadiusScale(state.GlobalCornerRadiusScale);
var normalizedThemeColorMode = ThemeAppearanceValues.NormalizeThemeColorMode(state.ThemeColorMode, state.ThemeColor);
var normalizedSystemMaterialMode = ThemeAppearanceValues.NormalizeSystemMaterialMode(state.SystemMaterialMode);
var normalizedSelectedWallpaperSeed = string.IsNullOrWhiteSpace(state.SelectedWallpaperSeed)
@@ -276,6 +279,12 @@ internal sealed class ThemeAppearanceService : IThemeAppearanceService
changedKeys.Add(nameof(AppSettingsSnapshot.UseSystemChrome));
}
+ if (Math.Abs(GlobalAppearanceSettings.NormalizeCornerRadiusScale(snapshot.GlobalCornerRadiusScale) - normalizedCornerRadiusScale) > 0.0001d)
+ {
+ snapshot.GlobalCornerRadiusScale = normalizedCornerRadiusScale;
+ changedKeys.Add(nameof(AppSettingsSnapshot.GlobalCornerRadiusScale));
+ }
+
if (!string.Equals(snapshot.ThemeColorMode, normalizedThemeColorMode, StringComparison.OrdinalIgnoreCase))
{
snapshot.ThemeColorMode = normalizedThemeColorMode;
diff --git a/LanMountainDesktop/Styles/ComponentEditorThemeResources.axaml b/LanMountainDesktop/Styles/ComponentEditorThemeResources.axaml
index 43e9588..692f90f 100644
--- a/LanMountainDesktop/Styles/ComponentEditorThemeResources.axaml
+++ b/LanMountainDesktop/Styles/ComponentEditorThemeResources.axaml
@@ -18,7 +18,7 @@
-
+
@@ -40,7 +40,7 @@
-
+
@@ -61,7 +61,7 @@
-
+
@@ -100,7 +100,7 @@
-
+
@@ -108,7 +108,7 @@
-
+
@@ -139,14 +139,14 @@
-
+
@@ -175,7 +177,7 @@
-
+
@@ -184,7 +186,7 @@
-
+
@@ -193,7 +195,7 @@
-
+
@@ -206,7 +208,7 @@
diff --git a/LanMountainDesktop/Styles/SettingsCardStyles.axaml b/LanMountainDesktop/Styles/SettingsCardStyles.axaml
index c9786c4..6ea1c28 100644
--- a/LanMountainDesktop/Styles/SettingsCardStyles.axaml
+++ b/LanMountainDesktop/Styles/SettingsCardStyles.axaml
@@ -48,21 +48,21 @@
@@ -201,7 +201,7 @@
-
+
@@ -229,7 +229,7 @@
-
+
@@ -254,7 +254,7 @@
-
+
diff --git a/LanMountainDesktop/ViewModels/SettingsViewModels.cs b/LanMountainDesktop/ViewModels/SettingsViewModels.cs
index ba6063a..3b3f397 100644
--- a/LanMountainDesktop/ViewModels/SettingsViewModels.cs
+++ b/LanMountainDesktop/ViewModels/SettingsViewModels.cs
@@ -12,6 +12,7 @@ using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
+using LanMountainDesktop.Settings.Core;
namespace LanMountainDesktop.ViewModels;
@@ -481,6 +482,9 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private bool _useSystemChrome;
+ [ObservableProperty]
+ private double _globalCornerRadiusScale = GlobalAppearanceSettings.DefaultCornerRadiusScale;
+
[ObservableProperty]
private SelectionOption _selectedThemeColorMode = new(ThemeAppearanceValues.ColorModeSeedMonet, "User theme color Monet");
@@ -547,6 +551,12 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private string _systemMaterialLabel = string.Empty;
+ [ObservableProperty]
+ private string _globalCornerRadiusLabel = string.Empty;
+
+ [ObservableProperty]
+ private string _globalCornerRadiusDescription = string.Empty;
+
[ObservableProperty]
private string _themeHeader = string.Empty;
@@ -668,6 +678,32 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
PersistCurrentState(restartRequired: false);
}
+ partial void OnGlobalCornerRadiusScaleChanged(double value)
+ {
+ if (_isInitializing)
+ {
+ return;
+ }
+
+ var normalized = GlobalAppearanceSettings.NormalizeCornerRadiusScale(value);
+ if (Math.Abs(normalized - value) > 0.0001d)
+ {
+ _isInitializing = true;
+ try
+ {
+ GlobalCornerRadiusScale = normalized;
+ }
+ finally
+ {
+ _isInitializing = false;
+ }
+
+ return;
+ }
+
+ PersistCurrentState(restartRequired: false);
+ }
+
partial void OnSelectedThemeColorModeChanged(SelectionOption value)
{
if (_isInitializing || value is null)
@@ -732,6 +768,8 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
ThemeColorLabel = L("settings.color.theme_color_label", "Theme Accent Color");
ThemeColorModeLabel = L("settings.appearance.theme_color_mode_label", "Theme color source");
SystemMaterialLabel = L("settings.appearance.system_material_label", "System material");
+ GlobalCornerRadiusLabel = L("settings.appearance.corner_radius.label", "Global corner radius");
+ GlobalCornerRadiusDescription = L("settings.appearance.corner_radius.description", "Adjust the shared radius scale used by cards, panels, and component containers.");
ThemeSourceNeutralText = L("settings.appearance.theme_color_mode.neutral", "Default neutral");
ThemeSourceUserColorText = L("settings.appearance.theme_color_mode.user", "User theme color Monet");
ThemeSourceWallpaperText = L("settings.appearance.theme_color_mode.wallpaper", "Wallpaper Monet");
@@ -776,6 +814,7 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
IsNightMode = theme.IsNightMode;
ThemeColor = theme.ThemeColor ?? string.Empty;
UseSystemChrome = theme.UseSystemChrome;
+ GlobalCornerRadiusScale = GlobalAppearanceSettings.NormalizeCornerRadiusScale(theme.GlobalCornerRadiusScale);
_selectedWallpaperSeed = theme.SelectedWallpaperSeed;
SyncCustomSeedPickerWithSavedThemeColor();
@@ -825,6 +864,7 @@ public sealed partial class AppearanceSettingsPageViewModel : ViewModelBase
IsNightMode,
themeColor,
UseSystemChrome,
+ GlobalAppearanceSettings.NormalizeCornerRadiusScale(GlobalCornerRadiusScale),
themeColorMode,
ThemeAppearanceValues.NormalizeSystemMaterialMode(SelectedSystemMaterialMode?.Value),
_selectedWallpaperSeed);
@@ -956,7 +996,7 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
private string _pageDescription = string.Empty;
[ObservableProperty]
- private string _gridHeader = string.Empty;
+ private string _componentsHeader = string.Empty;
[ObservableProperty]
private string _shortSideCellsLabel = string.Empty;
@@ -967,6 +1007,18 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private string _spacingPresetLabel = string.Empty;
+ [ObservableProperty]
+ private double _globalCornerRadiusScale = GlobalAppearanceSettings.DefaultCornerRadiusScale;
+
+ [ObservableProperty]
+ private string _componentRadiusHeader = string.Empty;
+
+ [ObservableProperty]
+ private string _globalCornerRadiusLabel = string.Empty;
+
+ [ObservableProperty]
+ private string _globalCornerRadiusDescription = string.Empty;
+
public void Load()
{
var state = _settingsFacade.Grid.Get();
@@ -976,6 +1028,9 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
SelectedSpacingPreset = SpacingPresets.FirstOrDefault(option =>
string.Equals(option.Value, spacingPreset, StringComparison.OrdinalIgnoreCase))
?? SpacingPresets[1];
+
+ var theme = _settingsFacade.Theme.Get();
+ GlobalCornerRadiusScale = GlobalAppearanceSettings.NormalizeCornerRadiusScale(theme.GlobalCornerRadiusScale);
}
partial void OnShortSideCellsChanged(int value)
@@ -1008,6 +1063,32 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
SaveGrid();
}
+ partial void OnGlobalCornerRadiusScaleChanged(double value)
+ {
+ if (_isInitializing)
+ {
+ return;
+ }
+
+ var normalized = GlobalAppearanceSettings.NormalizeCornerRadiusScale(value);
+ if (Math.Abs(normalized - value) > 0.0001d)
+ {
+ _isInitializing = true;
+ try
+ {
+ GlobalCornerRadiusScale = normalized;
+ }
+ finally
+ {
+ _isInitializing = false;
+ }
+
+ return;
+ }
+
+ SaveComponentCornerRadius();
+ }
+
private void SaveGrid()
{
_settingsFacade.Grid.Save(new GridSettingsState(
@@ -1016,23 +1097,39 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
Math.Clamp(EdgeInsetPercent, 0, 30)));
}
+ private void SaveComponentCornerRadius()
+ {
+ var theme = _settingsFacade.Theme.Get();
+ _settingsFacade.Theme.Save(new ThemeAppearanceSettingsState(
+ theme.IsNightMode,
+ theme.ThemeColor,
+ theme.UseSystemChrome,
+ GlobalAppearanceSettings.NormalizeCornerRadiusScale(GlobalCornerRadiusScale),
+ theme.ThemeColorMode,
+ theme.SystemMaterialMode,
+ theme.SelectedWallpaperSeed));
+ }
+
private IReadOnlyList CreateSpacingPresets()
{
return
[
- new SelectionOption("Compact", L("settings.grid.spacing_compact", "Compact")),
- new SelectionOption("Relaxed", L("settings.grid.spacing_relaxed", "Relaxed"))
+ new SelectionOption("Compact", L("settings.components.spacing_compact", "Compact")),
+ new SelectionOption("Relaxed", L("settings.components.spacing_relaxed", "Relaxed"))
];
}
private void RefreshLocalizedText()
{
PageTitle = L("settings.components.title", "Components");
- PageDescription = L("settings.components.description", "Desktop grid and widget placement density.");
- GridHeader = L("settings.components.grid_header", "Grid Layout");
- ShortSideCellsLabel = L("settings.grid.short_side_label", "Short Side Cells");
- EdgeInsetPercentLabel = L("settings.grid.edge_inset_label", "Screen Inset");
- SpacingPresetLabel = L("settings.grid.spacing_label", "Grid Spacing");
+ PageDescription = L("settings.components.description", "Adjust component layout and corner design.");
+ ComponentsHeader = L("settings.components.header", "Grid Settings");
+ ShortSideCellsLabel = L("settings.components.short_side_label", "Short Side Cells");
+ EdgeInsetPercentLabel = L("settings.components.edge_inset_label", "Screen Inset");
+ SpacingPresetLabel = L("settings.components.spacing_label", "Component Spacing");
+ ComponentRadiusHeader = L("settings.components.corner_radius.header", "Corner Design");
+ GlobalCornerRadiusLabel = L("settings.components.corner_radius.label", "Component Corner Radius");
+ GlobalCornerRadiusDescription = L("settings.components.corner_radius.description", "Adjust the shared corner radius used by component containers, and expand the internal safe area with it.");
}
private string L(string key, string fallback)
diff --git a/LanMountainDesktop/Views/ComponentEditorWindow.axaml b/LanMountainDesktop/Views/ComponentEditorWindow.axaml
index dc35e67..d38de29 100644
--- a/LanMountainDesktop/Views/ComponentEditorWindow.axaml
+++ b/LanMountainDesktop/Views/ComponentEditorWindow.axaml
@@ -53,7 +53,7 @@