改变无声
This commit is contained in:
lincube
2026-03-20 00:41:14 +08:00
parent cb86ca10e7
commit 915739ff7b
82 changed files with 977 additions and 225 deletions

8
Directory.Build.props Normal file
View File

@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<Version>1.0.0</Version>
<TargetFramework Condition="'$(TargetFramework)' == ''">net10.0</TargetFramework>
<Nullable Condition="'$(Nullable)' == ''">enable</Nullable>
<ImplicitUsings Condition="'$(ImplicitUsings)' == ''">enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -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);
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Settings.Core\LanMountainDesktop.Settings.Core.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Host.Abstractions\LanMountainDesktop.Host.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Settings.Core\LanMountainDesktop.Settings.Core.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Appearance\LanMountainDesktop.Appearance.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Host.Abstractions\LanMountainDesktop.Host.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -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();
}
}

View File

@@ -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<IClassicDesktopStyleApplicationLifetime> _createAndAssignMainWindow;
private readonly Action _performExitCleanup;
private readonly Action _startActivationListener;
private readonly Action _startWeatherRefresh;
public DesktopShellHost(
Action initializePluginRuntime,
Action initializeTrayIcon,
Action<IClassicDesktopStyleApplicationLifetime> 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();
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Settings.Core\LanMountainDesktop.Settings.Core.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Appearance\LanMountainDesktop.Appearance.csproj" />
<ProjectReference Include="..\LanMountainDesktop.DesktopComponents.Runtime\LanMountainDesktop.DesktopComponents.Runtime.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Host.Abstractions\LanMountainDesktop.Host.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using System;
namespace LanMountainDesktop.DesktopHost;
public sealed class SettingsWindowHost
{
private readonly Action<string, string?> _openSettingsWindow;
public SettingsWindowHost(Action<string, string?> openSettingsWindow)
{
_openSettingsWindow = openSettingsWindow ?? throw new ArgumentNullException(nameof(openSettingsWindow));
}
public void Open(string source, string? pageId = null)
{
_openSettingsWindow(source, pageId);
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace LanMountainDesktop.DesktopHost;
public sealed class ShutdownCoordinator
{
private readonly Action<bool, string> _prepareForShutdown;
private readonly Action<string> _resetShutdownIntent;
public ShutdownCoordinator(Action<bool, string> prepareForShutdown, Action<string> 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);
}

View File

@@ -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);

View File

@@ -0,0 +1,6 @@
namespace LanMountainDesktop.Host.Abstractions;
public interface IDesktopShellHost
{
void Initialize();
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />
</ItemGroup>
</Project>

View File

@@ -12,6 +12,7 @@
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@@ -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<T>()
{
return (T?)Services.GetService(typeof(T));

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@@ -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);

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.12" />
</ItemGroup>
</Project>

View File

@@ -1,5 +1,11 @@
<Solution>
<Project Path="LanAirApp/tools/LanMountainDesktop.PluginPackager/LanMountainDesktop.PluginPackager.csproj" />
<Project Path="LanMountainDesktop.Host.Abstractions/LanMountainDesktop.Host.Abstractions.csproj" />
<Project Path="LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj" />
<Project Path="LanMountainDesktop.Settings.Core/LanMountainDesktop.Settings.Core.csproj" />
<Project Path="LanMountainDesktop.Appearance/LanMountainDesktop.Appearance.csproj" />
<Project Path="LanMountainDesktop.DesktopComponents.Runtime/LanMountainDesktop.DesktopComponents.Runtime.csproj" />
<Project Path="LanMountainDesktop.DesktopHost/LanMountainDesktop.DesktopHost.csproj" />
<Project Path="LanMountainDesktop.PluginSdk/LanMountainDesktop.PluginSdk.csproj" />
<Project Path="LanMountainDesktop.PluginsInstallHelper/LanMountainDesktop.PluginsInstallHelper.csproj" />
<Project Path="LanMountainDesktop/LanMountainDesktop.csproj" />

View File

@@ -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) &&

View File

@@ -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);

View File

@@ -0,0 +1,8 @@
using LanMountainDesktop.Host.Abstractions;
namespace LanMountainDesktop.ComponentSystem;
public interface IComponentChromeContextAware
{
void SetComponentChromeContext(ComponentChromeContext context);
}

View File

@@ -29,6 +29,12 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.Host.Abstractions\LanMountainDesktop.Host.Abstractions.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Settings.Core\LanMountainDesktop.Settings.Core.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Appearance\LanMountainDesktop.Appearance.csproj" />
<ProjectReference Include="..\LanMountainDesktop.DesktopComponents.Runtime\LanMountainDesktop.DesktopComponents.Runtime.csproj" />
<ProjectReference Include="..\LanMountainDesktop.DesktopHost\LanMountainDesktop.DesktopHost.csproj" />
<ProjectReference Include="..\LanMountainDesktop.PluginSdk\LanMountainDesktop.PluginSdk.csproj" />
<ProjectReference Include="..\LanMountainDesktop.PluginsInstallHelper\LanMountainDesktop.PluginsInstallHelper.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

View File

@@ -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",

View File

@@ -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": "最新发布",

View File

@@ -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";

View File

@@ -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
{

View File

@@ -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<Color> 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),

View File

@@ -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);

View File

@@ -20,6 +20,7 @@ public sealed record ComponentLibraryCategoryEntry(
public sealed record ComponentLibraryCreateContext(
double CellSize,
double GlobalCornerRadiusScale,
TimeZoneService TimeZoneService,
IWeatherInfoService WeatherInfoService,
IRecommendationInfoService RecommendationInfoService,

View File

@@ -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)
]);

View File

@@ -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);

View File

@@ -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<string>();
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;

View File

@@ -18,7 +18,7 @@
<Setter Property="Background" Value="{DynamicResource EditorSelectFieldBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource EditorSelectOutlineBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="18" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
<Setter Property="Padding" Value="16,14,12,14" />
<Setter Property="MinHeight" Value="56" />
<Setter Property="FontSize" Value="14" />
@@ -40,7 +40,7 @@
<Setter Property="Background" Value="{DynamicResource EditorSelectFieldBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource EditorSelectOutlineBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="18" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
<Setter Property="Padding" Value="16,14,12,14" />
<Setter Property="MinHeight" Value="56" />
<Setter Property="Foreground" Value="{DynamicResource ComponentEditorPrimaryTextBrush}" />
@@ -61,7 +61,7 @@
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="16,12" />
<Setter Property="Margin" Value="6,4" />
<Setter Property="CornerRadius" Value="14" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="MinHeight" Value="44" />
</Style>
@@ -100,7 +100,7 @@
<Setter Property="Background" Value="{DynamicResource EditorSurfaceContainerHighBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource EditorSelectOutlineBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusLg}" />
<Setter Property="Padding" Value="4" />
</Style>
@@ -108,7 +108,7 @@
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="16" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="Padding" Value="18,12" />
<Setter Property="MinHeight" Value="48" />
<Setter Property="FontSize" Value="14" />
@@ -139,14 +139,14 @@
<Setter Property="Background" Value="{DynamicResource ComponentEditorHeroBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComponentEditorCardBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="28" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXl}" />
</Style>
<Style Selector="Border.component-editor-card">
<Setter Property="Background" Value="{DynamicResource ComponentEditorCardBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ComponentEditorCardBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="24" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusLg}" />
</Style>
<Style Selector="TextBlock.component-editor-headline">

View File

