This commit is contained in:
lincube
2026-03-20 22:37:37 +08:00
parent 20cd6041a7
commit 33baaa579d
92 changed files with 1149 additions and 2752 deletions

View File

@@ -9,6 +9,7 @@ using Avalonia.Layout;
using Avalonia.Media;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.ComponentSystem.Extensions;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Views.Components;
@@ -62,7 +63,11 @@ public static class DesktopComponentRegistryFactory
registration.ComponentId,
registration.DisplayNameLocalizationKey,
factoryContext => CreatePluginControl(contribution, factoryContext),
registration.CornerRadiusResolver));
chromeContext =>
{
var appearanceContext = CreatePluginAppearanceContext(chromeContext);
return registration.ResolveCornerRadius(appearanceContext, chromeContext.CellSize);
}));
}
}
@@ -123,6 +128,10 @@ public static class DesktopComponentRegistryFactory
contribution.Plugin.Manifest.Id,
settingsService);
var appearanceSnapshot = HostAppearanceThemeProvider.GetOrCreate().GetCurrent();
var pluginAppearance = new PluginAppearanceContext(new PluginAppearanceSnapshot(
GlobalCornerRadiusScale: appearanceSnapshot.GlobalCornerRadiusScale,
CornerRadiusTokens: PluginCornerRadiusTokens.FromShared(appearanceSnapshot.CornerRadiusTokens),
ThemeVariant: appearanceSnapshot.IsNightMode ? "Dark" : "Light"));
var pluginContext = new PluginDesktopComponentContext(
contribution.Plugin.Manifest,
contribution.Plugin.Context.PluginDirectory,
@@ -132,8 +141,7 @@ public static class DesktopComponentRegistryFactory
contribution.Registration.ComponentId,
context.PlacementId,
context.CellSize,
appearanceSnapshot.GlobalCornerRadiusScale,
appearanceSnapshot.CornerRadiusTokens,
pluginAppearance,
pluginSettings);
return contribution.Registration.ControlFactory(contribution.Plugin.Services, pluginContext);
@@ -146,6 +154,14 @@ public static class DesktopComponentRegistryFactory
}
}
private static IPluginAppearanceContext CreatePluginAppearanceContext(ComponentChromeContext chromeContext)
{
return new PluginAppearanceContext(new PluginAppearanceSnapshot(
GlobalCornerRadiusScale: chromeContext.GlobalCornerRadiusScale,
CornerRadiusTokens: PluginCornerRadiusTokens.FromShared(chromeContext.CornerRadiusTokens),
ThemeVariant: "Unknown"));
}
private static Control CreatePluginErrorControl(
PluginDesktopComponentContribution contribution,
Exception exception)

View File

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

View File

@@ -381,12 +381,13 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
var unifiedMainRectangle = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = unifiedMainRectangle;
RootBorder.Padding = new Thickness(0);
var horizontalPadding = Math.Clamp(16 * softScale, 8, 24);
var verticalPadding = Math.Clamp(14 * softScale, 7, 20);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
CardBorder.CornerRadius = unifiedMainRectangle;
CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
var innerWidth = Math.Max(120, totalWidth - (horizontalPadding * 2d));
@@ -615,6 +616,11 @@ public partial class BaiduHotSearchWidget : UserControl, IDesktopComponentWidget
return Math.Clamp(Math.Min(scaleX, scaleY), 0.72, 2.8);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);

View File

@@ -386,12 +386,13 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
var unifiedMainRectangle = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = unifiedMainRectangle;
RootBorder.Padding = new Thickness(0);
var horizontalPadding = Math.Clamp(16 * softScale, 8, 24);
var verticalPadding = Math.Clamp(14 * softScale, 7, 20);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52);
CardBorder.CornerRadius = unifiedMainRectangle;
CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
var innerWidth = Math.Max(120, totalWidth - (horizontalPadding * 2d));
@@ -629,6 +630,11 @@ public partial class BilibiliHotSearchWidget : UserControl, IDesktopComponentWid
return Math.Clamp(Math.Min(scaleX, scaleY), 0.72, 2.8);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);

View File

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

View File

@@ -613,7 +613,7 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
? CreateBrush("#FF4FC3F7")
: CreateBrush("#FF3250");
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.45, 24, 44);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.Background = _isNightVisual
? CreateGradientBrush("#171A21", "#0C0E14")
: CreateGradientBrush("#F7F8FC", "#ECEFF6");

View File

