This commit is contained in:
lincube
2026-03-20 18:05:42 +08:00
parent 65a3cf832a
commit 20cd6041a7
15 changed files with 170 additions and 30 deletions

View File

@@ -33,7 +33,7 @@ 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);
GlobalCornerRadiusScale = Math.Max(0d, globalCornerRadiusScale);
CornerRadiusTokens = cornerRadiusTokens;
PluginSettings = pluginSettings;
}

View File

@@ -3,9 +3,8 @@ 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 const double MinimumCornerRadiusScale = 0.0;
public const double MaximumCornerRadiusScale = 2.50;
public static double NormalizeCornerRadiusScale(double value)
{
@@ -14,7 +13,6 @@ public static class GlobalAppearanceSettings
return DefaultCornerRadiusScale;
}
var clamped = Math.Clamp(value, MinimumCornerRadiusScale, MaximumCornerRadiusScale);
return Math.Round(clamped / CornerRadiusScaleStep, MidpointRounding.AwayFromZero) * CornerRadiusScaleStep;
return Math.Clamp(value, MinimumCornerRadiusScale, MaximumCornerRadiusScale);
}
}

View File

@@ -0,0 +1,67 @@
using Avalonia;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Settings.Core;
using LanMountainDesktop.Shared.Contracts;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class CornerRadiusScaleTests
{
[Theory]
[InlineData(-1d, 0d)]
[InlineData(0d, 0d)]
[InlineData(0.33d, 0.33d)]
[InlineData(1.234d, 1.234d)]
[InlineData(2.5d, 2.5d)]
[InlineData(3d, 2.5d)]
public void NormalizeCornerRadiusScale_ClampsWithoutSnapping(double input, double expected)
{
Assert.Equal(expected, GlobalAppearanceSettings.NormalizeCornerRadiusScale(input), 3);
}
[Fact]
public void NormalizeCornerRadiusScale_UsesDefaultForInvalidValues()
{
Assert.Equal(
GlobalAppearanceSettings.DefaultCornerRadiusScale,
GlobalAppearanceSettings.NormalizeCornerRadiusScale(double.NaN),
3);
Assert.Equal(
GlobalAppearanceSettings.DefaultCornerRadiusScale,
GlobalAppearanceSettings.NormalizeCornerRadiusScale(double.PositiveInfinity),
3);
}
[Fact]
public void PluginDesktopComponentContext_AllowsZeroRadiusScaling()
{
var context = new PluginDesktopComponentContext(
new PluginManifest("plugin.id", "Plugin Name", "plugin.dll"),
"C:\\Plugins\\plugin.id",
"C:\\Data\\plugin.id",
new NullServiceProvider(),
new Dictionary<string, object?>(),
"component-1",
null,
96d,
0d,
new AppearanceCornerRadiusTokens(
new CornerRadius(6),
new CornerRadius(10),
new CornerRadius(14),
new CornerRadius(18),
new CornerRadius(24),
new CornerRadius(30),
new CornerRadius(36)));
Assert.Equal(0d, context.GlobalCornerRadiusScale, 3);
Assert.Equal(0d, context.ResolveScaledCornerRadius(12d), 3);
Assert.Equal(0d, context.ResolveScaledCornerRadius(12d, 8d, 18d), 3);
}
private sealed class NullServiceProvider : IServiceProvider
{
public object? GetService(Type serviceType) => null;
}
}

View File

@@ -0,0 +1,58 @@
using Avalonia;
using Avalonia.Controls;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Shared.Contracts;
using LanMountainDesktop.Views.Components;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class DesktopComponentRuntimeRegistrationCornerRadiusTests
{
[Fact]
public void LegacyCellSizeResolver_AppliesGlobalCornerRadiusScale()
{
var registration = new DesktopComponentRuntimeRegistration(
componentId: "test.component",
displayNameLocalizationKey: null,
controlFactory: () => new Border(),
cornerRadiusResolver: cellSize => Math.Clamp(cellSize * 0.30, 10, 40));
var resolver = Assert.IsType<Func<ComponentChromeContext, double>>(registration.CornerRadiusResolver);
var resolved = resolver(CreateChromeContext(cellSize: 120, globalScale: 2.0));
Assert.Equal(72.0, resolved, 3);
}
[Fact]
public void ChromeContextResolver_IsNotDoubleScaledByRegistrationWrapper()
{
var registration = new DesktopComponentRuntimeRegistration(
componentId: "test.component",
displayNameLocalizationKey: null,
controlFactory: _ => new Border(),
cornerRadiusResolver: chromeContext => chromeContext.CellSize + chromeContext.GlobalCornerRadiusScale);
var resolver = Assert.IsType<Func<ComponentChromeContext, double>>(registration.CornerRadiusResolver);
var resolved = resolver(CreateChromeContext(cellSize: 50, globalScale: 2.5));
Assert.Equal(52.5, resolved, 3);
}
private static ComponentChromeContext CreateChromeContext(double cellSize, double globalScale)
{
return new ComponentChromeContext(
ComponentId: "test.component",
PlacementId: null,
CellSize: cellSize,
GlobalCornerRadiusScale: globalScale,
CornerRadiusTokens: new AppearanceCornerRadiusTokens(
new CornerRadius(6),
new CornerRadius(10),
new CornerRadius(14),
new CornerRadius(18),
new CornerRadius(24),
new CornerRadius(30),
new CornerRadius(36)));
}
}