@@ -4,11 +4,13 @@
<Styles.Resources>
<!-- Unified corner radius tokens used across settings and widget panels -->
<CornerRadius x:Key="DesignCornerRadiusMicro">6</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusXl">32</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusLg">28</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusMd">20</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusSm">14</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusXs">12</CornerRadius>
<CornerRadius x:Key="DesignCornerRadiusIsland">36</CornerRadius>
</Styles.Resources>
<Style Selector="TextBlock">
@@ -19,7 +21,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
<Setter Property="CornerRadius" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
<Setter Property="Foreground" Value="{DynamicResource AdaptiveTextPrimaryBrush}" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Padding" Value="16,10" />
@@ -155,7 +157,7 @@
<Style Selector="Button.swatch-button">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="16" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXs}" />
<Setter Property="Opacity" Value="0.88" />
</Style>
@@ -175,7 +177,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassPanelBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassPanelBorderBrush}" />
<Setter Property="BorderThickness" Value="1.2" />
<Setter Property="CornerRadius" Value="28" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusLg}" />
<Setter Property="Opacity" Value="{DynamicResource AdaptiveGlassPanelOpacity}" />
<Setter Property="BoxShadow" Value="0 4 12 #1A000000" />
</Style>
@@ -184,7 +186,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassStrongBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassStrongBorderBrush}" />
<Setter Property="BorderThickness" Value="1.5" />
<Setter Property="CornerRadius" Value="32" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXl}" />
<Setter Property="Opacity" Value="{DynamicResource AdaptiveGlassStrongOpacity}" />
<Setter Property="BoxShadow" Value="0 8 24 #26000000" />
</Style>
@@ -193,7 +195,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveDockGlassBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveDockGlassBorderBrush}" />
<Setter Property="BorderThickness" Value="1.5" />
<Setter Property="CornerRadius" Value="36" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusIsland}" />
<Setter Property="Opacity" Value="{DynamicResource AdaptiveGlassStrongOpacity}" />
<Setter Property="BoxShadow" Value="0 12 32 #33000000" />
<Setter Property="Transitions">
@@ -206,7 +208,7 @@
<Style Selector="Border.surface-solid-strong">
<Setter Property="Background" Value="{DynamicResource AdaptiveGlassStrongBackgroundBrush}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="36" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusIsland}" />
<Setter Property="Opacity" Value="{DynamicResource AdaptiveGlassStrongOpacity}" />
<Setter Property="BoxShadow" Value="0 8 22 #2A000000" />
</Style>

View File

@@ -48,21 +48,21 @@
</Style>
<Style Selector="Border.settings-section-card">
<Setter Property="CornerRadius" Value="18" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusLg}" />
<Setter Property="Padding" Value="20" />
<Setter Property="Margin" Value="0,0,0,14" />
<Setter Property="BoxShadow" Value="0 2 8 #12000000" />
</Style>
<Style Selector="Border.settings-option-card">
<Setter Property="CornerRadius" Value="14" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="Padding" Value="16" />
<Setter Property="Margin" Value="0,0,0,12" />
<Setter Property="BoxShadow" Value="0 1 4 #0F000000" />
</Style>
<Style Selector="Border.settings-list-item">
<Setter Property="CornerRadius" Value="14" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="Padding" Value="16" />
<Setter Property="Margin" Value="0,0,0,10" />
<Setter Property="BoxShadow" Value="0 1 4 #0F000000" />
@@ -77,7 +77,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXs}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
@@ -201,7 +201,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveSurfaceRaisedBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveGlassPanelBorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXs}" />
<Setter Property="Padding" Value="14,12" />
<Setter Property="Margin" Value="0,0,0,8" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
@@ -229,7 +229,7 @@
<Setter Property="Background" Value="{DynamicResource AdaptiveAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AdaptiveOnAccentBrush}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXs}" />
<Setter Property="Padding" Value="16,10" />
<Setter Property="MinHeight" Value="36" />
</Style>
@@ -254,7 +254,7 @@
<Setter Property="Width" Value="36" />
<Setter Property="Height" Value="36" />
<Setter Property="Padding" Value="0" />
<Setter Property="CornerRadius" Value="10" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusXs}" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="Background" Value="{DynamicResource AdaptiveButtonBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />

View File

@@ -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<SelectionOption> 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)

View File

@@ -53,7 +53,7 @@
<!-- MD3 Button Styles -->
<Style Selector="Button.component-editor-footer-button">
<Setter Property="CornerRadius" Value="20" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusMd}" />
<Setter Property="Background" Value="{DynamicResource EditorPrimaryBrush}" />
<Setter Property="Foreground" Value="{DynamicResource EditorOnPrimaryBrush}" />
<Setter Property="Height" Value="40" />
@@ -118,7 +118,7 @@
Height="64"
Background="{DynamicResource EditorPrimaryBrush}"
Foreground="{DynamicResource EditorOnPrimaryBrush}"
CornerRadius="18"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Classes="accent"
Click="OnCloseClick">
<Button.Styles>

View File

@@ -39,7 +39,7 @@
ColumnDefinitions="240,*"
ColumnSpacing="12">
<Border Classes="surface-translucent-panel"
CornerRadius="24"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Padding="10">
<ListBox x:Name="CategoryListBox"
Background="Transparent"
@@ -50,7 +50,7 @@
<DataTemplate x:DataType="vm:ComponentLibraryCategoryViewModel">
<Border Padding="10"
Margin="0,0,0,6"
CornerRadius="14"
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
Background="{DynamicResource AdaptiveNavItemBackgroundBrush}">
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="8">
@@ -71,7 +71,7 @@
<Border Grid.Column="1"
Classes="surface-translucent-strong"
CornerRadius="24"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Padding="10">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
@@ -87,14 +87,14 @@
<Border Width="240"
Height="220"
Margin="6"
CornerRadius="18"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Padding="10"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
BorderThickness="1">
<Grid RowDefinitions="*,Auto,Auto"
RowSpacing="8">
<Border CornerRadius="12"
<Border CornerRadius="{DynamicResource DesignCornerRadiusXs}"
Background="{DynamicResource AdaptiveGlassPanelBackgroundBrush}"
BorderThickness="1"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"

View File

@@ -326,7 +326,7 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(42 * scale, 16, 56));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(42 * scale, 16, 56);
RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 8, 26));
ApplyModeVisualIfNeeded();
}

View File

@@ -381,12 +381,12 @@ public partial class BaiduHotSearchWidget : UserControl, IDesktopComponentWidget
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * softScale, 16, 52));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
RootBorder.Padding = new Thickness(0);
var horizontalPadding = Math.Clamp(16 * softScale, 8, 24);
var verticalPadding = Math.Clamp(14 * softScale, 7, 20);
CardBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * softScale, 16, 52));
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
var innerWidth = Math.Max(120, totalWidth - (horizontalPadding * 2d));

View File

@@ -386,12 +386,12 @@ public partial class BilibiliHotSearchWidget : UserControl, IDesktopComponentWid
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * softScale, 16, 52));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
RootBorder.Padding = new Thickness(0);
var horizontalPadding = Math.Clamp(16 * softScale, 8, 24);
var verticalPadding = Math.Clamp(14 * softScale, 7, 20);
CardBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * softScale, 16, 52));
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
var innerWidth = Math.Max(120, totalWidth - (horizontalPadding * 2d));

View File

@@ -79,11 +79,11 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
{
_currentCellSize = Math.Max(1, cellSize);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 12, 28));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 12, 28);
RootBorder.Padding = new Thickness(Math.Clamp(_currentCellSize * 0.20, 8, 18));
WebViewHostBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.24, 10, 22));
AddressBarBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.22, 10, 20));
WebViewHostBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.24, 10, 22);
AddressBarBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.22, 10, 20);
AddressBarBorder.Padding = new Thickness(8, 6);
if (RootBorder.Child is Grid rootGrid)

View File