@@ -132,8 +132,8 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
var targetHeight = Math.Clamp(cellSize * 0.74, 34, 74);
RootBorder.Height = targetHeight;
// 2. 动态圆角:确保始终是完美的胶囊半圆
RootBorder.CornerRadius = new CornerRadius(targetHeight / 2);
// 2. 主矩形统一到主题主档圆角
RootBorder.CornerRadius = ResolveUnifiedMainRectangle();
RootBorder.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center;
// 3. 核心:满盈字阶 (Filled Typography)
@@ -189,4 +189,9 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
RootBorder.ClearValue(Border.BorderThicknessProperty);
RootBorder.ClearValue(Border.BoxShadowProperty);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
}

View File

@@ -545,10 +545,11 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
var scale = ResolveScale();
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
var unifiedMainRectangle = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = unifiedMainRectangle;
RootBorder.Padding = new Thickness(0);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
CardBorder.CornerRadius = unifiedMainRectangle;
CardBorder.Padding = new Thickness(
Math.Clamp(16 * scale, 8, 24),
Math.Clamp(14 * scale, 7, 22),
@@ -865,6 +866,11 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.56, 2.0);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private static string NormalizeCompactText(string? text)
{
if (string.IsNullOrWhiteSpace(text))

View File

@@ -9,6 +9,25 @@ namespace LanMountainDesktop.Views.Components;
internal static class ComponentChromeCornerRadiusHelper
{
public static double ResolveMainRectangleRadiusValue(ComponentChromeContext? chromeContext = null, double fallback = 24d)
{
if (chromeContext is not null)
{
return Math.Max(0d, chromeContext.CornerRadiusTokens.Lg.TopLeft);
}
var snapshot = HostAppearanceThemeProvider.GetOrCreate().GetCurrent();
var resolved = snapshot.CornerRadiusTokens.Lg.TopLeft;
return double.IsFinite(resolved)
? Math.Max(0d, resolved)
: Math.Max(0d, fallback * ResolveScale(chromeContext));
}
public static CornerRadius ResolveMainRectangleRadius(ComponentChromeContext? chromeContext = null, double fallback = 24d)
{
return new CornerRadius(ResolveMainRectangleRadiusValue(chromeContext, fallback));
}
public static double ResolveScale(ComponentChromeContext? chromeContext = null)
{
if (chromeContext is not null)

View File

@@ -101,7 +101,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
RootBorder.CornerRadius = ResolveUnifiedMainRectangle();
InfoPanel.Padding = new Thickness(
Math.Clamp(18 * scale, 10, 28),
@@ -754,6 +754,11 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.56, 2.0);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private static string NormalizeCompactText(string? text)
{
if (string.IsNullOrWhiteSpace(text))

View File

@@ -92,7 +92,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
RootBorder.CornerRadius = ResolveUnifiedMainRectangle();
RootBorder.Padding = new Thickness(
Math.Clamp(20 * scale, 10, 34),
Math.Clamp(16 * scale, 8, 28),
@@ -452,6 +452,11 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.52, 2.2);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private void ApplyAdaptiveTextLayout(bool isNightMode, double scale, double totalWidth, double totalHeight)
{
var padding = RootBorder.Padding;

View File

@@ -328,8 +328,9 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 14, 40);
CardBorder.CornerRadius = RootBorder.CornerRadius;
var unifiedMainRectangle = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = unifiedMainRectangle;
CardBorder.CornerRadius = unifiedMainRectangle;
CardBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 8, 18),
Math.Clamp(11 * scale, 7, 16),
@@ -482,6 +483,11 @@ public partial class DailyWord2x2Widget : UserControl, IDesktopComponentWidget,
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.56, 2.0);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);

View File

@@ -298,7 +298,7 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
isFourByThree = widthRatio >= 0.9 && heightRatio >= 1.35;
}
var containerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52);
var containerRadius = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = containerRadius;
RootBorder.Padding = new Thickness(0);
@@ -527,6 +527,11 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.56, 2.0);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private string BuildPronunciationText(DailyWordSnapshot snapshot)
{
var uk = NormalizeCompactText(snapshot.UkPronunciation);

View File

@@ -324,8 +324,9 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon
private void ApplyAdaptiveTypography()
{
var scale = ResolveScale();
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(28 * scale, 16, 40);
RootBorder.CornerRadius = mainRectangleCornerRadius;
RootBorder.Padding = new Thickness(Math.Clamp(11 * scale, 7, 17));
LayoutRoot.ColumnSpacing = Math.Clamp(10 * scale, 6, 16);
@@ -337,7 +338,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 = ComponentChromeCornerRadiusHelper.Scale(24 * scale, 14, 34);
LunarCardBorder.CornerRadius = mainRectangleCornerRadius;
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

@@ -89,12 +89,7 @@ public sealed class DesktopComponentRuntimeRegistration
public sealed class DesktopComponentRuntimeDescriptor
{
private static readonly Func<ComponentChromeContext, double> DefaultCornerRadiusResolver =
chromeContext =>
{
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);
};
chromeContext => ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadiusValue(chromeContext);
private readonly Func<DesktopComponentControlFactoryContext, Control> _controlFactory;
private readonly Func<ComponentChromeContext, double> _cornerRadiusResolver;
@@ -324,194 +319,155 @@ public sealed class DesktopComponentRuntimeRegistry
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.Date,
"component.date",
() => new DateWidget(),
_ => 16),
() => new DateWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.MonthCalendar,
"component.month_calendar",
() => new MonthCalendarWidget(),
cellSize => Math.Clamp(cellSize * 0.26, 10, 22)),
() => new MonthCalendarWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.LunarCalendar,
"component.lunar_calendar",
() => new LunarCalendarWidget(),
cellSize => Math.Clamp(cellSize * 0.30, 12, 26)),
() => new LunarCalendarWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopClock,
"component.desktop_clock",
() => new AnalogClockWidget(),
cellSize => Math.Clamp(cellSize * 0.30, 12, 28)),
() => new AnalogClockWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopWeatherClock,
"component.weather_clock",
() => new WeatherClockWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new WeatherClockWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopWorldClock,
"component.world_clock",
() => new WorldClockWidget(),
cellSize => Math.Clamp(cellSize * 0.30, 10, 24)),
() => new WorldClockWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopTimer,
"component.desktop_timer",
() => new TimerWidget(),
cellSize => Math.Clamp(cellSize * 0.30, 12, 28)),
() => new TimerWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopWeather,
"component.desktop_weather",
() => new WeatherWidget(),
cellSize => Math.Clamp(cellSize * 0.45, 24, 44)),
() => new WeatherWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopHourlyWeather,
"component.hourly_weather",
() => new HourlyWeatherWidget(),
cellSize => Math.Clamp(cellSize * 0.45, 24, 44)),
() => new HourlyWeatherWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopMultiDayWeather,
"component.multiday_weather",
() => new MultiDayWeatherWidget(),
cellSize => Math.Clamp(cellSize * 0.45, 24, 44)),
() => new MultiDayWeatherWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopExtendedWeather,
"component.extended_weather",
() => new ExtendedWeatherWidget(),
cellSize => Math.Clamp(cellSize * 0.45, 24, 44)),
() => new ExtendedWeatherWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopClassSchedule,
"component.class_schedule",
() => new ClassScheduleWidget(),
cellSize => Math.Clamp(cellSize * 0.45, 24, 44)),
() => new ClassScheduleWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopMusicControl,
"component.music_control",
() => new MusicControlWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new MusicControlWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopAudioRecorder,
"component.audio_recorder",
() => new RecordingWidget(),
cellSize => Math.Clamp(cellSize * 0.36, 16, 34)),
() => new RecordingWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudyEnvironment,
"component.study_environment",
() => new StudyEnvironmentWidget(),
cellSize => Math.Clamp(cellSize * 0.36, 12, 26)),
() => new StudyEnvironmentWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudySessionControl,
"component.study_session_control",
() => new StudySessionControlWidget(),
cellSize => Math.Clamp(cellSize * 0.36, 10, 24)),
() => new StudySessionControlWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudySessionHistory,
"component.study_session_history",
() => new StudySessionHistoryWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 10, 24)),
() => new StudySessionHistoryWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudyNoiseCurve,
"component.study_noise_curve",
() => new StudyNoiseCurveWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 12, 26)),
() => new StudyNoiseCurveWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudyNoiseDistribution,
"component.study_noise_distribution",
() => new StudyNoiseDistributionWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 12, 26)),
() => new StudyNoiseDistributionWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudyScoreOverview,
"component.study_score_overview",
() => new StudyScoreOverviewWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 12, 28)),
() => new StudyScoreOverviewWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudyDeductionReasons,
"component.study_deduction_reasons",
() => new StudyDeductionReasonsWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 10, 24)),
() => new StudyDeductionReasonsWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStudyInterruptDensity,
"component.study_interrupt_density",
() => new StudyInterruptDensityWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 10, 24)),
() => new StudyInterruptDensityWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopDailyPoetry,
"component.daily_poetry",
() => new DailyPoetryWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new DailyPoetryWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopDailyArtwork,
"component.daily_artwork",
() => new DailyArtworkWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new DailyArtworkWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopDailyWord,
"component.daily_word",
() => new DailyWordWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new DailyWordWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopDailyWord2x2,
"component.daily_word_2x2",
() => new DailyWord2x2Widget(),
cellSize => Math.Clamp(cellSize * 0.34, 12, 26)),
() => new DailyWord2x2Widget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopCnrDailyNews,
"component.cnr_daily_news",
() => new CnrDailyNewsWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new CnrDailyNewsWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopIfengNews,
"component.ifeng_news",
() => new IfengNewsWidget(),
cellSize => Math.Clamp(cellSize * 0.30, 12, 24)),
() => new IfengNewsWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopBilibiliHotSearch,
"component.bilibili_hot_search",
() => new BilibiliHotSearchWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new BilibiliHotSearchWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopBaiduHotSearch,
"component.baidu_hot_search",
() => new BaiduHotSearchWidget(),
cellSize => Math.Clamp(cellSize * 0.34, 14, 30)),
() => new BaiduHotSearchWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopStcn24Forum,
"component.stcn24_forum",
() => new Stcn24ForumWidget(),
cellSize => Math.Clamp(cellSize * 0.28, 12, 24)),
() => new Stcn24ForumWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopExchangeRateCalculator,
"component.exchange_rate_converter",
() => new ExchangeRateCalculatorWidget(),
cellSize => Math.Clamp(cellSize * 0.28, 12, 26)),
() => new ExchangeRateCalculatorWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopWhiteboard,
"component.whiteboard",
() => new WhiteboardWidget(),
cellSize => Math.Clamp(cellSize * 0.24, 10, 24)),
() => new WhiteboardWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopBlackboardLandscape,
"component.blackboard_landscape",
() => new WhiteboardWidget(baseWidthCells: 4),
cellSize => Math.Clamp(cellSize * 0.24, 10, 24)),
() => new WhiteboardWidget(baseWidthCells: 4)),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopBrowser,
"component.browser",
() => new BrowserWidget(),
cellSize => Math.Clamp(cellSize * 0.24, 10, 24)),
() => new BrowserWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopOfficeRecentDocuments,
"component.office_recent_documents",
_ => new OfficeRecentDocumentsWidget(),
chromeContext => Math.Clamp(chromeContext.CellSize * 0.50, 10, 24) *
Math.Max(GlobalAppearanceSettings.MinimumCornerRadiusScale, chromeContext.GlobalCornerRadiusScale)),
() => new OfficeRecentDocumentsWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopRemovableStorage,
"component.removable_storage",
() => new RemovableStorageWidget(),
cellSize => Math.Clamp(cellSize * 0.46, 12, 26)),
() => new RemovableStorageWidget()),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.HolidayCalendar,
"component.holiday_calendar",
() => new HolidayCalendarWidget(),
cellSize => Math.Clamp(cellSize * 0.32, 12, 28))
() => new HolidayCalendarWidget())
];
}