View File

@@ -313,7 +313,7 @@
"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.components.corner_radius.description": "Adjust the shared corner radius from a square edge to a capsule-like shape, 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

@@ -312,7 +312,7 @@
"settings.components.spacing_relaxed": "宽松",
"settings.components.corner_radius.header": "圆角设计",
"settings.components.corner_radius.label": "组件圆角",
"settings.components.corner_radius.description": "统一调整组件容器圆角,并随圆角增大同步扩展内部安全区。",
"settings.components.corner_radius.description": "组件容器圆角从直角连续调到接近胶囊的形态,并随圆角增大同步扩展内部安全区。",
"settings.update.title": "更新",
"settings.update.current_version_label": "当前版本",
"settings.update.latest_version_label": "最新发布",

View File

@@ -1010,6 +1010,10 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
[ObservableProperty]
private double _globalCornerRadiusScale = GlobalAppearanceSettings.DefaultCornerRadiusScale;
public double GlobalCornerRadiusMinimum => GlobalAppearanceSettings.MinimumCornerRadiusScale;
public double GlobalCornerRadiusMaximum => GlobalAppearanceSettings.MaximumCornerRadiusScale;
[ObservableProperty]
private string _componentRadiusHeader = string.Empty;
@@ -1129,7 +1133,9 @@ public sealed partial class ComponentsSettingsPageViewModel : ViewModelBase
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.");
GlobalCornerRadiusDescription = L(
"settings.components.corner_radius.description",
"Adjust the shared corner radius from a square edge to a capsule-like shape, and expand the internal safe area with it.");
}
private string L(string key, string fallback)

View File

@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Media;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Services;
using LanMountainDesktop.Settings.Core;
namespace LanMountainDesktop.Views.Components;
@@ -12,10 +13,12 @@ internal static class ComponentChromeCornerRadiusHelper
{
if (chromeContext is not null)
{
return Math.Max(0.1d, chromeContext.GlobalCornerRadiusScale);
return Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, chromeContext.GlobalCornerRadiusScale);
}
return Math.Max(0.1d, HostAppearanceThemeProvider.GetOrCreate().GetCurrent().GlobalCornerRadiusScale);
return Math.Max(
GlobalAppearanceSettings.MinimumCornerRadiusScale,
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().GlobalCornerRadiusScale);
}
public static CornerRadius Scale(double baseRadius, double min, double max, ComponentChromeContext? chromeContext = null)

View File

@@ -9,6 +9,7 @@ using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Settings.Core;
namespace LanMountainDesktop.Views.Components;
@@ -36,7 +37,10 @@ public sealed class DesktopComponentRuntimeRegistration
componentId,
displayNameLocalizationKey,
_ => controlFactory(),
cornerRadiusResolver is null ? null : chromeContext => cornerRadiusResolver(chromeContext.CellSize))
cornerRadiusResolver is null
? null
: chromeContext => cornerRadiusResolver(chromeContext.CellSize) *
Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, chromeContext.GlobalCornerRadiusScale))
{
}
@@ -49,7 +53,10 @@ public sealed class DesktopComponentRuntimeRegistration
componentId,
displayNameLocalizationKey,
controlFactory,
cornerRadiusResolver is null ? null : chromeContext => cornerRadiusResolver(chromeContext.CellSize))
cornerRadiusResolver is null
? null
: chromeContext => cornerRadiusResolver(chromeContext.CellSize) *
Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, chromeContext.GlobalCornerRadiusScale))
{
}
@@ -84,7 +91,7 @@ public sealed class DesktopComponentRuntimeDescriptor
private static readonly Func<ComponentChromeContext, double> DefaultCornerRadiusResolver =
chromeContext =>
{
var scale = Math.Max(0.1d, chromeContext.GlobalCornerRadiusScale);
var scale = Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, chromeContext.GlobalCornerRadiusScale);
var baseRadius = Math.Clamp(chromeContext.CellSize * 0.22, 8, 18);
return Math.Clamp(baseRadius * scale, 8 * scale, 18 * scale);
};
@@ -492,8 +499,9 @@ public sealed class DesktopComponentRuntimeRegistry
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopOfficeRecentDocuments,
"component.office_recent_documents",
() => new OfficeRecentDocumentsWidget(),
cellSize => Math.Clamp(cellSize * 0.50, 10, 24)),
_ => new OfficeRecentDocumentsWidget(),
chromeContext => Math.Clamp(chromeContext.CellSize * 0.50, 10, 24) *
Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, chromeContext.GlobalCornerRadiusScale)),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopRemovableStorage,
"component.removable_storage",