@@ -613,18 +613,17 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
? CreateBrush("#FF4FC3F7")
: CreateBrush("#FF3250");
var cornerRadius = Math.Clamp(_currentCellSize * 0.45, 24, 44);
RootBorder.CornerRadius = new CornerRadius(cornerRadius);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.45, 24, 44);
RootBorder.Background = _isNightVisual
? CreateGradientBrush("#171A21", "#0C0E14")
: CreateGradientBrush("#F7F8FC", "#ECEFF6");
RootBorder.BorderBrush = CreateBrush(_isNightVisual ? "#24FFFFFF" : "#15000000");
var rootPadding = new Thickness(
Math.Clamp(16 * scale, 10, 24),
Math.Clamp(14 * scale, 9, 20),
Math.Clamp(16 * scale, 10, 24),
Math.Clamp(14 * scale, 8, 20));
ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 9, 20),
ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 8, 20));
RootBorder.Padding = rootPadding;
LayoutGrid.RowSpacing = Math.Clamp(14 * scale, 6, 20);

View File

@@ -480,7 +480,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
{
Width = 160,
Height = 90,
CornerRadius = new CornerRadius(16),
CornerRadius = ComponentChromeCornerRadiusHelper.Scale(16, 8, 22),
ClipToBounds = true,
Background = new SolidColorBrush(Color.Parse("#E6E6E6")),
IsHitTestVisible = false
@@ -545,10 +545,10 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
var scale = ResolveScale();
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 16, 52));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
RootBorder.Padding = new Thickness(0);
CardBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 16, 52));
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
CardBorder.Padding = new Thickness(
Math.Clamp(16 * scale, 8, 24),
Math.Clamp(14 * scale, 7, 22),
@@ -573,8 +573,8 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
News1ImageHost.Height = imageHeight;
News2ImageHost.Width = imageWidth;
News2ImageHost.Height = imageHeight;
News1ImageHost.CornerRadius = new CornerRadius(Math.Clamp(16 * scale, 8, 22));
News2ImageHost.CornerRadius = new CornerRadius(Math.Clamp(16 * scale, 8, 22));
News1ImageHost.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(16 * scale, 8, 22);
News2ImageHost.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(16 * scale, 8, 22);
var columnGap = Math.Clamp(12 * scale, 6, 18);
NewsItem1Grid.ColumnSpacing = columnGap;
@@ -611,7 +611,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
row.ImageHost.Width = imageWidth;
row.ImageHost.Height = imageHeight;
row.ImageHost.CornerRadius = new CornerRadius(Math.Clamp(16 * scale, 8, 22));
row.ImageHost.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(16 * scale, 8, 22);
row.TitleTextBlock.MaxWidth = availableTextWidth;
row.TitleTextBlock.FontSize = Math.Clamp(19 * scale, 10, 25);

View File

@@ -0,0 +1,72 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
internal static class ComponentChromeCornerRadiusHelper
{
public static double ResolveScale(ComponentChromeContext? chromeContext = null)
{
if (chromeContext is not null)
{
return Math.Max(0.1d, chromeContext.GlobalCornerRadiusScale);
}
return Math.Max(0.1d, HostAppearanceThemeProvider.GetOrCreate().GetCurrent().GlobalCornerRadiusScale);
}
public static CornerRadius Scale(double baseRadius, double min, double max, ComponentChromeContext? chromeContext = null)
{
var scale = ResolveScale(chromeContext);
return new CornerRadius(Math.Clamp(baseRadius * scale, min * scale, max * scale));
}
public static void Apply(CornerRadius radius, params Border?[] chromeLayers)
{
foreach (var chromeLayer in chromeLayers)
{
if (chromeLayer is not null)
{
chromeLayer.CornerRadius = radius;
}
}
}
public static CornerRadius ResolveToken(string key, double fallback)
{
var application = Application.Current;
return application is not null &&
application.Resources.TryGetResource(key, application.ActualThemeVariant, out var resource) &&
resource is CornerRadius radius
? radius
: new CornerRadius(fallback);
}
public static double ScaleValue(double value, ComponentChromeContext? chromeContext = null)
{
return value * ResolveScale(chromeContext);
}
public static double ResolveContentSafetyScale(
ComponentChromeContext? chromeContext = null,
double responsiveness = 0.45d)
{
var scale = ResolveScale(chromeContext);
var normalizedResponsiveness = Math.Clamp(responsiveness, 0d, 1d);
return 1d + ((scale - 1d) * normalizedResponsiveness);
}
public static double SafeValue(
double baseValue,
double min,
double max,
ComponentChromeContext? chromeContext = null,
double responsiveness = 0.45d)
{
var safetyScale = ResolveContentSafetyScale(chromeContext, responsiveness);
return Math.Clamp(baseValue * safetyScale, min * safetyScale, max * safetyScale);
}
}

View File

@@ -101,7 +101,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 16, 52));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
InfoPanel.Padding = new Thickness(
Math.Clamp(18 * scale, 10, 28),

View File

@@ -92,7 +92,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 16, 52));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
RootBorder.Padding = new Thickness(
Math.Clamp(20 * scale, 10, 34),
Math.Clamp(16 * scale, 8, 28),

View File

@@ -328,7 +328,7 @@ public partial class DailyWord2x2Widget : UserControl, IDesktopComponentWidget,
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(30 * scale, 14, 40));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 14, 40);
CardBorder.CornerRadius = RootBorder.CornerRadius;
CardBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 8, 18),

View File

@@ -298,10 +298,11 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
isFourByThree = widthRatio >= 0.9 && heightRatio >= 1.35;
}
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 16, 52));
var containerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
RootBorder.CornerRadius = containerRadius;
RootBorder.Padding = new Thickness(0);
CardBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 16, 52));
CardBorder.CornerRadius = containerRadius;
CardBorder.Padding = new Thickness(
Math.Clamp(16 * scale, 8, 24),
Math.Clamp(14 * scale, 7, 22),

View File

@@ -325,7 +325,7 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon
{
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(28 * scale, 16, 40));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(28 * scale, 16, 40);
RootBorder.Padding = new Thickness(Math.Clamp(11 * scale, 7, 17));
LayoutRoot.ColumnSpacing = Math.Clamp(10 * scale, 6, 16);
@@ -337,7 +337,7 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon
Math.Clamp(2.4 * scale, 1, 4));
CalendarGrid.Margin = new Thickness(0, 0, 0, Math.Clamp(0.8 * scale, 0, 2));
LunarCardBorder.CornerRadius = new CornerRadius(Math.Clamp(24 * scale, 14, 34));
LunarCardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(24 * scale, 14, 34);
LunarCardBorder.Padding = new Thickness(Math.Clamp(14 * scale, 8, 20));
RightPanelGrid.RowSpacing = Math.Clamp(7.5 * scale, 3.5, 11);
DividerBorder.Margin = new Thickness(0, Math.Clamp(1 * scale, 0, 2), 0, Math.Clamp(1 * scale, 0, 2));

View File