View File

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

View File

@@ -124,13 +124,9 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
28,
54,
_chromeContext);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius(_chromeContext);
ComponentChromeCornerRadiusHelper.Apply(
radius,
mainRectangleCornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,

View File

@@ -216,7 +216,7 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge
var titleNeedsTwoLines = isUltraCompact || titleUnits >= (isCompact ? 13 : 17);
var dateNeedsTwoLines = isUltraCompact || dateUnits >= (isCompact ? 15 : 20);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(shortSide * 0.13, 10, 46);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
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);

View File

@@ -270,14 +270,10 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
24,
46,
_chromeContext);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius(_chromeContext);
ComponentChromeCornerRadiusHelper.Apply(
cornerRadius,
mainRectangleCornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,

View File

@@ -400,8 +400,9 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(32 * softScale, 16, 46);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * softScale, 16, 46);
var unifiedMainRectangle = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = unifiedMainRectangle;
CardBorder.CornerRadius = unifiedMainRectangle;
var horizontalPadding = Math.Clamp(14 * softScale, 8, 20);
var verticalPadding = Math.Clamp(14 * softScale, 8, 20);
@@ -683,6 +684,11 @@ public partial class IfengNewsWidget : UserControl, IDesktopComponentWidget, IRe
return Math.Clamp(Math.Min(scaleX, scaleY), 0.72, 2.4);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private static string NormalizeCompactText(string? text)
{
if (string.IsNullOrWhiteSpace(text))

View File

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

View File

@@ -216,8 +216,9 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget,
private void ApplyAdaptiveTypography()
{
var scale = ResolveScale();
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(28 * scale, 14, 40);
RootBorder.CornerRadius = mainRectangleCornerRadius;
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);

View File

@@ -268,14 +268,10 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
24,
46,
_chromeContext);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius(_chromeContext);
ComponentChromeCornerRadiusHelper.Apply(
cornerRadius,
mainRectangleCornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,

View File

@@ -63,7 +63,7 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
var rootCornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 16, 44);
var rootCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = rootCornerRadius;
ContentPaddingBorder.Padding = new Thickness(
@@ -84,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 = ComponentChromeCornerRadiusHelper.Scale(12 * scale, 8, 16);
CoverBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
TitleTextBlock.FontSize = Math.Clamp(20 * scale, 12, 28);
ArtistTextBlock.FontSize = Math.Clamp(14 * scale, 9, 18);

View File

@@ -36,7 +36,7 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
return;
}
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(cellSize * 0.50, 10, 24);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)