View File

@@ -63,8 +63,7 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
var rootRadius = Math.Clamp(30 * scale, 16, 44);
var rootCornerRadius = new CornerRadius(rootRadius);
var rootCornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 16, 44);
RootBorder.CornerRadius = rootCornerRadius;
ContentPaddingBorder.Padding = new Thickness(
@@ -85,7 +84,7 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
CoverBorder.Width = Math.Clamp(56 * scale, 38, 86);
CoverBorder.Height = Math.Clamp(56 * scale, 38, 86);
CoverBorder.CornerRadius = new CornerRadius(Math.Clamp(12 * scale, 8, 16));
CoverBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(12 * scale, 8, 16);
TitleTextBlock.FontSize = Math.Clamp(20 * scale, 12, 28);
ArtistTextBlock.FontSize = Math.Clamp(14 * scale, 9, 18);

View File

@@ -10,7 +10,7 @@
x:Class="LanMountainDesktop.Views.Components.OfficeRecentDocumentsWidget">
<Border x:Name="RootBorder"
CornerRadius="34"
CornerRadius="{DynamicResource DesignCornerRadiusIsland}"
Background="#2D5A8E"
ClipToBounds="True"
BorderThickness="0"
@@ -39,7 +39,7 @@
Grid.Column="1"
Width="28"
Height="28"
CornerRadius="14"
CornerRadius="{DynamicResource DesignCornerRadiusSm}"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
@@ -67,7 +67,7 @@
<Border x:Name="DocumentCard"
Width="130"
Height="90"
CornerRadius="10"
CornerRadius="{DynamicResource DesignCornerRadiusXs}"
Background="#3AFFFFFF"
Padding="10"
Cursor="Hand"

View File

@@ -36,8 +36,7 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
return;
}
var scale = cellSize / 100.0;
RootBorder.CornerRadius = new Avalonia.CornerRadius(Math.Max(8, 34 * scale));
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(cellSize * 0.50, 10, 24);
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)

View File

@@ -17,6 +17,7 @@ using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Settings.Core;
using LanMountainDesktop.Theme;
using LanMountainDesktop.Views.Components;
using PathShape = Avalonia.Controls.Shapes.Path;
@@ -1532,7 +1533,7 @@ public partial class MainWindow
appearanceSnapshot.CornerRadiusTokens));
}
var scale = Math.Max(0.1d, appearanceSnapshot.GlobalCornerRadiusScale);
var scale = Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, appearanceSnapshot.GlobalCornerRadiusScale);
return Math.Clamp(_currentDesktopCellSize * 0.22, 8, 18) * scale;
}

View File

@@ -516,6 +516,7 @@ public partial class MainWindow
SystemMaterialMode = latestThemeState.SystemMaterialMode,
SelectedWallpaperSeed = latestThemeState.SelectedWallpaperSeed,
UseSystemChrome = latestThemeState.UseSystemChrome,
GlobalCornerRadiusScale = latestThemeState.GlobalCornerRadiusScale,
WallpaperPath = latestWallpaperState.WallpaperPath,
WallpaperType = latestWallpaperState.Type,
WallpaperColor = string.Equals(latestWallpaperState.Type, "SolidColor", StringComparison.OrdinalIgnoreCase)

View File

@@ -83,13 +83,13 @@
<TextBlock Text="{Binding GlobalCornerRadiusLabel}"
VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0.7"
Maximum="1.4"
IsSnapToTickEnabled="True"
TickFrequency="0.05"
Minimum="{Binding GlobalCornerRadiusMinimum}"
Maximum="{Binding GlobalCornerRadiusMaximum}"
SmallChange="0.01"
LargeChange="0.1"
Value="{Binding GlobalCornerRadiusScale}" />
<TextBlock Grid.Column="2"
Width="48"
Width="56"
Text="{Binding GlobalCornerRadiusScale, StringFormat={}{0:F2}x}"
VerticalAlignment="Center"
HorizontalAlignment="Right" />