@@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using LanMountainDesktop.Appearance;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -30,7 +32,11 @@ public sealed class DesktopComponentRuntimeRegistration
string? displayNameLocalizationKey,
Func<Control> controlFactory,
Func<double, double>? cornerRadiusResolver = null)
: this(componentId, displayNameLocalizationKey, _ => controlFactory(), cornerRadiusResolver)
: this(
componentId,
displayNameLocalizationKey,
_ => controlFactory(),
cornerRadiusResolver is null ? null : chromeContext => cornerRadiusResolver(chromeContext.CellSize))
{
}
@@ -39,6 +45,19 @@ public sealed class DesktopComponentRuntimeRegistration
string? displayNameLocalizationKey,
Func<DesktopComponentControlFactoryContext, Control> controlFactory,
Func<double, double>? cornerRadiusResolver = null)
: this(
componentId,
displayNameLocalizationKey,
controlFactory,
cornerRadiusResolver is null ? null : chromeContext => cornerRadiusResolver(chromeContext.CellSize))
{
}
public DesktopComponentRuntimeRegistration(
string componentId,
string? displayNameLocalizationKey,
Func<DesktopComponentControlFactoryContext, Control> controlFactory,
Func<ComponentChromeContext, double>? cornerRadiusResolver = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(componentId);
ArgumentNullException.ThrowIfNull(controlFactory);
@@ -57,22 +76,27 @@ public sealed class DesktopComponentRuntimeRegistration
public Func<DesktopComponentControlFactoryContext, Control> ControlFactory { get; }
public Func<double, double>? CornerRadiusResolver { get; }
public Func<ComponentChromeContext, double>? CornerRadiusResolver { get; }
}
public sealed class DesktopComponentRuntimeDescriptor
{
private static readonly Func<double, double> DefaultCornerRadiusResolver =
cellSize => Math.Clamp(cellSize * 0.22, 8, 18);
private static readonly Func<ComponentChromeContext, double> DefaultCornerRadiusResolver =
chromeContext =>
{
var scale = Math.Max(0.1d, chromeContext.GlobalCornerRadiusScale);
var baseRadius = Math.Clamp(chromeContext.CellSize * 0.22, 8, 18);
return Math.Clamp(baseRadius * scale, 8 * scale, 18 * scale);
};
private readonly Func<DesktopComponentControlFactoryContext, Control> _controlFactory;
private readonly Func<double, double> _cornerRadiusResolver;
private readonly Func<ComponentChromeContext, double> _cornerRadiusResolver;
internal DesktopComponentRuntimeDescriptor(
DesktopComponentDefinition definition,
string? displayNameLocalizationKey,
Func<DesktopComponentControlFactoryContext, Control> controlFactory,
Func<double, double>? cornerRadiusResolver)
Func<ComponentChromeContext, double>? cornerRadiusResolver)
{
Definition = definition;
DisplayNameLocalizationKey = displayNameLocalizationKey;
@@ -97,9 +121,16 @@ public sealed class DesktopComponentRuntimeDescriptor
var settingsService = settingsFacade.Settings;
var appearanceTheme = HostAppearanceThemeProvider.GetOrCreate();
var appearanceSnapshot = appearanceTheme.GetCurrent();
var componentAccessor = settingsService.GetComponentAccessor(Definition.Id, placementId);
var componentSettingsStore = new ComponentSettingsService(settingsService);
componentSettingsStore.SetScopedComponentContext(Definition.Id, placementId);
var chromeContext = new ComponentChromeContext(
Definition.Id,
placementId,
cellSize,
appearanceSnapshot.GlobalCornerRadiusScale,
appearanceSnapshot.CornerRadiusTokens);
var control = _controlFactory(new DesktopComponentControlFactoryContext(
Definition,
cellSize,
@@ -118,6 +149,7 @@ public sealed class DesktopComponentRuntimeDescriptor
settingsFacade,
settingsService,
appearanceTheme,
chromeContext,
componentAccessor,
componentSettingsStore);
@@ -145,6 +177,11 @@ public sealed class DesktopComponentRuntimeDescriptor
placementAwareComponent.SetComponentPlacementContext(Definition.Id, placementId);
}
if (control is IComponentChromeContextAware chromeContextAwareComponent)
{
chromeContextAwareComponent.SetComponentChromeContext(chromeContext);
}
if (control is IDesktopComponentWidget sizedComponent)
{
sizedComponent.ApplyCellSize(cellSize);
@@ -173,9 +210,22 @@ public sealed class DesktopComponentRuntimeDescriptor
return control;
}
public double ResolveCornerRadius(ComponentChromeContext chromeContext)
{
ArgumentNullException.ThrowIfNull(chromeContext);
var resolved = _cornerRadiusResolver(chromeContext with { CellSize = Math.Max(1, chromeContext.CellSize) });
return double.IsFinite(resolved) ? Math.Max(0d, resolved) : DefaultCornerRadiusResolver(chromeContext);
}
public double ResolveCornerRadius(double cellSize)
{
return _cornerRadiusResolver(Math.Max(1, cellSize));
return ResolveCornerRadius(new ComponentChromeContext(
Definition.Id,
null,
Math.Max(1, cellSize),
1d,
AppearanceCornerRadiusTokenFactory.Create(1d)));
}
private static void ApplySettingsDependencies(

View File

@@ -80,8 +80,8 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
{
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 14, 48));
RootBorder.Padding = new Thickness(Math.Clamp(12 * scale, 6, 18));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 14, 48);
RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(12 * scale, 6, 18));
}
public void SetRecommendationInfoService(IRecommendationInfoService recommendationInfoService)

View File

@@ -10,6 +10,7 @@ using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
@@ -18,7 +19,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentChromeContextAware
{
private static readonly IWeatherInfoService DefaultWeatherInfoService = new XiaomiWeatherService();
private static readonly IReadOnlyList<int> SupportedAutoRefreshIntervalsMinutes = RefreshIntervalCatalog.SupportedIntervalsMinutes;
@@ -34,6 +35,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
private TimeZoneService? _timeZoneService;
private CancellationTokenSource? _refreshCts;
private double _currentCellSize = 48;
private ComponentChromeContext? _chromeContext;
private double _phase;
private bool _isAttached;
private bool _isOnActivePage = true;
@@ -122,21 +124,34 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
var metrics = HyperOS3WeatherTheme.ResolveMetrics(HyperOS3WeatherWidgetKind.Extended4x4);
var width = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 4;
var height = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 4;
var radius = Math.Clamp(_currentCellSize * metrics.CornerRadiusScale, 28, 54);
RootBorder.CornerRadius = new CornerRadius(radius);
BackgroundImageLayer.CornerRadius = new CornerRadius(radius);
BackgroundMotionLayer.CornerRadius = new CornerRadius(radius);
BackgroundTintLayer.CornerRadius = new CornerRadius(radius);
BackgroundLightLayer.CornerRadius = new CornerRadius(radius);
BackgroundShadeLayer.CornerRadius = new CornerRadius(radius);
var radius = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
28,
54,
_chromeContext);
ComponentChromeCornerRadiusHelper.Apply(
radius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,
BackgroundTintLayer,
BackgroundLightLayer,
BackgroundShadeLayer);
var horizontalPadding = Math.Clamp(Math.Min(width * metrics.HorizontalPaddingScale * 0.30, width * 0.11), 4, 34);
var verticalPadding = Math.Clamp(Math.Min(height * metrics.VerticalPaddingScale * 0.30, height * 0.11), 4, 34);
ContentPaddingBorder.Padding = new Thickness(
horizontalPadding,
verticalPadding);
ComponentChromeCornerRadiusHelper.SafeValue(horizontalPadding, 4, 34, _chromeContext),
ComponentChromeCornerRadiusHelper.SafeValue(verticalPadding, 4, 34, _chromeContext));
ApplyTypography(width, height);
}
public void SetComponentChromeContext(ComponentChromeContext context)
{
ArgumentNullException.ThrowIfNull(context);
_chromeContext = context;
ApplyCellSize(_currentCellSize);
}
public void SetTimeZoneService(TimeZoneService timeZoneService)
{
if (_timeZoneService is not null)

View File

@@ -216,8 +216,8 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge
var titleNeedsTwoLines = isUltraCompact || titleUnits >= (isCompact ? 13 : 17);
var dateNeedsTwoLines = isUltraCompact || dateUnits >= (isCompact ? 15 : 20);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(shortSide * 0.13, 10, 46));
var padding = Math.Clamp(shortSide * 0.05, 4.5, 21);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(shortSide * 0.13, 10, 46);
var padding = ComponentChromeCornerRadiusHelper.SafeValue(shortSide * 0.05, 4.5, 21);
RootBorder.Padding = new Thickness(padding);
LayoutRoot.RowSpacing = Math.Clamp(shortSide * 0.028, 2.2, 12);
var rowWeights = ApplyAdaptiveRowHeights(isCompact, isUltraCompact, titleNeedsTwoLines, dateNeedsTwoLines);