View File

@@ -63,7 +63,7 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(34 * chromeScale, 16, 56);
var rootRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = rootRadius;
RootBorder.Padding = new Thickness(0);
RecorderCardBorder.CornerRadius = rootRadius;

View File

@@ -347,7 +347,7 @@ public partial class RemovableStorageWidget : UserControl, IDesktopComponentWidg
var scale = ResolveScale();
var width = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 2;
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 18, 34);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.Padding = new Thickness(
ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(15 * scale, 10, 22),

View File

@@ -602,8 +602,9 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(30 * softScale, 14, 44);
CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * softScale, 14, 44);
var unifiedMainRectangle = ResolveUnifiedMainRectangle();
RootBorder.CornerRadius = unifiedMainRectangle;
CardBorder.CornerRadius = unifiedMainRectangle;
CardBorder.Padding = new Thickness(
Math.Clamp(12 * softScale, 8, 18),
Math.Clamp(12 * softScale, 8, 18),
@@ -833,6 +834,11 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I
return Math.Clamp(Math.Min(scaleX, scaleY), 0.62, 2.6);
}
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);

View File

@@ -229,7 +229,8 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.46, 12, 34);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = mainRectangleCornerRadius;
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale * compactMultiplier, 6, 18),
Math.Clamp(10 * scale * compactMultiplier, 5, 16));
@@ -276,6 +277,9 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
SustainedRowBorder.Padding = rowPadding;
TimeRowBorder.Padding = rowPadding;
SegmentRowBorder.Padding = rowPadding;
SustainedRowBorder.CornerRadius = mainRectangleCornerRadius;
TimeRowBorder.CornerRadius = mainRectangleCornerRadius;
SegmentRowBorder.CornerRadius = mainRectangleCornerRadius;
SustainedMetricTextBlock.IsVisible = !_isUltraCompactMode;
TimeMetricTextBlock.IsVisible = !_isUltraCompactMode;

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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 10, 28);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.Padding = new Thickness(
Math.Clamp(14 * scale, 8, 20),
Math.Clamp(10 * scale, 6, 16));