View File

@@ -12,13 +12,14 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentChromeContextAware
{
private enum WeatherVisualKind
{
@@ -117,6 +118,7 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
private WeatherSnapshot? _latestSnapshot;
private string _languageCode = "zh-CN";
private double _currentCellSize = 48;
private ComponentChromeContext? _chromeContext;
private WeatherVisualKind _activeVisualKind = WeatherVisualKind.ClearDay;
private double _animationPhase;
private int _activeParticleCount;
@@ -254,6 +256,13 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
}
}
public void SetComponentChromeContext(ComponentChromeContext context)
{
ArgumentNullException.ThrowIfNull(context);
_chromeContext = context;
ApplyCellSize(_currentCellSize);
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
@@ -261,17 +270,23 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
var scale = ResolveScale();
var hostWidth = Bounds.Width > 1 ? Bounds.Width : Math.Max(140, _currentCellSize * 4);
var hostHeight = Bounds.Height > 1 ? Bounds.Height : Math.Max(78, _currentCellSize * 2);
var cornerRadius = Math.Clamp(_currentCellSize * metrics.CornerRadiusScale, 24, 46);
var cornerRadius = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
24,
46,
_chromeContext);
RootBorder.CornerRadius = new CornerRadius(cornerRadius);
BackgroundImageLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundMotionLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundTintLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundLightLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundShadeLayer.CornerRadius = new CornerRadius(cornerRadius);
ComponentChromeCornerRadiusHelper.Apply(
cornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,
BackgroundTintLayer,
BackgroundLightLayer,
BackgroundShadeLayer);
ContentPaddingBorder.Padding = new Thickness(
Math.Clamp(Math.Min((_currentCellSize * metrics.HorizontalPaddingScale) * scale, hostWidth * 0.034), 4, 22),
Math.Clamp(Math.Min((_currentCellSize * metrics.VerticalPaddingScale) * scale, hostHeight * 0.068), 3, 18));
ComponentChromeCornerRadiusHelper.SafeValue(Math.Min((_currentCellSize * metrics.HorizontalPaddingScale) * scale, hostWidth * 0.034), 4, 22, _chromeContext),
ComponentChromeCornerRadiusHelper.SafeValue(Math.Min((_currentCellSize * metrics.VerticalPaddingScale) * scale, hostHeight * 0.068), 3, 18, _chromeContext));
ApplyAdaptiveTypography();
ResetParticles();
}

View File

@@ -400,8 +400,8 @@ public partial class IfengNewsWidget : UserControl, IDesktopComponentWidget, IRe
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(32 * softScale, 16, 46));
CardBorder.CornerRadius = new CornerRadius(Math.Clamp(32 * softScale, 16, 46));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * softScale, 16, 46);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * softScale, 16, 46);
var horizontalPadding = Math.Clamp(14 * softScale, 8, 20);
var verticalPadding = Math.Clamp(14 * softScale, 8, 20);
@@ -452,7 +452,7 @@ public partial class IfengNewsWidget : UserControl, IDesktopComponentWidget, IRe
visual.ImageHost.Width = imageWidth;
visual.ImageHost.Height = imageHeight;
visual.ImageHost.CornerRadius = new CornerRadius(Math.Clamp(imageHeight * 0.15, 8, 16));
visual.ImageHost.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(imageHeight * 0.15, 8, 16);
visual.TitleTextBlock.MaxWidth = textWidth;
visual.TitleTextBlock.FontSize = titleFont;

View File

@@ -182,8 +182,8 @@ public partial class LunarCalendarWidget : UserControl, IDesktopComponentWidget,
{
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(30 * scale, 16, 44));
RootBorder.Padding = new Thickness(Math.Clamp(16 * scale, 8, 24));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 16, 44);
RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 8, 24));
LayoutRoot.RowSpacing = Math.Clamp(10 * scale, 5, 18);
DividerBorder.Margin = new Thickness(
Math.Clamp(8 * scale, 3, 14),

View File

@@ -217,8 +217,8 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget,
{
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(28 * scale, 14, 40));
RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 8, 22));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(28 * scale, 14, 40);
RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 8, 22));
LayoutRoot.RowSpacing = Math.Clamp(10 * scale, 5, 16);
LayoutRoot.Width = Math.Clamp(280 * scale, 220, 420);
LayoutRoot.Height = Math.Clamp(280 * scale, 220, 420);

View File

@@ -10,13 +10,14 @@ using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentChromeContextAware
{
private enum WeatherVisualKind
{
@@ -115,6 +116,7 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
private WeatherSnapshot? _latestSnapshot;
private string _languageCode = "zh-CN";
private double _currentCellSize = 48;
private ComponentChromeContext? _chromeContext;
private WeatherVisualKind _activeVisualKind = WeatherVisualKind.ClearDay;
private double _animationPhase;
private int _activeParticleCount;
@@ -252,6 +254,13 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
}
}
public void SetComponentChromeContext(ComponentChromeContext context)
{
ArgumentNullException.ThrowIfNull(context);
_chromeContext = context;
ApplyCellSize(_currentCellSize);
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
@@ -259,17 +268,23 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
var scale = ResolveScale();
var hostWidth = Bounds.Width > 1 ? Bounds.Width : Math.Max(140, _currentCellSize * 4);
var hostHeight = Bounds.Height > 1 ? Bounds.Height : Math.Max(78, _currentCellSize * 2);
var cornerRadius = Math.Clamp(_currentCellSize * metrics.CornerRadiusScale, 24, 46);
var cornerRadius = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
24,
46,
_chromeContext);
RootBorder.CornerRadius = new CornerRadius(cornerRadius);
BackgroundImageLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundMotionLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundTintLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundLightLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundShadeLayer.CornerRadius = new CornerRadius(cornerRadius);
ComponentChromeCornerRadiusHelper.Apply(
cornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,
BackgroundTintLayer,
BackgroundLightLayer,
BackgroundShadeLayer);
ContentPaddingBorder.Padding = new Thickness(
Math.Clamp(Math.Min((_currentCellSize * metrics.HorizontalPaddingScale) * scale, hostWidth * 0.034), 4, 22),
Math.Clamp(Math.Min((_currentCellSize * metrics.VerticalPaddingScale) * scale, hostHeight * 0.068), 3, 18));
ComponentChromeCornerRadiusHelper.SafeValue(Math.Min((_currentCellSize * metrics.HorizontalPaddingScale) * scale, hostWidth * 0.034), 4, 22, _chromeContext),
ComponentChromeCornerRadiusHelper.SafeValue(Math.Min((_currentCellSize * metrics.VerticalPaddingScale) * scale, hostHeight * 0.068), 3, 18, _chromeContext));
ApplyAdaptiveTypography();
ResetParticles();
}

View File

@@ -63,15 +63,15 @@ public partial class RecordingWidget : UserControl, IDesktopComponentWidget, IDe
var chromeScale = Math.Clamp(rawScale, 0.62, 2.0);
var contentScale = Math.Clamp(rawScale, 0.74, 1.0);
var rootRadius = Math.Clamp(34 * chromeScale, 16, 56);
RootBorder.CornerRadius = new CornerRadius(rootRadius);
var rootRadius = ComponentChromeCornerRadiusHelper.Scale(34 * chromeScale, 16, 56);
RootBorder.CornerRadius = rootRadius;
RootBorder.Padding = new Thickness(0);
RecorderCardBorder.CornerRadius = new CornerRadius(rootRadius);
RecorderCardBorder.CornerRadius = rootRadius;
RecorderContentGrid.Margin = new Thickness(
Math.Clamp(24 * contentScale, 14, 26),
Math.Clamp(18 * contentScale, 10, 22),
Math.Clamp(24 * contentScale, 14, 26),
Math.Clamp(18 * contentScale, 10, 24));
ComponentChromeCornerRadiusHelper.SafeValue(24 * contentScale, 14, 26),
ComponentChromeCornerRadiusHelper.SafeValue(18 * contentScale, 10, 22),
ComponentChromeCornerRadiusHelper.SafeValue(24 * contentScale, 14, 26),
ComponentChromeCornerRadiusHelper.SafeValue(18 * contentScale, 10, 24));
var sideButtonSize = Math.Clamp(54 * contentScale, 34, 58);
DiscardButtonBorder.Width = sideButtonSize;

View File

@@ -347,13 +347,12 @@ public partial class RemovableStorageWidget : UserControl, IDesktopComponentWidg
var scale = ResolveScale();
var width = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 2;
var cornerRadius = Math.Clamp(_currentCellSize * 0.44, 18, 34);
RootBorder.CornerRadius = new CornerRadius(cornerRadius);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 18, 34);
RootBorder.Padding = new Thickness(
Math.Clamp(16 * scale, 10, 24),
Math.Clamp(15 * scale, 10, 22),
Math.Clamp(16 * scale, 10, 24),
Math.Clamp(15 * scale, 10, 22));
ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(15 * scale, 10, 22),
ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(15 * scale, 10, 22));
LayoutGrid.RowSpacing = Math.Clamp(10 * scale, 8, 16);
HeaderGrid.ColumnSpacing = Math.Clamp(12 * scale, 8, 16);

View File

@@ -602,8 +602,8 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(30 * softScale, 14, 44));
CardBorder.CornerRadius = new CornerRadius(Math.Clamp(30 * softScale, 14, 44));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * softScale, 14, 44);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * softScale, 14, 44);
CardBorder.Padding = new Thickness(
Math.Clamp(12 * softScale, 8, 18),
Math.Clamp(12 * softScale, 8, 18),
@@ -628,7 +628,6 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I
var innerWidth = Math.Max(100, totalWidth - CardBorder.Padding.Left - CardBorder.Padding.Right);
var rowPaddingHorizontal = Math.Clamp(8 * softScale, 5, 14);
var rowPaddingVertical = Math.Clamp(6 * softScale, 3, 10);
var itemCornerRadius = Math.Clamp(10 * softScale, 6, 14);
var avatarSize = Math.Clamp(30 * softScale, 20, 40);
var avatarFont = Math.Clamp(13 * softScale, 9, 18);
var titleFont = Math.Clamp(14 * softScale, 10, 19);
@@ -658,7 +657,7 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I
foreach (var visual in _itemVisuals)
{
visual.Host.CornerRadius = new CornerRadius(itemCornerRadius);
visual.Host.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(10 * softScale, 6, 14);
visual.Host.Padding = new Thickness(rowPaddingHorizontal, rowPaddingVertical);
visual.RowGrid.ColumnSpacing = Math.Clamp(8 * softScale, 4, 12);

View File

@@ -229,7 +229,7 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
_isUltraCompactMode = scale < 0.72 || (Bounds.Width > 1 && Bounds.Width < 300) || (Bounds.Height > 1 && Bounds.Height < 145);
var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.46, 12, 34));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.46, 12, 34);
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale * compactMultiplier, 6, 18),
Math.Clamp(10 * scale * compactMultiplier, 5, 16));

View File

@@ -52,7 +52,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
_currentCellSize = Math.Max(1, cellSize);
var scale = Math.Clamp(_currentCellSize / 48d, 0.82, 2.2);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 10, 28));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 10, 28);
RootBorder.Padding = new Thickness(
Math.Clamp(14 * scale, 8, 20),
Math.Clamp(10 * scale, 6, 16));

View File

@@ -255,7 +255,7 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
_isUltraCompactMode = scale < 0.72 || (Bounds.Width > 1 && Bounds.Width < 295) || (Bounds.Height > 1 && Bounds.Height < 130);
var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.46, 12, 34));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.46, 12, 34);
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale * compactMultiplier, 6, 18),
Math.Clamp(9 * scale * compactMultiplier, 5, 16));

View File

@@ -105,7 +105,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
_currentCellSize = Math.Max(1, cellSize);
var scale = Math.Clamp(_currentCellSize / 48d, 0.78, 2.4);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.44, 14, 42));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 14, 42);
RootBorder.Padding = new Thickness(
Math.Clamp(14 * scale, 8, 22),
Math.Clamp(10 * scale, 6, 16));

View File

@@ -323,7 +323,7 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
_isUltraCompactMode = scale < 0.74 || (Bounds.Width > 1 && Bounds.Width < 300) || (Bounds.Height > 1 && Bounds.Height < 142);
var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.44, 12, 34));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 12, 34);
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale * compactMultiplier, 6, 18),
Math.Clamp(9 * scale * compactMultiplier, 5, 16));

View File

@@ -258,7 +258,7 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0;
var expandedMultiplier = _isExpandedMode ? 1.12 : 1.0;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.50, 14, 42));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.50, 14, 42);
RootBorder.Padding = new Thickness(
Math.Clamp(16 * scale * compactMultiplier * expandedMultiplier, 8, 30),
Math.Clamp(14 * scale * compactMultiplier * expandedMultiplier, 6, 26));
@@ -305,7 +305,7 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
var cardPadding = new Thickness(
Math.Clamp(10 * scale * compactMultiplier * expandedMultiplier, 6, 20),
Math.Clamp(8 * scale * compactMultiplier * expandedMultiplier, 4, 16));
var cardCornerRadius = new CornerRadius(Math.Clamp(10 * scale, 6, 18));
var cardCornerRadius = ComponentChromeCornerRadiusHelper.Scale(10 * scale, 6, 18);
AverageCardBorder.Padding = cardPadding;
MinimumCardBorder.Padding = cardPadding;
MaximumCardBorder.Padding = cardPadding;

View File

@@ -268,7 +268,7 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
_isUltraCompactMode = scale < 0.74 || (Bounds.Width > 1 && Bounds.Width < 180) || (Bounds.Height > 1 && Bounds.Height < 76);
var compactMultiplier = _isUltraCompactMode ? 0.78 : _isCompactMode ? 0.90 : 1.0;
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 10, 28));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 10, 28);
RootBorder.Padding = new Thickness(
Math.Clamp(14 * scale * compactMultiplier, 7, 22),
Math.Clamp(10 * scale * compactMultiplier, 5, 16));

View File

@@ -237,7 +237,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
var rowBorder = new Border
{
CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.20, 8, 14)),
CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.20, 8, 14),
Background = new SolidColorBrush(rowBackground),
BorderBrush = new SolidColorBrush(rowBorderColor),
BorderThickness = new Thickness(1),
@@ -588,7 +588,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
_isCompactMode = scale < 0.92 || (Bounds.Width > 1 && Bounds.Width < 320) || (Bounds.Height > 1 && Bounds.Height < 145);
_isUltraCompactMode = scale < 0.78 || (Bounds.Width > 1 && Bounds.Width < 280) || (Bounds.Height > 1 && Bounds.Height < 120);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.44, 12, 36));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 12, 36);
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 7, 22),
Math.Clamp(9 * scale, 5, 16));
@@ -606,7 +606,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
DialogOverlayBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 8, 20),
Math.Clamp(10 * scale, 8, 18));
DialogCardBorder.CornerRadius = new CornerRadius(Math.Clamp(12 * scale, 10, 18));
DialogCardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(12 * scale, 10, 18);
DialogCardBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 9, 20),
Math.Clamp(11 * scale, 8, 18));