View File

@@ -255,7 +255,8 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.46, 12, 34);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = mainRectangleCornerRadius;
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale * compactMultiplier, 6, 18),
Math.Clamp(9 * scale * compactMultiplier, 5, 16));
@@ -301,6 +302,8 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
Math.Clamp(6 * scale * compactMultiplier, 3, 9));
CountCardBorder.Padding = cardPadding;
DurationCardBorder.Padding = cardPadding;
CountCardBorder.CornerRadius = mainRectangleCornerRadius;
DurationCardBorder.CornerRadius = mainRectangleCornerRadius;
TitleTextBlock.IsVisible = !_isUltraCompactMode;
ThresholdTextBlock.IsVisible = !_isUltraCompactMode;

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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 14, 42);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 12, 34);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale * compactMultiplier, 6, 18),
Math.Clamp(9 * scale * compactMultiplier, 5, 16));

View File

@@ -258,7 +258,8 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.50, 14, 42);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = mainRectangleCornerRadius;
RootBorder.Padding = new Thickness(
Math.Clamp(16 * scale * compactMultiplier * expandedMultiplier, 8, 30),
Math.Clamp(14 * scale * compactMultiplier * expandedMultiplier, 6, 26));
@@ -305,7 +306,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 = ComponentChromeCornerRadiusHelper.Scale(10 * scale, 6, 18);
var cardCornerRadius = mainRectangleCornerRadius;
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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 10, 28);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.20, 8, 14),
CornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius(),
Background = new SolidColorBrush(rowBackground),
BorderBrush = new SolidColorBrush(rowBorderColor),
BorderThickness = new Thickness(1),
@@ -588,7 +588,8 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 12, 36);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = mainRectangleCornerRadius;
RootBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 7, 22),
Math.Clamp(9 * scale, 5, 16));
@@ -606,7 +607,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 = ComponentChromeCornerRadiusHelper.Scale(12 * scale, 10, 18);
DialogCardBorder.CornerRadius = mainRectangleCornerRadius;
DialogCardBorder.Padding = new Thickness(
Math.Clamp(12 * scale, 9, 20),
Math.Clamp(11 * scale, 8, 18));

View File

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

View File

@@ -151,11 +151,7 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(
targetHeight * metrics.CornerRadiusScale,
15,
36,
_chromeContext);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius(_chromeContext);
var horizontalPadding = ComponentChromeCornerRadiusHelper.SafeValue(
targetHeight * Lerp(0.18, 0.12, compactness),
@@ -168,7 +164,7 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
20,
_chromeContext);
RootBorder.CornerRadius = cornerRadius;
RootBorder.CornerRadius = mainRectangleCornerRadius;
RootBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
var columnSpacing = Math.Clamp(targetHeight * Lerp(0.16, 0.08, compactness), 2, 22);

View File

@@ -213,16 +213,12 @@ 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 = ComponentChromeCornerRadiusHelper.Scale(
_currentCellSize * metrics.CornerRadiusScale,
26,
46,
_chromeContext);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius(_chromeContext);
var horizontalPadding = Math.Clamp(_currentCellSize * metrics.HorizontalPaddingScale, 10, 24);
var verticalPadding = Math.Clamp(_currentCellSize * metrics.VerticalPaddingScale, 10, 24);
ComponentChromeCornerRadiusHelper.Apply(
cornerRadius,
mainRectangleCornerRadius,
RootBorder,
BackgroundImageLayer,
BackgroundMotionLayer,

View File

@@ -118,9 +118,10 @@ public partial class WhiteboardWidget : UserControl, IDesktopComponentWidget, IC
var toolbarPaddingVertical = Math.Clamp(buttonSize * 0.24, 4, 8);
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);
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
RootBorder.CornerRadius = mainRectangleCornerRadius;
CanvasBorder.CornerRadius = mainRectangleCornerRadius;
ToolbarBorder.CornerRadius = mainRectangleCornerRadius;
ToolbarBorder.Padding = new Thickness(
ComponentChromeCornerRadiusHelper.SafeValue(toolbarPaddingHorizontal, 6, 12),
ComponentChromeCornerRadiusHelper.SafeValue(toolbarPaddingVertical, 4, 8));

View File

@@ -166,11 +166,12 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
var mainRectangleCornerRadius = ComponentChromeCornerRadiusHelper.ResolveMainRectangleRadius();
var horizontalPadding = Math.Clamp(10 * scale, 4, 26);
var verticalPadding = Math.Clamp(8 * scale, 3, 22);
RootBorder.Padding = new Thickness(horizontalPadding, verticalPadding);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(24 * scale, 10, 46);
RootBorder.CornerRadius = mainRectangleCornerRadius;
var usableWidth = Math.Max(48, totalWidth - horizontalPadding * 2);
var usableHeight = Math.Max(28, totalHeight - verticalPadding * 2);

View File

@@ -269,7 +269,7 @@
Grid.ColumnSpan="1"
HorizontalAlignment="Stretch"
Margin="0"
CornerRadius="36"
CornerRadius="{DynamicResource DesignCornerRadiusLg}"
Padding="6">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="8">

View File