View File

@@ -197,9 +197,9 @@ public partial class TimerWidget : UserControl, IDesktopComponentWidget
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 12, 48));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 12, 48);
RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 7, 22));
TimerPanelBorder.CornerRadius = new CornerRadius(Math.Clamp(32 * scale, 12, 42));
TimerPanelBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * scale, 12, 42);
PlayButtonBorder.Width = Math.Clamp(42 * scale, 28, 58);
PlayButtonBorder.Height = Math.Clamp(42 * scale, 28, 58);

View File

@@ -11,12 +11,13 @@ using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentChromeContextAware
{
private sealed record WeatherClockConfig(
string LanguageCode,
@@ -52,6 +53,7 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
private TimeZoneService? _timeZoneService;
private CancellationTokenSource? _refreshCts;
private double _currentCellSize = 48;
private ComponentChromeContext? _chromeContext;
private bool _isAttached;
private bool _dialInitialized;
private bool _handsInitialized;
@@ -128,6 +130,13 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
RefreshFromSettings();
}
public void SetComponentChromeContext(ComponentChromeContext context)
{
ArgumentNullException.ThrowIfNull(context);
_chromeContext = context;
ApplyCellSize(_currentCellSize);
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
@@ -142,12 +151,24 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
var compactness = Math.Clamp((176 - targetWidth) / 86d, 0, 1);
var ultraCompact = targetWidth < 126 || targetHeight < 46;
var compactFactor = Lerp(1, ultraCompact ? 0.64 : 0.72, compactness);
var cornerRadius = Math.Clamp(targetHeight * metrics.CornerRadiusScale, 15, 36);
var cornerRadius = ComponentChromeCornerRadiusHelper.Scale(
targetHeight * metrics.CornerRadiusScale,
15,
36,
_chromeContext);
var horizontalPadding = Math.Clamp(targetHeight * Lerp(0.18, 0.12, compactness), 5, 30);
var verticalPadding = Math.Clamp(targetHeight * Lerp(0.14, 0.10, compactness), 3, 20);
var horizontalPadding = ComponentChromeCornerRadiusHelper.SafeValue(
targetHeight * Lerp(0.18, 0.12, compactness),
5,
30,
_chromeContext);
var verticalPadding = ComponentChromeCornerRadiusHelper.SafeValue(
targetHeight * Lerp(0.14, 0.10, compactness),
3,
20,
_chromeContext);
RootBorder.CornerRadius = new CornerRadius(cornerRadius);
RootBorder.CornerRadius = cornerRadius;
RootBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
var columnSpacing = Math.Clamp(targetHeight * Lerp(0.16, 0.08, compactness), 2, 22);

View File

@@ -12,13 +12,14 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentChromeContextAware
{
private enum WeatherVisualKind
{
@@ -111,6 +112,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
private WeatherSnapshot? _latestSnapshot;
private string _languageCode = "zh-CN";
private double _currentCellSize = 48;
private ComponentChromeContext? _chromeContext;
private WeatherVisualKind _activeVisualKind = WeatherVisualKind.ClearDay;
private double _animationPhase;
private int _activeParticleCount;
@@ -197,6 +199,13 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
}
}
public void SetComponentChromeContext(ComponentChromeContext context)
{
ArgumentNullException.ThrowIfNull(context);
_chromeContext = context;
ApplyCellSize(_currentCellSize);
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
@@ -204,19 +213,25 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
var metrics = HyperOS3WeatherTheme.ResolveMetrics(HyperOS3WeatherWidgetKind.Realtime2x2);
var hostWidth = Bounds.Width > 1 ? Bounds.Width : Math.Max(80, _currentCellSize * 2);
var hostHeight = Bounds.Height > 1 ? Bounds.Height : Math.Max(80, _currentCellSize * 2);
var cornerRadius = Math.Clamp(_currentCellSize * metrics.CornerRadiusScale, 26, 46);
var cornerRadius = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
26,
46,
_chromeContext);
var horizontalPadding = Math.Clamp(_currentCellSize * metrics.HorizontalPaddingScale, 10, 24);
var verticalPadding = Math.Clamp(_currentCellSize * metrics.VerticalPaddingScale, 10, 24);
RootBorder.CornerRadius = new CornerRadius(cornerRadius);
BackgroundImageLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundMotionLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundTintLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundLightLayer.CornerRadius = new CornerRadius(cornerRadius);
BackgroundShadeLayer.CornerRadius = new CornerRadius(cornerRadius);
ComponentChromeCornerRadiusHelper.Apply(
cornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,
BackgroundTintLayer,
BackgroundLightLayer,
BackgroundShadeLayer);
ContentPaddingBorder.Padding = new Thickness(
Math.Clamp(Math.Min(horizontalPadding * scale, hostWidth * 0.12), 3, 24),
Math.Clamp(Math.Min(verticalPadding * scale, hostHeight * 0.12), 3, 24));
ComponentChromeCornerRadiusHelper.SafeValue(Math.Min(horizontalPadding * scale, hostWidth * 0.12), 3, 24, _chromeContext),
ComponentChromeCornerRadiusHelper.SafeValue(Math.Min(verticalPadding * scale, hostHeight * 0.12), 3, 24, _chromeContext));
ApplyAdaptiveTypography();
ResetParticles();
}

View File

@@ -117,11 +117,13 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
var toolbarPaddingHorizontal = Math.Clamp(buttonSize * 0.36, 6, 12);
var toolbarPaddingVertical = Math.Clamp(buttonSize * 0.24, 4, 8);
RootBorder.Padding = new Thickness(Math.Clamp(_currentCellSize * 0.14, 6, 14));
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 12, 28));
CanvasBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.24, 10, 22));
ToolbarBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.22, 10, 20));
ToolbarBorder.Padding = new Thickness(toolbarPaddingHorizontal, toolbarPaddingVertical);
RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(_currentCellSize * 0.14, 6, 14));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 12, 28);
CanvasBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.24, 10, 22);
ToolbarBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.22, 10, 20);
ToolbarBorder.Padding = new Thickness(
ComponentChromeCornerRadiusHelper.SafeValue(toolbarPaddingHorizontal, 6, 12),
ComponentChromeCornerRadiusHelper.SafeValue(toolbarPaddingVertical, 4, 8));
ToolbarButtonsPanel.Spacing = toolbarSpacing;
foreach (var button in new[] { PenButton, EraserButton, ClearButton, ExportButton })

View File

@@ -170,7 +170,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
var horizontalPadding = Math.Clamp(10 * scale, 4, 26);
var verticalPadding = Math.Clamp(8 * scale, 3, 22);
RootBorder.Padding = new Thickness(horizontalPadding, verticalPadding);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(24 * scale, 10, 46));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(24 * scale, 10, 46);
var usableWidth = Math.Max(48, totalWidth - horizontalPadding * 2);
var usableHeight = Math.Max(28, totalHeight - verticalPadding * 2);

View File

@@ -13,11 +13,13 @@ using Avalonia.VisualTree;
using FluentIcons.Avalonia;
using FluentIcons.Common;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Theme;
using LanMountainDesktop.Views.Components;
using PathShape = Avalonia.Controls.Shapes.Path;
namespace LanMountainDesktop.Views;
@@ -579,13 +581,18 @@ public partial class MainWindow
{
var window = new ComponentLibraryWindow(
_componentLibraryService,
cellSize => new ComponentLibraryCreateContext(
cellSize,
_timeZoneService,
_weatherDataService,
_recommendationInfoService,
_calculatorDataService,
_settingsFacade),
cellSize =>
{
var appearanceSnapshot = HostAppearanceThemeProvider.GetOrCreate().GetCurrent();
return new ComponentLibraryCreateContext(
cellSize,
appearanceSnapshot.GlobalCornerRadiusScale,
_timeZoneService,
_weatherDataService,
_recommendationInfoService,
_calculatorDataService,
_settingsFacade);
},
L);
window.AddComponentRequested += OnDetachedComponentLibraryAddComponentRequested;
window.Closed += OnDetachedComponentLibraryClosed;
@@ -1258,7 +1265,7 @@ public partial class MainWindow
Height = handleVisualSize,
IsHitTestVisible = false
};
resizeHandleVisual.Children.Add(new Path
resizeHandleVisual.Children.Add(new PathShape
{
Data = arcData,
Stretch = Stretch.Fill,
@@ -1266,7 +1273,7 @@ public partial class MainWindow
StrokeThickness = arcThickness + 3,
StrokeLineCap = PenLineCap.Round
});
resizeHandleVisual.Children.Add(new Path
resizeHandleVisual.Children.Add(new PathShape
{
Data = arcData,
Stretch = Stretch.Fill,
@@ -1513,12 +1520,20 @@ public partial class MainWindow
private double GetComponentCornerRadius(string componentId)
{
var appearanceSnapshot = HostAppearanceThemeProvider.GetOrCreate().GetCurrent();
if (_componentRuntimeRegistry.TryGetDescriptor(componentId, out var runtimeDescriptor))
{
return runtimeDescriptor.ResolveCornerRadius(_currentDesktopCellSize);
return runtimeDescriptor.ResolveCornerRadius(new ComponentChromeContext(
componentId,
null,
_currentDesktopCellSize,
appearanceSnapshot.GlobalCornerRadiusScale,
appearanceSnapshot.CornerRadiusTokens));
}
return Math.Clamp(_currentDesktopCellSize * 0.22, 8, 18);
var scale = Math.Max(0.1d, appearanceSnapshot.GlobalCornerRadiusScale);
return Math.Clamp(_currentDesktopCellSize * 0.22, 8, 18) * scale;
}
private Thickness GetDesktopComponentVisualInset(int widthCells, int heightCells)
@@ -1767,8 +1782,10 @@ public partial class MainWindow
{
try
{
var appearanceSnapshot = HostAppearanceThemeProvider.GetOrCreate().GetCurrent();
var createContext = new ComponentLibraryCreateContext(
cellSize,
appearanceSnapshot.GlobalCornerRadiusScale,
_timeZoneService,
_weatherDataService,
_recommendationInfoService,

View File

@@ -83,7 +83,7 @@
Background="{Binding NeutralLightPreviewBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewNeutralLightLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -94,7 +94,7 @@
Background="{Binding NeutralDarkPreviewBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewNeutralDarkLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -110,7 +110,7 @@
Background="{Binding PrimarySwatchBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewPrimaryLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -122,7 +122,7 @@
Background="{Binding SecondarySwatchBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewSecondaryLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -134,7 +134,7 @@
Background="{Binding TertiarySwatchBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewTertiaryLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -146,7 +146,7 @@
Background="{Binding NeutralSwatchBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewNeutralLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -177,7 +177,7 @@
Background="{Binding SeedSwatchBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewSeedLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />
@@ -217,7 +217,7 @@
Background="{Binding Brush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="12" />
CornerRadius="{DynamicResource DesignCornerRadiusXs}" />
<TextBlock Text="{Binding Label}"
HorizontalAlignment="Center"
TextAlignment="Center"
@@ -240,7 +240,7 @@
Background="{Binding SeedSwatchBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="14" />
CornerRadius="{DynamicResource DesignCornerRadiusSm}" />
<TextBlock Text="{Binding PreviewSeedLabel}"
HorizontalAlignment="Center"
TextAlignment="Center" />

View File

@@ -8,16 +8,14 @@
x:DataType="vm:ComponentsSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<!-- 网格布局设置分组 -->
<controls:IconText Icon="GridDots"
Text="{Binding GridHeader}"
<controls:IconText Icon="Apps"
Text="{Binding ComponentsHeader}"
Margin="0,0,0,4" />
<ui:SettingsExpander Header="{Binding GridHeader}"
<ui:SettingsExpander Header="{Binding ComponentsHeader}"
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="GridDots" />
<fi:SymbolIconSource Symbol="Apps" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="16">
@@ -71,6 +69,33 @@
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
<controls:IconText Icon="ShapeOrganic"
Text="{Binding ComponentRadiusHeader}"
Margin="0,12,0,4" />
<ui:SettingsExpander Header="{Binding GlobalCornerRadiusLabel}"
Description="{Binding GlobalCornerRadiusDescription}">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ShapeOrganic" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="16">
<TextBlock Text="{Binding GlobalCornerRadiusLabel}"
VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0.7"
Maximum="1.4"
IsSnapToTickEnabled="True"
TickFrequency="0.05"
Value="{Binding GlobalCornerRadiusScale}" />
<TextBlock Grid.Column="2"
Width="48"
Text="{Binding GlobalCornerRadiusScale, StringFormat={}{0:F2}x}"
VerticalAlignment="Center"
HorizontalAlignment="Right" />
</Grid>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -8,7 +8,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
"components",
"Components",
SettingsPageCategory.Components,
IconKey = "GridDots",
IconKey = "Apps",
SortOrder = 20,
TitleLocalizationKey = "settings.components.title",
DescriptionLocalizationKey = "settings.components.description")]

View File

@@ -515,7 +515,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
Background = isSelected ? SelectedSurfaceBrush : SurfaceBrush,
BorderBrush = isSelected ? SelectedBorderBrush : CardBorderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(18),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusMd", 18),
Padding = new Thickness(14),
Child = layoutGrid
};
@@ -597,7 +597,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
detailPanel.Children.Add(new Border
{
Background = new SolidColorBrush(Color.Parse("#24FFC42B1C")),
CornerRadius = new CornerRadius(14),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusSm", 14),
Padding = new Thickness(12),
Child = new TextBlock
{
@@ -617,7 +617,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
Background = SurfaceBrush,
BorderBrush = CardBorderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(16),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusMd", 16),
Padding = new Thickness(16),
Child = new TextBlock
{
@@ -1011,7 +1011,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
return new Border
{
Background = SurfaceBrush,
CornerRadius = new CornerRadius(18),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusMd", 18),
Padding = new Thickness(padding)
};
}
@@ -1021,7 +1021,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
return new Border
{
Background = SurfaceBrush,
CornerRadius = new CornerRadius(16),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusMd", 16),
BorderBrush = CardBorderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(18),
@@ -1124,7 +1124,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
Background = SurfaceBrush,
BorderBrush = CardBorderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(14),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusSm", 14),
Padding = new Thickness(14),
Child = new StackPanel
{
@@ -1154,7 +1154,7 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
Background = SurfaceBrush,
BorderBrush = CardBorderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(14),
CornerRadius = ResolveCornerRadiusResource("DesignCornerRadiusSm", 14),
Padding = new Thickness(14),
Child = new StackPanel
{
@@ -1257,4 +1257,11 @@ internal sealed class PluginMarketEmbeddedView : UserControl, IDisposable
_ => "Install"
};
}
private static CornerRadius ResolveCornerRadiusResource(string key, double fallback)
{
return Application.Current?.TryFindResource(key, out var resource) == true && resource is CornerRadius radius
? radius
: new CornerRadius(fallback);
}
}