@@ -346,6 +346,7 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
ApplyAdaptiveThemeResources();
_recommendedColors = snapshot.MonetPalette.RecommendedColors;
_monetColors = snapshot.MonetPalette.MonetColors;
ApplyUnifiedMainRectangleChrome(snapshot);
}, DispatcherPriority.Background);
}
@@ -491,7 +492,7 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
TopStatusBarHost.Padding = new Thickness(0);
BottomTaskbarContainer.Margin = new Thickness(0);
BottomTaskbarContainer.CornerRadius = new CornerRadius(Math.Clamp(taskbarCellHeight * 0.58, 20, 44));
ApplyUnifiedMainRectangleChrome();
BottomTaskbarContainer.Padding = new Thickness(Math.Clamp(taskbarCellHeight * 0.16, 6, 14));
ClockWidget.Margin = new Thickness(0);
@@ -527,6 +528,27 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
UpdateComponentLibraryLayout(cellSize);
}
private void ApplyUnifiedMainRectangleChrome(AppearanceThemeSnapshot? snapshot = null)
{
var unifiedMainRectangle = new CornerRadius(ResolveUnifiedMainRadiusValue(snapshot));
BottomTaskbarContainer.CornerRadius = unifiedMainRectangle;
if (_currentDesktopCellSize > 0)
{
ClockWidget.ApplyCellSize(_currentDesktopCellSize);
}
}
private double ResolveUnifiedMainRadiusValue(AppearanceThemeSnapshot? snapshot = null)
{
if (snapshot is not null)
{
return snapshot.CornerRadiusTokens.Lg.TopLeft;
}
return _appearanceThemeService.GetCurrent().CornerRadiusTokens.Lg.TopLeft;
}
private static void SetButtonContentSpacing(Button? button, double spacing)
{
if (button?.Content is StackPanel contentPanel)

View File

@@ -156,7 +156,7 @@ public sealed class PluginLoader
var pluginType = ResolvePluginType(assembly);
plugin = CreatePluginInstance(pluginType);
AppLogger.Info("PluginLoader", $"Plugin instance created. PluginId='{manifest.Id}'; PluginType='{pluginType.FullName}'.");
runtimeContext = CreateRuntimeContext(manifest, pluginDirectory, dataDirectory, properties);
runtimeContext = CreateRuntimeContext(manifest, pluginDirectory, dataDirectory, properties, services);
var serviceCollection = CreateServiceCollection(runtimeContext, services);
var hostBuilderContext = CreateHostBuilderContext(runtimeContext);
@@ -297,13 +297,15 @@ public sealed class PluginLoader
PluginManifest manifest,
string pluginDirectory,
string dataDirectory,
IReadOnlyDictionary<string, object?>? properties)
IReadOnlyDictionary<string, object?>? properties,
IServiceProvider? hostServices)
{
return new PluginRuntimeContext(
manifest,
pluginDirectory,
dataDirectory,
CreateReadOnlyProperties(properties));
CreateReadOnlyProperties(properties),
BuildAppearanceSnapshot(hostServices));
}
private ServiceCollection CreateServiceCollection(
@@ -313,6 +315,7 @@ public sealed class PluginLoader
var services = new ServiceCollection();
services.AddSingleton(runtimeContext);
services.AddSingleton<IPluginRuntimeContext>(runtimeContext);
services.AddSingleton<IPluginAppearanceContext>(runtimeContext.Appearance);
services.AddSingleton(runtimeContext.Manifest);
services.AddSingleton<IReadOnlyDictionary<string, object?>>(runtimeContext.Properties);
services.AddSingleton<IPluginMessageBus, PluginMessageBus>();
@@ -332,6 +335,33 @@ public sealed class PluginLoader
return services;
}
private static PluginAppearanceSnapshot BuildAppearanceSnapshot(IServiceProvider? hostServices)
{
var defaultSnapshot = new PluginAppearanceSnapshot(
GlobalCornerRadiusScale: 1d,
CornerRadiusTokens: new PluginCornerRadiusTokens(6, 10, 14, 18, 24, 30, 36),
ThemeVariant: "Unknown");
if (hostServices?.GetService(typeof(IAppearanceThemeService)) is not IAppearanceThemeService appearanceThemeService)
{
return defaultSnapshot;
}
try
{
var hostSnapshot = appearanceThemeService.GetCurrent();
return new PluginAppearanceSnapshot(
GlobalCornerRadiusScale: Math.Max(0d, hostSnapshot.GlobalCornerRadiusScale),
CornerRadiusTokens: PluginCornerRadiusTokens.FromShared(hostSnapshot.CornerRadiusTokens),
ThemeVariant: hostSnapshot.IsNightMode ? "Dark" : "Light");
}
catch (Exception ex)
{
AppLogger.Warn("PluginLoader", "Failed to resolve host appearance snapshot for plugin runtime context.", ex);
return defaultSnapshot;
}
}
private static void RegisterHostService<TService>(IServiceCollection services, IServiceProvider? hostServices)
where TService : class
{
@@ -730,12 +760,14 @@ public sealed class PluginLoader
PluginManifest manifest,
string pluginDirectory,
string dataDirectory,
IReadOnlyDictionary<string, object?> properties)
IReadOnlyDictionary<string, object?> properties,
PluginAppearanceSnapshot appearanceSnapshot)
{
Manifest = manifest;
PluginDirectory = pluginDirectory;
DataDirectory = dataDirectory;
Properties = properties;
Appearance = new PluginAppearanceContext(appearanceSnapshot);
Services = NullServiceProvider.Instance;
}
@@ -749,6 +781,8 @@ public sealed class PluginLoader
public IReadOnlyDictionary<string, object?> Properties { get; }
public IPluginAppearanceContext Appearance { get; }
public T? GetService<T>()
{
return (T?)Services.GetService(typeof(T));

View File

@@ -90,16 +90,30 @@ internal static class AirAppMarketDefaults
private static string? TryResolveWorkspacePath(string repositoryName, string relativePath)
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
while (current is not null && current.Exists)
{
var candidate = Path.Combine(current.FullName, repositoryName);
if (Directory.Exists(candidate))
var solutionPath = Path.Combine(current.FullName, "LanMountainDesktop.slnx");
if (File.Exists(solutionPath))
{
var candidatePath = Path.GetFullPath(Path.Combine(candidate, relativePath));
var workspaceRoot = current.Parent;
if (workspaceRoot is null)
{
return null;
}
var candidateRepositoryPath = Path.Combine(workspaceRoot.FullName, repositoryName);
if (!Directory.Exists(candidateRepositoryPath))
{
return null;
}
var candidatePath = Path.GetFullPath(Path.Combine(candidateRepositoryPath, relativePath));
if (File.Exists(candidatePath))
{
return candidatePath;
}
return null;
}
current = current.Parent;

View File

@@ -1,53 +1,26 @@
# 宿主侧插件运行时 / Host Plugin Runtime
## 中文
本目录保存阑山桌面宿主侧插件运行时实现。
### 主要职责
- 发现、安装和替换 `.laapp` 插件包
- 加载插件程序集和共享契约
- 接入插件设置页、桌面组件与市场界面
-`3.0.0` API 基线插件构建插件作用域的 `IServiceCollection` / `ServiceProvider`
- 在激活前解析共享契约缓存,并暴露显式插件导出
### 与 LanAirApp 的分工
- `LanAirApp` 负责官方市场索引、开发文档、校验工具和镜像样例
- 本目录负责宿主运行时发现、安装、加载和界面接入
- 权威示例插件是独立仓库 `LanMountainDesktop.SamplePlugin``LanAirApp` 中的样例目录只是镜像模板
### 市场安装顺序
1. 宿主读取官方 `LanAirApp/airappmarket/index.json`
2. 若条目同时包含 `releaseTag``releaseAssetName`,优先解析 GitHub Release 资产
3. 若 Release 解析失败,则回退到仓库根目录 `.laapp`
4. 插件详情始终读取插件仓库根目录 `README.md`
5. 市场安装为暂存安装,重启后生效
## English
# Host Plugin Runtime
This directory contains the host-side plugin runtime for LanMountainDesktop.
### Responsibilities
## Responsibilities
- discover, install, and replace `.laapp` packages
- load plugin assemblies and shared contracts
- integrate plugin settings pages, desktop components, and market UI
- build a plugin-scoped `IServiceCollection` / `ServiceProvider` for API `3.0.0` plugins
- resolve shared contract caches before activation and expose explicit plugin exports
- Discover, install, replace, and stage `.laapp` plugin packages
- Load plugin assemblies and shared contracts
- Integrate plugin settings sections, desktop components, and market UI
- Build plugin-scoped `IServiceCollection` / `ServiceProvider` for API `4.x` plugins
- Resolve shared contracts before activation and expose explicit plugin exports
### Relationship with LanAirApp
## Relationship with LanAirApp
- `LanAirApp` owns the official market index, developer docs, validation tools, and mirrored sample templates
- this directory owns host-side discovery, installation, loading, and UI integration
- the authoritative sample plugin lives in the standalone `LanMountainDesktop.SamplePlugin` repository; the `LanAirApp` sample directory is only a mirror/template copy
- `LanAirApp` is a standalone repository and owns market metadata plus developer ecosystem materials
- This host runtime only consumes market metadata and plugin packages
- The host no longer maintains an embedded `LanAirApp/` mirror inside this repository
- Workspace debugging resolves market files from sibling path `..\\LanAirApp\\...`
### Market install order
## Market Install Flow
1. The host reads the official `LanAirApp/airappmarket/index.json`
2. If an entry contains both `releaseTag` and `releaseAssetName`, the host first resolves the exact GitHub Release asset
3. If Release resolution fails, the host falls back to the repository-root `.laapp`
4. Plugin details always come from the plugin repository root `README.md`
5. Market installs are staged and take effect after restart
1. Host reads the official market index
2. If both `releaseTag` and `releaseAssetName` are present, host resolves the exact GitHub Release asset first
3. If release resolution fails, host falls back to repository-root `.laapp`
4. Plugin detail text is read from plugin repository root `README.md`
5. Installation is staged and becomes effective after restart