Revert "0.7.0.1"

This reverts commit ea8ce1f5ff.
This commit is contained in:
lincube
2026-03-20 14:12:40 +08:00
parent ea8ce1f5ff
commit 5d48a03f57
31 changed files with 602 additions and 2134 deletions

View File

@@ -7,7 +7,6 @@ using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
@@ -66,7 +65,6 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private readonly LocalizationService _localizationService = new();
private TimeZoneService? _timeZoneService;
private double _currentCellSize = 48;
private double _layoutScale = 1d;
private bool _dialInitialized;
private bool _handsInitialized;
private bool? _isNightModeApplied;
@@ -187,18 +185,17 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private void BuildTicks(bool isNightMode)
{
TickCanvas.Children.Clear();
var scale = Math.Clamp(_layoutScale, 0.78d, 1.22d);
var majorBrush = CreateBrush(isNightMode ? "#1A1A1A" : "#1E2430");
var minorBrush = CreateBrush(isNightMode ? "#D0D0D0" : "#D7DCE5");
var majorThickness = (isNightMode ? 3.0 : 2.8) * scale;
var minorThickness = (isNightMode ? 1.4 : 1.2) * scale;
var majorThickness = isNightMode ? 3.0 : 2.8;
var minorThickness = isNightMode ? 1.4 : 1.2;
for (var i = 0; i < 60; i++)
{
var angle = (i * 6 - 90) * Math.PI / 180d;
var isHourTick = i % 5 == 0;
var outerRadius = Center - (7 * scale);
var innerRadius = outerRadius - (isHourTick ? 16 * scale : 8 * scale);
var outerRadius = Center - 7;
var innerRadius = outerRadius - (isHourTick ? 16 : 8);
var x1 = Center + Math.Cos(angle) * innerRadius;
var y1 = Center + Math.Sin(angle) * innerRadius;
@@ -221,50 +218,34 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private void BuildNumbers(bool isNightMode)
{
NumberCanvas.Children.Clear();
var scale = Math.Clamp(_layoutScale, 0.78d, 1.22d);
var foreground = CreateBrush(isNightMode ? "#101010" : "#0F131A");
var fontWeight = isNightMode ? FontWeight.Bold : FontWeight.SemiBold;
for (var number = 1; number <= 12; number++)
{
var angle = (number * 30 - 90) * Math.PI / 180d;
var radius = 88 * scale;
var radius = 88;
var x = Center + Math.Cos(angle) * radius;
var y = Center + Math.Sin(angle) * radius;
var isDoubleDigit = number >= 10;
var glyphBox = ComponentTypographyLayoutService.ResolveGlyphBox(
isDoubleDigit ? 44 * scale : 36 * scale,
34 * scale,
preferredSizeScale: isDoubleDigit ? 0.92d : 0.88d,
minSize: 18,
maxSize: isDoubleDigit ? 36 : 30,
insetScale: 0d);
var text = number.ToString(CultureInfo.InvariantCulture);
var fontSize = ComponentTypographyLayoutService.FitFontSize(
text,
glyphBox.Width,
glyphBox.Height,
maxLines: 1,
minFontSize: 12 * scale,
maxFontSize: 18 * scale,
weight: fontWeight,
lineHeightFactor: 1d);
var width = isDoubleDigit ? 44 : 28;
var height = 34;
var numberText = new TextBlock
var text = new TextBlock
{
Text = text,
Width = glyphBox.Width,
Height = glyphBox.Height,
Text = number.ToString(CultureInfo.InvariantCulture),
Width = width,
Height = height,
TextAlignment = TextAlignment.Center,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
FontSize = fontSize,
FontSize = 18,
FontWeight = fontWeight,
Foreground = foreground
};
Canvas.SetLeft(numberText, x - glyphBox.Width / 2d);
Canvas.SetTop(numberText, y - glyphBox.Height / 2d);
NumberCanvas.Children.Add(numberText);
Canvas.SetLeft(text, x - width / 2d);
Canvas.SetTop(text, y - height / 2d);
NumberCanvas.Children.Add(text);
}
}
@@ -344,14 +325,10 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
{
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
var chromeScale = ComponentChromeCornerRadiusHelper.ResolveScale();
_layoutScale = Math.Clamp(scale * (0.9d + (chromeScale * 0.1d)), 0.58d, 2.0d);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(42 * _layoutScale, 16, 56);
RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(14 * _layoutScale, 14 * _layoutScale, null, 0.55d);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(42 * scale, 16, 56);
RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(14 * scale, 14 * scale, null, 0.55d);
ApplyModeVisualIfNeeded();
BuildTicks(_isNightModeApplied ?? ResolveIsNightMode());
BuildNumbers(_isNightModeApplied ?? ResolveIsNightMode());
}
private double ResolveScale()

View File

@@ -13,7 +13,6 @@ using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -433,61 +432,29 @@ public partial class BaiduHotSearchWidget : UserControl, IDesktopComponentWidget
RefreshGlyphIcon.FontSize = Math.Clamp(refreshButtonSize * 0.46, 10, 20);
var lineColumnGap = Math.Clamp(lineRowHeight * 0.34, 5, 12);
var indexBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
lineRowHeight,
lineRowHeight,
preferredSizeScale: 0.62d,
minSize: 16,
maxSize: 28);
var indexFont = ComponentTypographyLayoutService.FitFontSize(
"88",
indexBadge.Width,
indexBadge.Height,
1,
minFontSize: 10,
maxFontSize: Math.Clamp(indexBadge.Height * 0.82d, 10, 18),
weight: FontWeight.Bold,
lineHeightFactor: 1.0d,
fontFamily: MiSansFontFamily);
var itemFontMin = Math.Clamp(lineRowHeight * 0.42, 11, 16);
var itemFontMax = Math.Clamp(lineRowHeight * 0.68, 12, 24);
var indexWidth = Math.Clamp(lineRowHeight * 1.02, 16, 28);
var indexFont = Math.Clamp(lineRowHeight * 0.50, 10, 16);
var itemFont = Math.Clamp(lineRowHeight * 0.62, 12, 24);
var rowPadding = Math.Clamp(lineRowHeight * 0.08, 1, 4);
var itemTextWidth = Math.Max(56, innerWidth - indexBadge.Width - lineColumnGap);
var itemTextWidth = Math.Max(56, innerWidth - indexWidth - lineColumnGap);
foreach (var visual in _hotItemVisuals)
{
visual.RowGrid.ColumnSpacing = lineColumnGap;
if (visual.RowGrid.ColumnDefinitions.Count > 0)
{
visual.RowGrid.ColumnDefinitions[0].Width = new GridLength(indexBadge.Width, GridUnitType.Pixel);
visual.RowGrid.ColumnDefinitions[0].Width = new GridLength(indexWidth, GridUnitType.Pixel);
}
visual.Host.Padding = new Thickness(0, rowPadding, 0, rowPadding);
visual.IndexTextBlock.FontSize = indexFont;
visual.IndexTextBlock.MaxWidth = indexBadge.Width;
visual.IndexTextBlock.MinWidth = indexBadge.Width;
visual.IndexTextBlock.Margin = indexBadge.Margin;
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
visual.TitleTextBlock.Text,
itemTextWidth,
lineRowHeight * 1.9d,
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(visual.TitleTextBlock.Text) > 24 ? 2 : 1,
minFontSize: itemFontMin,
maxFontSize: itemFontMax,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.08d,
fontFamily: MiSansFontFamily);
visual.TitleTextBlock.FontSize = titleLayout.FontSize;
visual.TitleTextBlock.LineHeight = titleLayout.LineHeight;
visual.TitleTextBlock.MaxLines = titleLayout.MaxLines;
visual.TitleTextBlock.FontWeight = titleLayout.Weight;
visual.IndexTextBlock.MaxWidth = indexWidth;
visual.TitleTextBlock.FontSize = itemFont;
visual.TitleTextBlock.MaxWidth = itemTextWidth;
visual.TitleTextBlock.TextAlignment = TextAlignment.Left;
}
StatusTextBlock.FontSize = Math.Clamp(itemFontMax, 10, 20);
StatusTextBlock.FontSize = Math.Clamp(itemFont, 10, 20);
ApplyNightModeVisual();
}

View File

@@ -11,7 +11,6 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -438,93 +437,37 @@ public partial class BilibiliHotSearchWidget : UserControl, IDesktopComponentWid
Math.Clamp(searchBoxHeight * 0.24, 5, 10),
0);
SearchGlyphIcon.FontSize = Math.Clamp(searchBoxHeight * 0.45, 10, 20);
var searchLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
SearchEntryTextBlock.Text,
Math.Max(54, SearchBoxBorder.Width - Math.Clamp(searchBoxHeight * 0.48, 8, 16)),
searchBoxHeight,
minLines: 1,
maxLines: 1,
minFontSize: 10,
maxFontSize: Math.Clamp(searchBoxHeight * 0.44, 10, 18),
weightCandidates: new[] { FontWeight.Medium, FontWeight.SemiBold },
lineHeightFactor: 1.0d,
fontFamily: MiSansFontFamily);
SearchEntryTextBlock.FontSize = searchLayout.FontSize;
SearchEntryTextBlock.LineHeight = searchLayout.LineHeight;
SearchEntryTextBlock.FontSize = Math.Clamp(searchBoxHeight * 0.44, 10, 18);
TopRightTitleTextBlock.MaxWidth = Math.Max(80, innerWidth - SearchBoxBorder.Width - HeaderGrid.ColumnSpacing);
var topRightLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TopRightTitleTextBlock.Text,
TopRightTitleTextBlock.MaxWidth,
topRowHeight,
minLines: 1,
maxLines: 1,
minFontSize: 11,
maxFontSize: Math.Clamp(topRowHeight * 0.46, 11, 22),
weightCandidates: new[] { FontWeight.Medium, FontWeight.SemiBold },
lineHeightFactor: 1.0d,
fontFamily: MiSansFontFamily);
TopRightTitleTextBlock.FontSize = topRightLayout.FontSize;
TopRightTitleTextBlock.LineHeight = topRightLayout.LineHeight;
TopRightTitleTextBlock.FontSize = Math.Clamp(topRowHeight * 0.46, 11, 22);
var lineColumnGap = Math.Clamp(lineRowHeight * 0.34, 5, 12);
var indexBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
lineRowHeight,
lineRowHeight,
preferredSizeScale: 0.62d,
minSize: 16,
maxSize: 28);
var indexFont = ComponentTypographyLayoutService.FitFontSize(
"88",
indexBadge.Width,
indexBadge.Height,
1,
minFontSize: 10,
maxFontSize: Math.Clamp(indexBadge.Height * 0.82d, 10, 18),
weight: FontWeight.Bold,
lineHeightFactor: 1.0d,
fontFamily: MiSansFontFamily);
var itemFontMin = Math.Clamp(lineRowHeight * 0.42, 11, 16);
var itemFontMax = Math.Clamp(lineRowHeight * 0.68, 12, 24);
var indexWidth = Math.Clamp(lineRowHeight * 1.02, 16, 28);
var indexFont = Math.Clamp(lineRowHeight * 0.50, 10, 16);
var itemFont = Math.Clamp(lineRowHeight * 0.62, 12, 24);
var rowPadding = Math.Clamp(lineRowHeight * 0.08, 1, 4);
var itemTextWidth = Math.Max(56, innerWidth - indexBadge.Width - lineColumnGap);
var itemTextWidth = Math.Max(56, innerWidth - indexWidth - lineColumnGap);
foreach (var visual in _hotItemVisuals)
{
visual.RowGrid.ColumnSpacing = lineColumnGap;
if (visual.RowGrid.ColumnDefinitions.Count > 0)
{
visual.RowGrid.ColumnDefinitions[0].Width = new GridLength(indexBadge.Width, GridUnitType.Pixel);
visual.RowGrid.ColumnDefinitions[0].Width = new GridLength(indexWidth, GridUnitType.Pixel);
}
visual.Host.Padding = new Thickness(0, rowPadding, 0, rowPadding);
visual.IndexTextBlock.FontSize = indexFont;
visual.IndexTextBlock.MaxWidth = indexBadge.Width;
visual.IndexTextBlock.MinWidth = indexBadge.Width;
visual.IndexTextBlock.Margin = indexBadge.Margin;
visual.IndexTextBlock.MaxWidth = indexWidth;
visual.IndexTextBlock.HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right;
visual.IndexTextBlock.TextAlignment = TextAlignment.Right;
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
visual.TitleTextBlock.Text,
itemTextWidth,
lineRowHeight * 1.9d,
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(visual.TitleTextBlock.Text) > 24 ? 2 : 1,
minFontSize: itemFontMin,
maxFontSize: itemFontMax,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.08d,
fontFamily: MiSansFontFamily);
visual.TitleTextBlock.FontSize = titleLayout.FontSize;
visual.TitleTextBlock.LineHeight = titleLayout.LineHeight;
visual.TitleTextBlock.MaxLines = titleLayout.MaxLines;
visual.TitleTextBlock.FontWeight = titleLayout.Weight;
visual.TitleTextBlock.FontSize = itemFont;
visual.TitleTextBlock.MaxWidth = itemTextWidth;
visual.TitleTextBlock.TextAlignment = TextAlignment.Left;
}
StatusTextBlock.FontSize = Math.Clamp(itemFontMax, 10, 20);
StatusTextBlock.FontSize = Math.Clamp(itemFont, 10, 20);
ApplyNightModeVisual();
}

View File

@@ -6,7 +6,6 @@ using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using AvaloniaWebView;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services;
using WebViewCore.Events;
@@ -53,7 +52,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
}
AddressTextBox.Text = DefaultHomeUri.ToString();
UpdateAddressTypography();
UpdateWebViewActiveState();
}
@@ -118,7 +116,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
AddressTextBox.FontSize = Math.Clamp(_currentCellSize * 0.30, 12, 15);
AddressTextBox.Height = buttonSize;
UpdateAddressTypography();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
@@ -286,7 +283,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
{
_lastKnownUri = uri;
AddressTextBox.Text = uri.ToString();
UpdateAddressTypography();
if (_isWebViewActive)
{
TryNavigate(uri, "NavigateTo");
@@ -302,7 +298,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
_lastKnownUri = e.Url;
AddressTextBox.Text = e.Url.ToString();
UpdateAddressTypography();
}
private void UpdateWebViewActiveState()
@@ -412,7 +407,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
GoButton.IsEnabled = false;
AddressTextBox.IsEnabled = false;
AddressTextBox.Text = _lastKnownUri.ToString();
UpdateAddressTypography();
UnavailableMessageTextBlock.Text = _isWebViewFaulted
? "The browser component is temporarily unavailable. Restart the app to retry."
@@ -457,25 +451,4 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
? uri
: null;
}
private void UpdateAddressTypography()
{
var maxWidth = AddressTextBox.Bounds.Width > 1
? AddressTextBox.Bounds.Width
: Math.Max(120, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 7) - 120);
var maxHeight = AddressTextBox.Bounds.Height > 1
? AddressTextBox.Bounds.Height
: Math.Max(20, AddressTextBox.Height);
AddressTextBox.FontSize = ComponentTypographyLayoutService.FitFontSize(
AddressTextBox.Text,
maxWidth,
maxHeight,
maxLines: 1,
minFontSize: 12,
maxFontSize: 16,
weight: FontWeight.Normal,
lineHeightFactor: 1.06d,
fontFamily: AddressTextBox.FontFamily);
}
}

View File

@@ -8,7 +8,6 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
@@ -44,7 +43,6 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
private TimeZoneService? _timeZoneService;
private double _currentCellSize = 48;
private double _layoutScale = 1d;
private IReadOnlyList<CourseItemViewModel> _courseItems = Array.Empty<CourseItemViewModel>();
private bool _isNightVisual = true;
private string _languageCode = "zh-CN";
@@ -495,20 +493,18 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var useMonetColor = ComponentColorSchemeHelper.ShouldUseMonetColor(
_componentColorScheme,
ComponentColorSchemeHelper.GetCurrentGlobalThemeColorMode());
var chromeScale = ComponentChromeCornerRadiusHelper.ResolveScale();
_layoutScale = Math.Clamp(ResolveScale() * (0.9d + (chromeScale * 0.1d)), 0.52d, 2.2d);
var bulletSize = Math.Clamp(10 * _layoutScale, 5, 12);
var lineSpacing = Math.Clamp(4 * _layoutScale, 1.5, 8);
var scale = ResolveScale();
var bulletSize = Math.Clamp(10 * scale, 5, 12);
var courseNameSize = Math.Clamp(42 * scale, 14, 42);
var secondarySize = Math.Clamp(29 * scale, 10, 28);
var lineSpacing = Math.Clamp(4 * scale, 1.5, 8);
var itemPadding = new Thickness(
ComponentChromeCornerRadiusHelper.SafeValue(6 * _layoutScale, 3, 10),
ComponentChromeCornerRadiusHelper.SafeValue(4 * _layoutScale, 2, 8),
ComponentChromeCornerRadiusHelper.SafeValue(4 * _layoutScale, 2, 8),
ComponentChromeCornerRadiusHelper.SafeValue(4 * _layoutScale, 2, 8));
var maxVisibleItems = ResolveMaxVisibleItems(_layoutScale);
var itemContentWidth = Math.Max(28, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 4) - itemPadding.Left - itemPadding.Right - bulletSize - Math.Clamp(10 * _layoutScale, 4, 14));
var titleHeight = Math.Clamp(34 * _layoutScale, 16, 42);
var secondaryHeight = Math.Clamp(24 * _layoutScale, 12, 30);
ComponentChromeCornerRadiusHelper.SafeValue(6 * scale, 3, 10),
ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8),
ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8),
ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8));
var maxVisibleItems = ResolveMaxVisibleItems(scale);
var primaryBrush = CreateBrush(_isNightVisual ? "#F9FBFF" : "#151821");
var secondaryBrush = CreateBrush(_isNightVisual ? "#848B99" : "#667084");
@@ -529,32 +525,14 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
CornerRadius = new CornerRadius(bulletSize * 0.5),
Background = bulletBrush,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top,
Margin = new Thickness(0, Math.Clamp(8 * _layoutScale, 2, 12), 0, 0)
Margin = new Thickness(0, Math.Clamp(8 * scale, 2, 12), 0, 0)
};
var titleText = new TextBlock
{
Text = item.Name,
FontSize = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
item.Name,
itemContentWidth,
titleHeight,
minLines: 1,
maxLines: 2,
minFontSize: 14,
maxFontSize: Math.Clamp(42 * _layoutScale, 14, 42),
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1.05d).FontSize,
FontWeight = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
item.Name,
itemContentWidth,
titleHeight,
minLines: 1,
maxLines: 2,
minFontSize: 14,
maxFontSize: Math.Clamp(42 * _layoutScale, 14, 42),
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1.05d).Weight,
FontSize = courseNameSize,
FontWeight = ToVariableWeight(Lerp(620, 780, Math.Clamp((scale - 0.60) / 1.2, 0, 1))),
Foreground = primaryBrush,
TextTrimming = TextTrimming.CharacterEllipsis,
TextWrapping = TextWrapping.NoWrap
@@ -563,26 +541,8 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var timeText = new TextBlock
{
Text = item.TimeRange,
FontSize = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
item.TimeRange,
itemContentWidth,
secondaryHeight,
minLines: 1,
maxLines: 1,
minFontSize: 10,
maxFontSize: Math.Clamp(28 * _layoutScale, 10, 28),
weightCandidates: new[] { FontWeight.Medium, FontWeight.Normal },
lineHeightFactor: 1d).FontSize,
FontWeight = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
item.TimeRange,
itemContentWidth,
secondaryHeight,
minLines: 1,
maxLines: 1,
minFontSize: 10,
maxFontSize: Math.Clamp(28 * _layoutScale, 10, 28),
weightCandidates: new[] { FontWeight.Medium, FontWeight.Normal },
lineHeightFactor: 1d).Weight,
FontSize = secondarySize,
FontWeight = ToVariableWeight(Lerp(520, 680, Math.Clamp((scale - 0.60) / 1.2, 0, 1))),
Foreground = secondaryBrush,
TextTrimming = TextTrimming.CharacterEllipsis,
TextWrapping = TextWrapping.NoWrap
@@ -591,26 +551,8 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var detailText = new TextBlock
{
Text = item.Detail,
FontSize = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
item.Detail,
itemContentWidth,
secondaryHeight,
minLines: 1,
maxLines: 1,
minFontSize: 10,
maxFontSize: Math.Clamp(28 * _layoutScale, 10, 28),
weightCandidates: new[] { FontWeight.Normal, FontWeight.Medium },
lineHeightFactor: 1d).FontSize,
FontWeight = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
item.Detail,
itemContentWidth,
secondaryHeight,
minLines: 1,
maxLines: 1,
minFontSize: 10,
maxFontSize: Math.Clamp(28 * _layoutScale, 10, 28),
weightCandidates: new[] { FontWeight.Normal, FontWeight.Medium },
lineHeightFactor: 1d).Weight,
FontSize = secondarySize,
FontWeight = ToVariableWeight(Lerp(500, 640, Math.Clamp((scale - 0.60) / 1.2, 0, 1))),
Foreground = secondaryBrush,
TextTrimming = TextTrimming.CharacterEllipsis,
TextWrapping = TextWrapping.NoWrap
@@ -625,7 +567,7 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var itemGrid = new Grid
{
ColumnDefinitions = new ColumnDefinitions("Auto,*"),
ColumnSpacing = Math.Clamp(10 * _layoutScale, 4, 14)
ColumnSpacing = Math.Clamp(10 * scale, 4, 14)
};
itemGrid.Children.Add(bullet);
itemGrid.Children.Add(textStack);
@@ -661,8 +603,6 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
}
var scale = ResolveScale();
var chromeScale = ComponentChromeCornerRadiusHelper.ResolveScale();
_layoutScale = Math.Clamp(scale * (0.9d + (chromeScale * 0.1d)), 0.52d, 2.2d);
_isNightVisual = ResolveNightMode();
var useMonetColor = ComponentColorSchemeHelper.ShouldUseMonetColor(
@@ -672,66 +612,6 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
var slashBrush = useMonetColor
? CreateBrush("#FF4FC3F7")
: CreateBrush("#FF3250");
var sampleNow = _timeZoneService?.GetCurrentTime() ?? DateTime.Now;
var headerWidth = Math.Max(42, Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 4);
var headerHeight = Math.Max(42, Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 4);
var headerPrimaryWidth = Math.Clamp(headerWidth * 0.34, 28, 92);
var headerSecondaryWidth = Math.Clamp(headerWidth * 0.52, 40, 148);
var dateHeight = Math.Clamp(headerHeight * 0.30, 26, 96);
var secondaryHeaderHeight = Math.Clamp(headerHeight * 0.12, 16, 42);
var weekdayLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
FormatWeekday(sampleNow.DayOfWeek),
headerSecondaryWidth,
secondaryHeaderHeight,
minLines: 1,
maxLines: 1,
minFontSize: 13,
maxFontSize: 32,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1.02d);
var classCountLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
FormatClassCount(_courseItems.Count),
headerSecondaryWidth,
secondaryHeaderHeight,
minLines: 1,
maxLines: 1,
minFontSize: 14,
maxFontSize: 36,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1.02d);
var statusText = string.IsNullOrWhiteSpace(StatusTextBlock.Text)
? L("schedule.widget.no_class_today", "浠婂ぉ娌℃湁璇剧▼")
: StatusTextBlock.Text;
var statusLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
statusText,
headerSecondaryWidth,
secondaryHeaderHeight,
minLines: 1,
maxLines: 1,
minFontSize: 12,
maxFontSize: 30,
weightCandidates: new[] { FontWeight.Medium, FontWeight.Normal },
lineHeightFactor: 1.02d);
var monthLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
sampleNow.Month.ToString(CultureInfo.InvariantCulture),
headerPrimaryWidth,
dateHeight,
minLines: 1,
maxLines: 1,
minFontSize: 26,
maxFontSize: 82,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1d);
var dayLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
sampleNow.Day.ToString(CultureInfo.InvariantCulture),
headerPrimaryWidth,
dateHeight,
minLines: 1,
maxLines: 1,
minFontSize: 26,
maxFontSize: 82,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1d);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.45, 24, 44);
RootBorder.Background = _isNightVisual
@@ -740,21 +620,21 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
RootBorder.BorderBrush = CreateBrush(_isNightVisual ? "#24FFFFFF" : "#15000000");
var rootPadding = new Thickness(
ComponentChromeCornerRadiusHelper.SafeValue(16 * _layoutScale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(14 * _layoutScale, 9, 20),
ComponentChromeCornerRadiusHelper.SafeValue(16 * _layoutScale, 10, 24),
ComponentChromeCornerRadiusHelper.SafeValue(14 * _layoutScale, 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 * _layoutScale, 6, 20);
HeaderGrid.ColumnSpacing = Math.Clamp(10 * _layoutScale, 4, 16);
DateGroup.Spacing = Math.Clamp(1.5 * _layoutScale, 0.5, 3);
MetaStack.Spacing = Math.Clamp(6 * _layoutScale, 3, 10);
CourseListPanel.Spacing = Math.Clamp(6 * _layoutScale, 3, 10);
LayoutGrid.RowSpacing = Math.Clamp(14 * scale, 6, 20);
HeaderGrid.ColumnSpacing = Math.Clamp(10 * scale, 4, 16);
DateGroup.Spacing = Math.Clamp(1.5 * scale, 0.5, 3);
MetaStack.Spacing = Math.Clamp(6 * scale, 3, 10);
CourseListPanel.Spacing = Math.Clamp(6 * scale, 3, 10);
var dateFont = Math.Clamp(66 * _layoutScale, 26, 82);
MonthTextBlock.FontSize = monthLayout.FontSize;
DayTextBlock.FontSize = dayLayout.FontSize;
var dateFont = Math.Clamp(66 * scale, 26, 82);
MonthTextBlock.FontSize = dateFont;
DayTextBlock.FontSize = dateFont;
SlashTextBlock.FontSize = dateFont;
MonthTextBlock.Foreground = CreateBrush(_isNightVisual ? "#F8FAFF" : "#131722");
@@ -764,13 +644,12 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
ClassCountTextBlock.Foreground = CreateBrush(_isNightVisual ? "#8D95A4" : "#738095");
StatusTextBlock.Foreground = CreateBrush(_isNightVisual ? "#9AA2B1" : "#4B5565");
WeekdayTextBlock.FontSize = weekdayLayout.FontSize;
ClassCountTextBlock.FontSize = classCountLayout.FontSize;
StatusTextBlock.FontSize = statusLayout.FontSize;
WeekdayTextBlock.FontSize = Math.Clamp(34 * scale, 13, 32);
ClassCountTextBlock.FontSize = Math.Clamp(40 * scale, 14, 36);
StatusTextBlock.FontSize = Math.Clamp(30 * scale, 12, 30);
WeekdayTextBlock.FontWeight = weekdayLayout.Weight;
ClassCountTextBlock.FontWeight = classCountLayout.Weight;
StatusTextBlock.FontWeight = statusLayout.Weight;
WeekdayTextBlock.FontWeight = ToVariableWeight(Lerp(560, 700, Math.Clamp((scale - 0.60) / 1.2, 0, 1)));
ClassCountTextBlock.FontWeight = ToVariableWeight(Lerp(560, 680, Math.Clamp((scale - 0.60) / 1.2, 0, 1)));
}
private static string FormatTime(TimeSpan time)

View File

@@ -4,7 +4,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
@@ -121,13 +120,11 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
SecondsTextBlock.Text = now.ToString("ss", CultureInfo.CurrentCulture);
SecondsTextBlock.IsVisible = _displayFormat == ClockDisplayFormat.HourMinuteSecond;
ApplyTypographyLayout();
}
public void ApplyCellSize(double cellSize)
{
_lastAppliedCellSize = cellSize;
var layoutScale = Math.Clamp((cellSize / 44d) * (0.9d + (ComponentChromeCornerRadiusHelper.ResolveScale() * 0.1d)), 0.65d, 1.95d);
// --- Class Island “满盈”风格算法 ---
@@ -141,7 +138,7 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
// 3. 核心:满盈字阶 (Filled Typography)
// 使主时间文字占据容器高度的 ~68%,产生饱满的视觉张力
var mainFontSize = targetHeight * 0.68 * layoutScale;
var mainFontSize = targetHeight * 0.68;
MainTimeTextBlock.FontSize = mainFontSize;
MainTimeTextBlock.FontWeight = FontWeight.SemiBold;
@@ -155,74 +152,19 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
// 6. 间距微调
if (MainTimeTextBlock.Parent is StackPanel panel)
{
panel.Spacing = Math.Clamp(cellSize * 0.06 * layoutScale, 2, 8);
panel.Spacing = Math.Clamp(cellSize * 0.06, 2, 8);
}
if (_transparentBackground)
{
RootBorder.MinWidth = 0;
RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.06 * layoutScale, 4, 10), 0);
RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.06, 4, 10), 0);
return;
}
// 确保清除可能存在的固定 Padding由代码控制“紧密感”
RootBorder.MinWidth = cellSize * 2.2;
RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.15 * layoutScale, 12, 24), 0);
ApplyTypographyLayout();
}
private void ApplyTypographyLayout()
{
var layoutScale = Math.Clamp((_lastAppliedCellSize / 44d) * (0.9d + (ComponentChromeCornerRadiusHelper.ResolveScale() * 0.1d)), 0.65d, 1.95d);
var availableWidth = Math.Max(1, RootBorder.Bounds.Width > 1 ? RootBorder.Bounds.Width : Math.Max(1, _lastAppliedCellSize * 2.2));
var availableHeight = Math.Max(1, RootBorder.Bounds.Height > 1 ? RootBorder.Bounds.Height : Math.Clamp(_lastAppliedCellSize * 0.74, 34, 74));
var contentWidth = Math.Max(1, availableWidth - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(1, availableHeight - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var mainLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
MainTimeTextBlock.Text,
contentWidth * (SecondsTextBlock.IsVisible ? 0.78d : 0.84d),
contentHeight * 0.80d,
minLines: 1,
maxLines: 1,
minFontSize: Math.Clamp(18 * layoutScale, 16, 28),
maxFontSize: Math.Clamp(44 * layoutScale, 28, 64),
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 0.96d);
MainTimeTextBlock.FontSize = mainLayout.FontSize;
MainTimeTextBlock.FontWeight = mainLayout.Weight;
if (SecondsTextBlock.IsVisible)
{
var secondsLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
SecondsTextBlock.Text,
contentWidth * 0.28d,
contentHeight * 0.46d,
minLines: 1,
maxLines: 1,
minFontSize: Math.Clamp(11 * layoutScale, 9, 18),
maxFontSize: Math.Clamp(28 * layoutScale, 14, 34),
weightCandidates: new[] { FontWeight.Medium, FontWeight.Normal },
lineHeightFactor: 0.96d);
SecondsTextBlock.FontSize = secondsLayout.FontSize;
SecondsTextBlock.FontWeight = secondsLayout.Weight;
SecondsTextBlock.Opacity = 0.55;
}
if (MainTimeTextBlock.Parent is StackPanel panel)
{
panel.Spacing = Math.Clamp(contentHeight * 0.06 * layoutScale, 2, 8);
}
if (_transparentBackground)
{
RootBorder.MinWidth = 0;
RootBorder.Padding = new Thickness(Math.Clamp(_lastAppliedCellSize * 0.06 * layoutScale, 4, 10), 0);
return;
}
RootBorder.MinWidth = _lastAppliedCellSize * 2.2;
RootBorder.Padding = new Thickness(Math.Clamp(_lastAppliedCellSize * 0.15 * layoutScale, 12, 24), 0);
RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.15, 12, 24), 0);
}
private void ApplyChrome()

View File

@@ -16,7 +16,6 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -600,40 +599,17 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
News2TitleTextBlock.MaxWidth = availableTextWidth;
var newsFont = Math.Clamp(21 * scale, 10.5, 28);
var newsHeightBudget = Math.Max(28, imageHeight + columnGap * 2d);
var news1Layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
News1TitleTextBlock.Text,
availableTextWidth,
newsHeightBudget,
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(News1TitleTextBlock.Text) > 30 ? 2 : 1,
minFontSize: Math.Clamp(newsFont * 0.72, 10.5, 18),
maxFontSize: newsFont,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.14d,
fontFamily: MiSansFontFamily);
var news2Layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
News2TitleTextBlock.Text,
availableTextWidth,
newsHeightBudget,
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(News2TitleTextBlock.Text) > 30 ? 2 : 1,
minFontSize: Math.Clamp(newsFont * 0.72, 10.5, 18),
maxFontSize: newsFont,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.14d,
fontFamily: MiSansFontFamily);
News1TitleTextBlock.FontSize = news1Layout.FontSize;
News1TitleTextBlock.LineHeight = news1Layout.LineHeight;
News1TitleTextBlock.MinHeight = news1Layout.LineHeight * news1Layout.MaxLines;
News1TitleTextBlock.MaxLines = news1Layout.MaxLines;
News1TitleTextBlock.FontWeight = news1Layout.Weight;
News2TitleTextBlock.FontSize = news2Layout.FontSize;
News2TitleTextBlock.LineHeight = news2Layout.LineHeight;
News2TitleTextBlock.MinHeight = news2Layout.LineHeight * news2Layout.MaxLines;
News2TitleTextBlock.MaxLines = news2Layout.MaxLines;
News2TitleTextBlock.FontWeight = news2Layout.Weight;
News1TitleTextBlock.FontSize = newsFont;
News2TitleTextBlock.FontSize = newsFont;
var mainNewsLineHeight = newsFont * 1.14;
News1TitleTextBlock.LineHeight = mainNewsLineHeight;
News2TitleTextBlock.LineHeight = mainNewsLineHeight;
var mainNewsMinHeight = mainNewsLineHeight * 2;
News1TitleTextBlock.MinHeight = mainNewsMinHeight;
News2TitleTextBlock.MinHeight = mainNewsMinHeight;
StatusTextBlock.FontSize = Math.Clamp(16 * scale, 9, 24);
News1TitleTextBlock.MaxLines = 2;
News2TitleTextBlock.MaxLines = 2;
foreach (var row in _extraNewsRows)
{
@@ -647,23 +623,11 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
row.ImageHost.Height = imageHeight;
row.ImageHost.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(16 * scale, 8, 22);
var rowTitleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
row.TitleTextBlock.Text,
availableTextWidth,
Math.Max(32, imageHeight + columnGap),
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(row.TitleTextBlock.Text) > 28 ? 2 : 1,
minFontSize: Math.Clamp(19 * scale, 10, 16),
maxFontSize: Math.Clamp(19 * scale, 10, 25),
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.12d,
fontFamily: MiSansFontFamily);
row.TitleTextBlock.MaxWidth = availableTextWidth;
row.TitleTextBlock.FontSize = rowTitleLayout.FontSize;
row.TitleTextBlock.LineHeight = rowTitleLayout.LineHeight;
row.TitleTextBlock.MinHeight = rowTitleLayout.LineHeight * rowTitleLayout.MaxLines;
row.TitleTextBlock.MaxLines = rowTitleLayout.MaxLines;
row.TitleTextBlock.FontWeight = rowTitleLayout.Weight;
row.TitleTextBlock.FontSize = Math.Clamp(19 * scale, 10, 25);
row.TitleTextBlock.LineHeight = row.TitleTextBlock.FontSize * 1.12;
row.TitleTextBlock.MinHeight = row.TitleTextBlock.LineHeight * 2;
row.TitleTextBlock.MaxLines = 2;
}
ExtraNewsItemsPanel.Spacing = Math.Clamp(6 * scale, 3, 10);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -13,7 +13,6 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
@@ -27,13 +26,13 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
private static readonly IReadOnlyDictionary<DayOfWeek, string> ZhWeekdays =
new Dictionary<DayOfWeek, string>
{
[DayOfWeek.Monday] = "星期一",
[DayOfWeek.Tuesday] = "星期二",
[DayOfWeek.Wednesday] = "星期三",
[DayOfWeek.Thursday] = "星期四",
[DayOfWeek.Friday] = "星期五",
[DayOfWeek.Saturday] = "星期六",
[DayOfWeek.Sunday] = "星期日"
[DayOfWeek.Monday] = "星期一",
[DayOfWeek.Tuesday] = "星期二",
[DayOfWeek.Wednesday] = "星期三",
[DayOfWeek.Thursday] = "星期四",
[DayOfWeek.Friday] = "星期五",
[DayOfWeek.Saturday] = "星期六",
[DayOfWeek.Sunday] = "星期日"
};
private static readonly Regex MultiWhitespaceRegex = new(@"\s+", RegexOptions.Compiled);
@@ -450,7 +449,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
var leftSingleLineHeight = Math.Max(12, (leftContentHeight - dateStackSpacing) / 2d);
var dateBase = Math.Clamp(44 * scale, 16, 62);
DateTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
DateTextBlock.FontSize = FitFontSize(
DateTextBlock.Text,
leftContentWidth,
leftSingleLineHeight,
@@ -458,11 +457,10 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
minFontSize: Math.Max(12, dateBase * 0.68),
maxFontSize: dateBase,
weight: FontWeight.Bold,
lineHeightFactor: 1.10,
fontFamily: MiSansFontFamily);
lineHeightFactor: 1.10);
DateTextBlock.LineHeight = DateTextBlock.FontSize * 1.10;
WeekdayTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
WeekdayTextBlock.FontSize = FitFontSize(
WeekdayTextBlock.Text,
leftContentWidth,
leftSingleLineHeight,
@@ -470,8 +468,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
minFontSize: Math.Max(12, dateBase * 0.68),
maxFontSize: dateBase,
weight: FontWeight.Bold,
lineHeightFactor: 1.10,
fontFamily: MiSansFontFamily);
lineHeightFactor: 1.10);
WeekdayTextBlock.LineHeight = WeekdayTextBlock.FontSize * 1.10;
var rightContentHeight = Math.Max(42, totalHeight - rootPadding.Top - rootPadding.Bottom - InfoPanel.Padding.Top - InfoPanel.Padding.Bottom);
@@ -519,7 +516,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
yearHeightBudget = minYearHeight + extraHeight * (yearWeight / weightSum);
}
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
var titleLayout = FitAdaptiveTextLayout(
PaintingTitleTextBlock.Text,
rightContentWidth,
titleHeightBudget,
@@ -528,8 +525,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
minFontSize: titleMin,
maxFontSize: titleBase,
weightCandidates: TitleWeightCandidates,
lineHeightFactor: 1.10,
fontFamily: MiSansFontFamily);
lineHeightFactor: 1.10);
PaintingTitleTextBlock.MaxWidth = rightContentWidth;
PaintingTitleTextBlock.Margin = new Thickness(0, 0, 0, titleBottomMargin);
PaintingTitleTextBlock.MaxLines = titleLayout.MaxLines;
@@ -542,7 +538,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
artistInfoStack.Spacing = bottomStackSpacing;
}
var artistLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
var artistLayout = FitAdaptiveTextLayout(
ArtistTextBlock.Text,
rightContentWidth,
artistHeightBudget,
@@ -551,15 +547,14 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
minFontSize: artistMin,
maxFontSize: artistBase,
weightCandidates: ArtistWeightCandidates,
lineHeightFactor: 1.14,
fontFamily: MiSansFontFamily);
lineHeightFactor: 1.14);
ArtistTextBlock.MaxWidth = rightContentWidth;
ArtistTextBlock.MaxLines = artistLayout.MaxLines;
ArtistTextBlock.FontWeight = artistLayout.Weight;
ArtistTextBlock.FontSize = artistLayout.FontSize;
ArtistTextBlock.LineHeight = artistLayout.LineHeight;
var yearLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
var yearLayout = FitAdaptiveTextLayout(
YearTextBlock.Text,
rightContentWidth,
yearHeightBudget,
@@ -568,8 +563,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
minFontSize: yearMin,
maxFontSize: yearBase,
weightCandidates: SecondaryWeightCandidates,
lineHeightFactor: 1.08,
fontFamily: MiSansFontFamily);
lineHeightFactor: 1.08);
YearTextBlock.MaxWidth = rightContentWidth;
YearTextBlock.MaxLines = yearLayout.MaxLines;
YearTextBlock.FontWeight = yearLayout.Weight;
@@ -723,7 +717,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
normalized = "Untitled";
}
return $"“{normalized}”";
return $"“{normalized}”";
}
private void CancelRefreshRequest()
@@ -777,4 +771,222 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
return MultiWhitespaceRegex.Replace(text.Trim(), " ");
}
private static double FitFontSize(
string? text,
double maxWidth,
double maxHeight,
int maxLines,
double minFontSize,
double maxFontSize,
FontWeight weight,
double lineHeightFactor)
{
var content = string.IsNullOrWhiteSpace(text) ? " " : text.Trim();
var min = Math.Max(6, minFontSize);
var max = Math.Max(min, maxFontSize);
var low = min;
var high = max;
var best = min;
for (var i = 0; i < 18; i++)
{
var candidate = (low + high) / 2d;
var lineHeight = candidate * lineHeightFactor;
var size = MeasureTextSize(content, candidate, weight, Math.Max(1, maxWidth), lineHeight);
var lineCount = Math.Max(1, (int)Math.Ceiling(size.Height / Math.Max(1, lineHeight)));
var fits = size.Height <= maxHeight + 0.6 && lineCount <= Math.Max(1, maxLines);
if (fits)
{
best = candidate;
low = candidate;
}
else
{
high = candidate;
}
}
return best;
}
private static AdaptiveTextLayout FitAdaptiveTextLayout(
string? text,
double maxWidth,
double maxHeight,
int minLines,
int maxLines,
double minFontSize,
double maxFontSize,
FontWeight[] weightCandidates,
double lineHeightFactor)
{
var content = string.IsNullOrWhiteSpace(text) ? " " : text.Trim();
var safeMinLines = Math.Max(1, minLines);
var safeMaxLines = Math.Max(safeMinLines, maxLines);
var linesByHeight = ResolveMaxLinesByHeight(maxHeight, minFontSize, lineHeightFactor, safeMinLines, safeMaxLines);
var candidates = weightCandidates is { Length: > 0 }
? weightCandidates
: new[] { FontWeight.Normal };
AdaptiveTextLayout? best = null;
foreach (var weight in candidates)
{
for (var lineLimit = linesByHeight; lineLimit >= safeMinLines; lineLimit--)
{
var fontSize = FitFontSize(
content,
maxWidth,
maxHeight,
lineLimit,
minFontSize,
maxFontSize,
weight,
lineHeightFactor);
var lineHeight = fontSize * lineHeightFactor;
var measuredSize = MeasureTextSize(content, fontSize, weight, Math.Max(1, maxWidth), lineHeight);
var measuredLineCount = ResolveLineCount(measuredSize.Height, lineHeight);
var overflowLines = Math.Max(0, measuredLineCount - lineLimit);
var overflowHeight = Math.Max(0, measuredSize.Height - maxHeight);
var overflowScore = overflowLines * 1000d + overflowHeight;
var fitsCompletely = overflowLines == 0 && overflowHeight <= 0.6;
var candidate = new AdaptiveTextLayout(fontSize, weight, lineLimit, lineHeight, overflowScore, fitsCompletely);
if (best is null || IsBetterAdaptiveTextCandidate(candidate, best.Value))
{
best = candidate;
}
}
}
if (best is not null)
{
return best.Value;
}
var fallbackFontSize = Math.Max(6, minFontSize);
return new AdaptiveTextLayout(
fallbackFontSize,
FontWeight.Normal,
safeMinLines,
fallbackFontSize * lineHeightFactor,
double.MaxValue,
fitsCompletely: false);
}
private static bool IsBetterAdaptiveTextCandidate(AdaptiveTextLayout candidate, AdaptiveTextLayout best)
{
if (candidate.FitsCompletely && !best.FitsCompletely)
{
return true;
}
if (!candidate.FitsCompletely && best.FitsCompletely)
{
return false;
}
if (candidate.FitsCompletely && best.FitsCompletely)
{
if (candidate.FontSize > best.FontSize + 0.12)
{
return true;
}
if (Math.Abs(candidate.FontSize - best.FontSize) <= 0.12 && candidate.MaxLines < best.MaxLines)
{
return true;
}
return false;
}
if (candidate.OverflowScore < best.OverflowScore - 0.2)
{
return true;
}
if (Math.Abs(candidate.OverflowScore - best.OverflowScore) <= 0.2 &&
candidate.FontSize > best.FontSize + 0.12)
{
return true;
}
if (Math.Abs(candidate.OverflowScore - best.OverflowScore) <= 0.2 &&
Math.Abs(candidate.FontSize - best.FontSize) <= 0.12 &&
candidate.MaxLines > best.MaxLines)
{
return true;
}
return false;
}
private static int ResolveMaxLinesByHeight(
double maxHeight,
double minFontSize,
double lineHeightFactor,
int minLines,
int maxLines)
{
var safeMinLines = Math.Max(1, minLines);
var safeMaxLines = Math.Max(safeMinLines, maxLines);
var lineHeight = Math.Max(1, Math.Max(6, minFontSize) * lineHeightFactor);
var maxHeightWithTolerance = Math.Max(1, maxHeight + 0.6);
var linesByHeight = (int)Math.Floor(maxHeightWithTolerance / lineHeight);
return Math.Clamp(linesByHeight, safeMinLines, safeMaxLines);
}
private static int ResolveLineCount(double measuredHeight, double lineHeight)
{
return Math.Max(1, (int)Math.Ceiling(measuredHeight / Math.Max(1, lineHeight)));
}
private readonly struct AdaptiveTextLayout
{
public AdaptiveTextLayout(
double fontSize,
FontWeight weight,
int maxLines,
double lineHeight,
double overflowScore,
bool fitsCompletely)
{
FontSize = fontSize;
Weight = weight;
MaxLines = Math.Max(1, maxLines);
LineHeight = lineHeight;
OverflowScore = overflowScore;
FitsCompletely = fitsCompletely;
}
public double FontSize { get; }
public FontWeight Weight { get; }
public int MaxLines { get; }
public double LineHeight { get; }
public double OverflowScore { get; }
public bool FitsCompletely { get; }
}
private static Size MeasureTextSize(string text, double fontSize, FontWeight weight, double maxWidth, double lineHeight)
{
var probe = new TextBlock
{
Text = text,
FontFamily = MiSansFontFamily,
FontSize = fontSize,
FontWeight = weight,
TextWrapping = TextWrapping.Wrap,
LineHeight = lineHeight
};
probe.Measure(new Size(Math.Max(1, maxWidth), double.PositiveInfinity));
return probe.DesiredSize;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -11,7 +11,6 @@ using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -49,6 +48,8 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
private const double MinPoetryFontSize = 8;
private const double MinAuthorFontSize = 7;
private readonly record struct TextFitResult(double FontSize, FontWeight FontWeight, double LineHeight);
private readonly DispatcherTimer _refreshTimer = new()
{
Interval = TimeSpan.FromHours(6)
@@ -267,7 +268,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
{
if (!string.IsNullOrWhiteSpace(snapshot.Origin))
{
return $"{snapshot.Origin.Trim()} · {snapshot.Author.Trim()}";
return $"{snapshot.Origin.Trim()} \u00B7 {snapshot.Author.Trim()}";
}
return snapshot.Author.Trim();
@@ -296,7 +297,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
private void ApplyLoadingState()
{
_poetryRawText = L("poetry.widget.loading_content", "Loading...");
_authorRawText = L("poetry.widget.loading_author", "·");
_authorRawText = L("poetry.widget.loading_author", "...");
StatusTextBlock.IsVisible = false;
ApplyModeVisualIfNeeded(force: true);
}
@@ -483,29 +484,23 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
minFontWeight: ToVariableWeight(poemMinWeight),
lineHeightFactor: 1.12);
var poemFit = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
var poemFit = FitTextStable(
poemPrepared,
poemWidth,
availablePoemHeight,
minFontSize: poemMinFontSize,
maxFontSize: Math.Clamp(poemPreferredFontSize * 1.20, poemMinFontSize, 62),
minLines: poemMaxLines,
maxLines: poemMaxLines,
weightCandidates: new[]
{
ToVariableWeight(poemMinWeight),
ToVariableWeight((poemMinWeight + poemMaxWeight) / 2d),
ToVariableWeight(poemMaxWeight)
},
lineHeightFactor: 1.12,
fontFamily: MiSansFontFamily);
minWeight: poemMinWeight,
maxWeight: poemMaxWeight);
PoetryContentTextBlock.Text = poemPrepared;
PoetryContentTextBlock.MaxWidth = poemWidth;
PoetryContentTextBlock.MaxLines = poemMaxLines;
PoetryContentTextBlock.FontSize = poemFit.FontSize;
PoetryContentTextBlock.LineHeight = poemFit.LineHeight;
PoetryContentTextBlock.FontWeight = poemFit.Weight;
PoetryContentTextBlock.FontWeight = poemFit.FontWeight;
var authorWidth = Math.Max(72, Math.Min(innerWidth * (isNightMode ? 0.5 : 0.56), innerWidth - 8));
var authorUnitsTarget = 20;
@@ -525,22 +520,16 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
minFontWeight: ToVariableWeight(authorMinWeight),
lineHeightFactor: 1.12);
var authorFit = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
var authorFit = FitTextStable(
authorPrepared,
authorWidth,
AuthorAccent.Height,
minFontSize: authorMinFontSize,
maxFontSize: Math.Clamp(authorPreferredFontSize * 1.15, authorMinFontSize, 42),
minLines: 1,
maxLines: 1,
weightCandidates: new[]
{
ToVariableWeight(authorMinWeight),
ToVariableWeight((authorMinWeight + authorMaxWeight) / 2d),
ToVariableWeight(authorMaxWeight)
},
lineHeightFactor: 1.12,
fontFamily: MiSansFontFamily);
minWeight: authorMinWeight,
maxWeight: authorMaxWeight);
AuthorTextBlock.Text = authorPrepared;
AuthorTextBlock.TextWrapping = TextWrapping.NoWrap;
@@ -548,7 +537,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
AuthorTextBlock.MaxWidth = authorWidth;
AuthorTextBlock.FontSize = authorFit.FontSize;
AuthorTextBlock.LineHeight = authorFit.LineHeight;
AuthorTextBlock.FontWeight = authorFit.Weight;
AuthorTextBlock.FontWeight = authorFit.FontWeight;
}
private void UpdateRefreshButtonState()
@@ -702,7 +691,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
if (current.Length == 0)
{
if (ComponentTypographyLayoutService.CountTextDisplayUnits(remain) <= target || lines.Count == lineLimit - 1)
if (EstimateDisplayUnits(remain) <= target || lines.Count == lineLimit - 1)
{
current.Append(remain);
remain = string.Empty;
@@ -725,7 +714,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
}
var merged = current + remain;
if (ComponentTypographyLayoutService.CountTextDisplayUnits(merged) <= target || lines.Count == lineLimit - 1)
if (EstimateDisplayUnits(merged) <= target || lines.Count == lineLimit - 1)
{
current.Append(remain);
remain = string.Empty;
@@ -859,13 +848,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
}
var lineHeight = fontSize * lineHeightFactor;
var measured = ComponentTypographyLayoutService.MeasureTextSize(
text,
fontSize,
fontWeight,
maxWidth,
lineHeight,
MiSansFontFamily);
var measured = MeasureTextSize(text, fontSize, fontWeight, maxWidth, lineHeight);
var lineCount = Math.Max(1, (int)Math.Ceiling(measured.Height / Math.Max(1, lineHeight)));
return measured.Height <= maxHeight + 0.6 && lineCount <= Math.Max(1, maxLines);
}
@@ -908,6 +891,86 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
return text.Length;
}
private static double EstimateDisplayUnits(string text)
{
var units = 0d;
foreach (var ch in text)
{
units += ch <= 127 ? 0.56 : 1d;
}
return units;
}
private static TextFitResult FitTextStable(
string? text,
double maxWidth,
double maxHeight,
double minFontSize,
double maxFontSize,
int maxLines,
double lineHeightFactor,
double minWeight,
double maxWeight)
{
var normalizedText = string.IsNullOrWhiteSpace(text) ? " " : text.Trim();
var min = Math.Max(6, minFontSize);
var max = Math.Max(min, maxFontSize);
var low = min;
var high = max;
var bestSize = min;
var bestWeight = ToVariableWeight(minWeight);
for (var i = 0; i < 22; i++)
{
var candidate = (low + high) / 2d;
var progress = max <= min
? 0
: Math.Clamp((candidate - min) / (max - min), 0, 1);
var candidateWeight = ToVariableWeight(Lerp(minWeight, maxWeight, progress));
var lineHeight = candidate * lineHeightFactor;
var measured = MeasureTextSize(normalizedText, candidate, candidateWeight, Math.Max(1, maxWidth), lineHeight);
var lineCount = Math.Max(1, (int)Math.Ceiling(measured.Height / Math.Max(1, lineHeight)));
var fits = measured.Height <= maxHeight + 0.6 && lineCount <= Math.Max(1, maxLines);
if (fits)
{
bestSize = candidate;
bestWeight = candidateWeight;
low = candidate;
}
else
{
high = candidate;
}
}
var lineHeightResult = bestSize * lineHeightFactor;
return new TextFitResult(bestSize, bestWeight, lineHeightResult);
}
private static Size MeasureTextSize(
string text,
double fontSize,
FontWeight fontWeight,
double maxWidth,
double lineHeight)
{
var probe = new TextBlock
{
Text = text,
FontFamily = MiSansFontFamily,
FontSize = fontSize,
FontWeight = fontWeight,
TextWrapping = TextWrapping.Wrap,
LineHeight = lineHeight
};
probe.Measure(new Size(Math.Max(1, maxWidth), double.PositiveInfinity));
return probe.DesiredSize;
}
private static FontWeight ToVariableWeight(double weight)
{

View File

@@ -9,7 +9,6 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
@@ -75,7 +74,6 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
UpdateCurrencyLabels();
UpdateAmounts();
ApplyLoadingState();
UpdateTypography();
}
public void ApplyCellSize(double cellSize)
@@ -84,7 +82,6 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
var scale = ResolveScale();
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 14, 48);
RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(12 * scale, 12 * scale, null, 0.55d);
UpdateTypography();
}
public void SetRecommendationInfoService(IRecommendationInfoService recommendationInfoService)
@@ -246,7 +243,6 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
FromCurrencyNameTextBlock.Text = IsZh() ? from.ZhName : from.EnName;
ToCurrencyCodeTextBlock.Text = to.Code;
ToCurrencyNameTextBlock.Text = IsZh() ? to.ZhName : to.EnName;
UpdateTypography();
}
private void UpdateAmounts()
@@ -262,7 +258,6 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
_fromCurrency,
_calculatorDataService.FormatAmount(_currentRate, maxFractionDigits: 6),
_toCurrency);
UpdateTypography();
}
private void ApplyLoadingState()
@@ -338,103 +333,6 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.72, 1.95);
}
private void UpdateTypography()
{
var layoutWidth = LayoutRoot.Width > 1 ? LayoutRoot.Width : 304d;
var layoutHeight = LayoutRoot.Height > 1 ? LayoutRoot.Height : 304d;
var fromStackWidth = Math.Max(72, (layoutWidth - 86) * 0.36);
var amountWidth = Math.Max(116, (layoutWidth - 86) * 0.50);
var rateWidth = Math.Max(120, layoutWidth - 24);
var statusWidth = Math.Max(120, layoutWidth - 24);
FromCurrencyCodeTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
FromCurrencyCodeTextBlock.Text,
fromStackWidth,
26,
1,
11,
20,
FontWeight.SemiBold,
1.06d,
MiSansFontFamily);
FromCurrencyNameTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
FromCurrencyNameTextBlock.Text,
fromStackWidth,
18,
1,
9,
14,
FontWeight.Normal,
1.06d,
MiSansFontFamily);
ToCurrencyCodeTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
ToCurrencyCodeTextBlock.Text,
fromStackWidth,
26,
1,
11,
20,
FontWeight.SemiBold,
1.06d,
MiSansFontFamily);
ToCurrencyNameTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
ToCurrencyNameTextBlock.Text,
fromStackWidth,
18,
1,
9,
14,
FontWeight.Normal,
1.06d,
MiSansFontFamily);
InputAmountTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
InputAmountTextBlock.Text,
amountWidth,
Math.Max(42, layoutHeight * 0.16),
1,
20,
42,
FontWeight.Bold,
1.02d,
MiSansFontFamily);
ConvertedAmountTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
ConvertedAmountTextBlock.Text,
amountWidth,
Math.Max(42, layoutHeight * 0.16),
1,
20,
42,
FontWeight.Bold,
1.02d,
MiSansFontFamily);
RateTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
RateTextBlock.Text,
rateWidth,
20,
1,
10,
15,
FontWeight.Normal,
1.06d,
MiSansFontFamily);
if (StatusTextBlock.IsVisible)
{
StatusTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
StatusTextBlock.Text,
statusWidth,
22,
1,
10,
16,
FontWeight.Normal,
1.06d,
MiSansFontFamily);
}
}
private void CancelRefreshRequest()
{
var cts = Interlocked.Exchange(ref _refreshCts, null);

View File

@@ -9,7 +9,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
@@ -545,91 +544,34 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
var temperatureSample = string.IsNullOrWhiteSpace(TemperatureTextBlock.Text)
? "00°"
: TemperatureTextBlock.Text.Trim();
var temperatureGlyphCount = Math.Clamp(temperatureSample.Length, 3, 6);
var temperatureMaxWidth = Math.Max(30, innerWidth - iconSize - SummaryGrid.ColumnSpacing - 6);
var rawTemperatureSize = Math.Clamp(Lerp(72, 102, iconGrowth) * topScale, 14, 340);
var temperatureLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TemperatureTextBlock.Text,
temperatureMaxWidth,
Math.Max(18, summaryHeight * 0.84),
1,
1,
Math.Max(10, rawTemperatureSize * 0.42),
rawTemperatureSize,
[ToVariableWeight(Lerp(300, 380, emphasis))],
1.02);
TemperatureTextBlock.FontSize = temperatureLayout.FontSize;
TemperatureTextBlock.FontWeight = temperatureLayout.Weight;
var fitTemperatureSize = temperatureMaxWidth / (temperatureGlyphCount * 0.62);
TemperatureTextBlock.FontSize = Math.Clamp(Math.Min(rawTemperatureSize, fitTemperatureSize), 10, 340);
TemperatureTextBlock.FontWeight = ToVariableWeight(Lerp(300, 380, emphasis));
TemperatureTextBlock.MaxWidth = Math.Clamp(temperatureMaxWidth, 30, Math.Max(300, innerWidth * 0.66));
TemperatureTextBlock.Margin = new Thickness(0, Math.Clamp(-2.2 * topScale, -12, 0), 0, 0);
var cityBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
innerWidth * 0.36,
Math.Max(16, summaryHeight * 0.34),
preferredSizeScale: 0.28d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
CityInfoBadge.Padding = cityBadge.Padding;
CityInfoBadge.CornerRadius = new CornerRadius(cityBadge.Size / 2d);
CityInfoBadge.MaxWidth = Math.Clamp(innerWidth * 0.36, 34, 420);
var cityLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
CityTextBlock.Text,
Math.Max(24, CityInfoBadge.MaxWidth - cityBadge.Padding.Left - cityBadge.Padding.Right),
Math.Max(12, summaryHeight * 0.36),
1,
1,
6,
Math.Max(6, 18.5 * topScale),
[ToVariableWeight(Lerp(530, 620, emphasis))],
1.08);
CityTextBlock.FontSize = cityLayout.FontSize;
CityTextBlock.FontWeight = cityLayout.Weight;
CityTextBlock.LineHeight = cityLayout.LineHeight;
CityTextBlock.MaxWidth = CityInfoBadge.MaxWidth;
var conditionBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
innerWidth * 0.25,
Math.Max(16, summaryHeight * 0.34),
preferredSizeScale: 0.26d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
ConditionInfoBadge.Padding = conditionBadge.Padding;
ConditionInfoBadge.CornerRadius = new CornerRadius(conditionBadge.Size / 2d);
ConditionInfoBadge.MaxWidth = Math.Clamp(innerWidth * 0.25, 26, 340);
var conditionLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ConditionTextBlock.Text,
Math.Max(24, ConditionInfoBadge.MaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right),
Math.Max(12, summaryHeight * 0.30),
1,
1,
7,
Math.Max(6, 20 * topScale),
[ToVariableWeight(Lerp(580, 660, emphasis))],
1.06);
var rangeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
RangeTextBlock.Text,
Math.Max(24, ConditionInfoBadge.MaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right),
Math.Max(12, summaryHeight * 0.30),
1,
1,
7,
Math.Max(6, 20 * topScale),
[ToVariableWeight(Lerp(600, 680, emphasis))],
1.06);
ConditionTextBlock.FontSize = conditionLayout.FontSize;
ConditionTextBlock.FontWeight = conditionLayout.Weight;
ConditionTextBlock.LineHeight = conditionLayout.LineHeight;
RangeTextBlock.FontSize = rangeLayout.FontSize;
RangeTextBlock.FontWeight = rangeLayout.Weight;
RangeTextBlock.LineHeight = rangeLayout.LineHeight;
CityTextBlock.MaxWidth = CityInfoBadge.MaxWidth;
ConditionTextBlock.MaxWidth = ConditionInfoBadge.MaxWidth;
RangeTextBlock.MaxWidth = ConditionInfoBadge.MaxWidth;
var cityFontSize = Math.Clamp(18.5 * topScale, 7, 86);
var conditionFontSize = Math.Clamp(20 * topScale, 7, 90);
var rangeFontSize = Math.Clamp(20 * topScale, 7, 90);
CityTextBlock.FontSize = cityFontSize;
ConditionTextBlock.FontSize = conditionFontSize;
RangeTextBlock.FontSize = rangeFontSize;
CityTextBlock.FontWeight = ToVariableWeight(Lerp(530, 620, emphasis));
ConditionTextBlock.FontWeight = ToVariableWeight(Lerp(580, 660, emphasis));
RangeTextBlock.FontWeight = ToVariableWeight(Lerp(600, 680, emphasis));
CityTextBlock.LineHeight = cityFontSize * 1.08;
ConditionTextBlock.LineHeight = conditionFontSize * 1.06;
RangeTextBlock.LineHeight = rangeFontSize * 1.06;
WeatherIconImage.Width = iconSize;
WeatherIconImage.Height = iconSize;
WeatherIconImage.Margin = new Thickness(0, Math.Clamp(-2.4 * topScale, -12, 0), 0, 0);
ConditionTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.25, 28, 340);
RangeTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.31, 34, 380);
CityTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.36, 34, 420);
HourlyPanelBorder.Padding = new Thickness(0);
HourlyPanelBorder.CornerRadius = new CornerRadius(0);
@@ -648,30 +590,10 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
var hourlyStackSpacing = Math.Clamp(2 * hourlyCellScale, 0.2, 10);
for (var i = 0; i < _hourlyTempBlocks.Length; i++)
{
var hourlyTempLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_hourlyTempBlocks[i].Text,
Math.Clamp(hourlyCellWidth, 12, 260),
Math.Max(10, hourlyHeight * 0.42),
1,
1,
6,
hourlyTempSize,
[ToVariableWeight(Lerp(540, 650, emphasis))],
1.02);
var hourlyTimeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_hourlyTimeBlocks[i].Text,
Math.Clamp(hourlyCellWidth, 12, 260),
Math.Max(10, hourlyHeight * 0.34),
1,
1,
6,
hourlyTimeSize,
[ToVariableWeight(Lerp(450, 560, emphasis))],
1.02);
_hourlyTempBlocks[i].FontSize = hourlyTempLayout.FontSize;
_hourlyTimeBlocks[i].FontSize = hourlyTimeLayout.FontSize;
_hourlyTempBlocks[i].FontWeight = hourlyTempLayout.Weight;
_hourlyTimeBlocks[i].FontWeight = hourlyTimeLayout.Weight;
_hourlyTempBlocks[i].FontSize = hourlyTempSize;
_hourlyTimeBlocks[i].FontSize = hourlyTimeSize;
_hourlyTempBlocks[i].FontWeight = ToVariableWeight(Lerp(540, 650, emphasis));
_hourlyTimeBlocks[i].FontWeight = ToVariableWeight(Lerp(450, 560, emphasis));
_hourlyTempBlocks[i].MaxWidth = Math.Clamp(hourlyCellWidth, 12, 260);
_hourlyTimeBlocks[i].MaxWidth = Math.Clamp(hourlyCellWidth, 12, 260);
_hourlyIconBlocks[i].Width = hourlyIconSize;
@@ -698,42 +620,12 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
var dailyHighRightGap = Math.Clamp(innerWidth * 0.018, 1, 28);
for (var i = 0; i < _dailyLabelBlocks.Length; i++)
{
var dailyLabelLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_dailyLabelBlocks[i].Text,
dailyLabelMaxWidth,
Math.Max(10, dailyRowHeight * 0.50),
1,
1,
6,
dailyLabelSize,
[ToVariableWeight(Lerp(520, 620, emphasis))],
1.04);
var dailyHighLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_dailyHighBlocks[i].Text,
dailyHighWidth,
Math.Max(10, dailyRowHeight * 0.42),
1,
1,
6,
dailyTempSize,
[ToVariableWeight(Lerp(560, 680, emphasis))],
1.02);
var dailyLowLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_dailyLowBlocks[i].Text,
dailyLowWidth,
Math.Max(10, dailyRowHeight * 0.42),
1,
1,
6,
dailyTempSize,
[ToVariableWeight(Lerp(470, 590, emphasis))],
1.02);
_dailyLabelBlocks[i].FontSize = dailyLabelLayout.FontSize;
_dailyHighBlocks[i].FontSize = dailyHighLayout.FontSize;
_dailyLowBlocks[i].FontSize = dailyLowLayout.FontSize;
_dailyLabelBlocks[i].FontWeight = dailyLabelLayout.Weight;
_dailyHighBlocks[i].FontWeight = dailyHighLayout.Weight;
_dailyLowBlocks[i].FontWeight = dailyLowLayout.Weight;
_dailyLabelBlocks[i].FontSize = dailyLabelSize;
_dailyHighBlocks[i].FontSize = dailyTempSize;
_dailyLowBlocks[i].FontSize = dailyTempSize;
_dailyLabelBlocks[i].FontWeight = ToVariableWeight(Lerp(520, 620, emphasis));
_dailyHighBlocks[i].FontWeight = ToVariableWeight(Lerp(560, 680, emphasis));
_dailyLowBlocks[i].FontWeight = ToVariableWeight(Lerp(470, 590, emphasis));
_dailyLabelBlocks[i].MaxWidth = dailyLabelMaxWidth;
_dailyHighBlocks[i].Width = dailyHighWidth;
_dailyLowBlocks[i].Width = dailyLowWidth;

View File

@@ -11,7 +11,6 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
@@ -1272,88 +1271,31 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
var temperatureSample = string.IsNullOrWhiteSpace(TemperatureTextBlock.Text)
? "00°"
: TemperatureTextBlock.Text.Trim();
var temperatureGlyphCount = Math.Clamp(temperatureSample.Length, 3, 6);
var temperatureMaxWidth = Math.Max(28, innerWidth - iconSize - TopRowGrid.ColumnSpacing - 4);
var rawTemperatureSize = Math.Clamp(Lerp(64, 92, iconGrowth) * topScale, 12, 320);
var temperatureLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TemperatureTextBlock.Text,
temperatureMaxWidth,
Math.Max(18, topZoneHeight * 0.84),
1,
1,
Math.Max(9, rawTemperatureSize * 0.42),
rawTemperatureSize,
[ToVariableWeight(Lerp(300, 360, emphasis))],
1.02);
TemperatureTextBlock.FontSize = temperatureLayout.FontSize;
TemperatureTextBlock.FontWeight = temperatureLayout.Weight;
var fitTemperatureSize = temperatureMaxWidth / (temperatureGlyphCount * 0.62);
TemperatureTextBlock.FontSize = Math.Clamp(Math.Min(rawTemperatureSize, fitTemperatureSize), 9, 320);
TemperatureTextBlock.FontWeight = ToVariableWeight(Lerp(300, 360, emphasis));
TemperatureTextBlock.Margin = new Thickness(0, Math.Clamp(-2.0 * topScale, -10, 0), 0, 0);
TemperatureTextBlock.MaxWidth = Math.Clamp(temperatureMaxWidth, 28, Math.Max(280, innerWidth * 0.68));
var cityBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
innerWidth * 0.37,
Math.Max(16, topZoneHeight * 0.34),
preferredSizeScale: 0.28d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
CityInfoBadge.Padding = cityBadge.Padding;
CityInfoBadge.CornerRadius = new CornerRadius(cityBadge.Size / 2d);
CityInfoBadge.MaxWidth = Math.Clamp(innerWidth * 0.37, 34, 460);
CityInfoBadge.Padding = new Thickness(0);
CityInfoBadge.CornerRadius = new CornerRadius(0);
LocationIcon.FontSize = Math.Clamp(13 * topScale, 6, 52);
var cityLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
CityTextBlock.Text,
Math.Max(24, CityInfoBadge.MaxWidth - cityBadge.Padding.Left - cityBadge.Padding.Right),
Math.Max(12, topZoneHeight * 0.36),
1,
1,
6,
Math.Max(6, 18.5 * topScale),
[ToVariableWeight(Lerp(530, 620, emphasis))],
1.08);
CityTextBlock.FontSize = cityLayout.FontSize;
CityTextBlock.FontWeight = cityLayout.Weight;
CityTextBlock.LineHeight = cityLayout.LineHeight;
CityTextBlock.MaxWidth = CityInfoBadge.MaxWidth;
CityTextBlock.FontSize = Math.Clamp(18.5 * topScale, 7, 88);
CityTextBlock.FontWeight = ToVariableWeight(Lerp(530, 620, emphasis));
CityTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.37, 34, 460);
var conditionBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
innerWidth * 0.24,
Math.Max(16, bottomZoneHeight * 0.34),
preferredSizeScale: 0.26d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
ConditionInfoBadge.Padding = conditionBadge.Padding;
ConditionInfoBadge.CornerRadius = new CornerRadius(conditionBadge.Size / 2d);
ConditionInfoBadge.MaxWidth = Math.Clamp(innerWidth * 0.24, 26, 320);
ConditionInfoBadge.Padding = new Thickness(0);
ConditionInfoBadge.CornerRadius = new CornerRadius(0);
ConditionRangeStack.Spacing = Math.Clamp(8.5 * topScale, 1, 24);
var conditionLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ConditionTextBlock.Text,
Math.Max(24, ConditionInfoBadge.MaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right),
Math.Max(12, bottomZoneHeight * 0.30),
1,
1,
7,
Math.Max(6, 19 * topScale),
[ToVariableWeight(Lerp(580, 660, emphasis))],
1.10);
var rangeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
RangeTextBlock.Text,
Math.Max(24, ConditionInfoBadge.MaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right),
Math.Max(12, bottomZoneHeight * 0.30),
1,
1,
7,
Math.Max(6, 21 * topScale),
[ToVariableWeight(Lerp(600, 680, emphasis))],
1.10);
ConditionTextBlock.FontSize = conditionLayout.FontSize;
ConditionTextBlock.FontWeight = conditionLayout.Weight;
ConditionTextBlock.LineHeight = conditionLayout.LineHeight;
RangeTextBlock.FontSize = rangeLayout.FontSize;
RangeTextBlock.FontWeight = rangeLayout.Weight;
RangeTextBlock.LineHeight = rangeLayout.LineHeight;
ConditionTextBlock.MaxWidth = ConditionInfoBadge.MaxWidth;
RangeTextBlock.MaxWidth = ConditionInfoBadge.MaxWidth;
ConditionTextBlock.FontSize = Math.Clamp(19 * topScale, 7, 78);
RangeTextBlock.FontSize = Math.Clamp(21 * topScale, 7, 84);
ConditionTextBlock.FontWeight = ToVariableWeight(Lerp(580, 660, emphasis));
RangeTextBlock.FontWeight = ToVariableWeight(Lerp(600, 680, emphasis));
ConditionTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.24, 26, 320);
RangeTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.31, 32, 360);
BottomInfoStack.Spacing = Math.Clamp(2.0 * topScale, 0.4, 14);
WeatherIconImage.Width = iconSize;
@@ -1383,34 +1325,14 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
for (var i = 0; i < _hourlyTimeBlocks.Length; i++)
{
var hourlyTempLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_hourlyTempBlocks[i].Text,
Math.Clamp(hourlyCellWidth, 12, 240),
Math.Max(10, hourlyCellScale * 28),
1,
1,
6,
hourlyTempSize,
[ToVariableWeight(Lerp(580, 690, emphasis))],
1.02);
var hourlyTimeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_hourlyTimeBlocks[i].Text,
Math.Clamp(hourlyCellWidth, 12, 240),
Math.Max(10, hourlyCellScale * 24),
1,
1,
6,
hourlyTimeSize,
[ToVariableWeight(Lerp(500, 600, emphasis))],
1.02);
_hourlyTempBlocks[i].FontSize = hourlyTempLayout.FontSize;
_hourlyTimeBlocks[i].FontSize = hourlyTimeLayout.FontSize;
_hourlyTempBlocks[i].FontSize = hourlyTempSize;
_hourlyTimeBlocks[i].FontSize = hourlyTimeSize;
_hourlyIconBlocks[i].Width = hourlyIconSize;
_hourlyIconBlocks[i].Height = hourlyIconSize;
_hourlyTimeBlocks[i].MaxWidth = Math.Clamp(hourlyCellWidth, 12, 240);
_hourlyTempBlocks[i].MaxWidth = Math.Clamp(hourlyCellWidth, 12, 240);
_hourlyTimeBlocks[i].FontWeight = hourlyTimeLayout.Weight;
_hourlyTempBlocks[i].FontWeight = hourlyTempLayout.Weight;
_hourlyTimeBlocks[i].FontWeight = ToVariableWeight(Lerp(500, 600, emphasis));
_hourlyTempBlocks[i].FontWeight = ToVariableWeight(Lerp(580, 690, emphasis));
if (_hourlyTimeBlocks[i].Parent is StackPanel hourlyStack)
{
hourlyStack.Spacing = stackSpacing;

View File

@@ -15,7 +15,6 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -463,23 +462,11 @@ public partial class IfengNewsWidget : UserControl, IDesktopComponentWidget, IRe
visual.ImageHost.Height = imageHeight;
visual.ImageHost.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(imageHeight * 0.15, 8, 16);
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
visual.TitleTextBlock.Text,
textWidth,
itemHeight,
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(visual.TitleTextBlock.Text) > 28 ? 2 : 1,
minFontSize: Math.Clamp(titleFont * 0.72, 10, 16),
maxFontSize: titleFont,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.12d,
fontFamily: MiSansFontFamily);
visual.TitleTextBlock.MaxWidth = textWidth;
visual.TitleTextBlock.FontSize = titleLayout.FontSize;
visual.TitleTextBlock.LineHeight = titleLayout.LineHeight;
visual.TitleTextBlock.MinHeight = titleLayout.LineHeight * titleLayout.MaxLines;
visual.TitleTextBlock.MaxLines = titleLayout.MaxLines;
visual.TitleTextBlock.FontWeight = titleLayout.Weight;
visual.TitleTextBlock.FontSize = titleFont;
visual.TitleTextBlock.LineHeight = titleFont * 1.12;
visual.TitleTextBlock.MinHeight = visual.TitleTextBlock.LineHeight * 2;
visual.TitleTextBlock.MaxLines = 2;
}
StatusTextBlock.FontSize = Math.Clamp(titleFont, 10, 20);

View File

@@ -9,7 +9,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
@@ -1121,88 +1120,31 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
var temperatureSample = string.IsNullOrWhiteSpace(TemperatureTextBlock.Text)
? "00°"
: TemperatureTextBlock.Text.Trim();
var temperatureGlyphCount = Math.Clamp(temperatureSample.Length, 3, 6);
var temperatureMaxWidth = Math.Max(28, innerWidth - iconSize - TopRowGrid.ColumnSpacing - 4);
var rawTemperatureSize = Math.Clamp(Lerp(64, 92, iconGrowth) * topScale, 12, 320);
var temperatureLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TemperatureTextBlock.Text,
temperatureMaxWidth,
Math.Max(18, topZoneHeight * 0.84),
1,
1,
Math.Max(9, rawTemperatureSize * 0.42),
rawTemperatureSize,
[ToVariableWeight(Lerp(300, 360, emphasis))],
1.02);
TemperatureTextBlock.FontSize = temperatureLayout.FontSize;
TemperatureTextBlock.FontWeight = temperatureLayout.Weight;
var fitTemperatureSize = temperatureMaxWidth / (temperatureGlyphCount * 0.62);
TemperatureTextBlock.FontSize = Math.Clamp(Math.Min(rawTemperatureSize, fitTemperatureSize), 9, 320);
TemperatureTextBlock.FontWeight = ToVariableWeight(Lerp(300, 360, emphasis));
TemperatureTextBlock.Margin = new Thickness(0, Math.Clamp(-2.0 * topScale, -10, 0), 0, 0);
TemperatureTextBlock.MaxWidth = Math.Clamp(temperatureMaxWidth, 28, Math.Max(280, innerWidth * 0.68));
var cityBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
innerWidth * 0.37,
Math.Max(16, topZoneHeight * 0.34),
preferredSizeScale: 0.28d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
CityInfoBadge.Padding = cityBadge.Padding;
CityInfoBadge.CornerRadius = new CornerRadius(cityBadge.Size / 2d);
CityInfoBadge.MaxWidth = Math.Clamp(innerWidth * 0.37, 34, 460);
CityInfoBadge.Padding = new Thickness(0);
CityInfoBadge.CornerRadius = new CornerRadius(0);
LocationIcon.FontSize = Math.Clamp(13 * topScale, 6, 52);
var cityLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
CityTextBlock.Text,
Math.Max(24, CityInfoBadge.MaxWidth - cityBadge.Padding.Left - cityBadge.Padding.Right),
Math.Max(12, topZoneHeight * 0.36),
1,
1,
6,
Math.Max(6, 18.5 * topScale),
[ToVariableWeight(Lerp(530, 620, emphasis))],
1.08);
CityTextBlock.FontSize = cityLayout.FontSize;
CityTextBlock.FontWeight = cityLayout.Weight;
CityTextBlock.LineHeight = cityLayout.LineHeight;
CityTextBlock.MaxWidth = CityInfoBadge.MaxWidth;
CityTextBlock.FontSize = Math.Clamp(18.5 * topScale, 7, 88);
CityTextBlock.FontWeight = ToVariableWeight(Lerp(530, 620, emphasis));
CityTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.37, 34, 460);
var conditionBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
innerWidth * 0.24,
Math.Max(16, topZoneHeight * 0.34),
preferredSizeScale: 0.26d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
ConditionInfoBadge.Padding = conditionBadge.Padding;
ConditionInfoBadge.CornerRadius = new CornerRadius(conditionBadge.Size / 2d);
ConditionInfoBadge.MaxWidth = Math.Clamp(innerWidth * 0.24, 26, 320);
ConditionInfoBadge.Padding = new Thickness(0);
ConditionInfoBadge.CornerRadius = new CornerRadius(0);
ConditionIconStack.Spacing = Math.Clamp(8.5 * topScale, 1, 24);
var conditionLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ConditionTextBlock.Text,
Math.Max(24, ConditionInfoBadge.MaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right),
Math.Max(12, topZoneHeight * 0.30),
1,
1,
7,
Math.Max(6, 19 * topScale),
[ToVariableWeight(Lerp(580, 660, emphasis))],
1.06);
var rangeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
RangeTextBlock.Text,
Math.Max(24, ConditionInfoBadge.MaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right),
Math.Max(12, topZoneHeight * 0.30),
1,
1,
7,
Math.Max(6, 21 * topScale),
[ToVariableWeight(Lerp(600, 680, emphasis))],
1.06);
ConditionTextBlock.FontSize = conditionLayout.FontSize;
ConditionTextBlock.FontWeight = conditionLayout.Weight;
ConditionTextBlock.LineHeight = conditionLayout.LineHeight;
RangeTextBlock.FontSize = rangeLayout.FontSize;
RangeTextBlock.FontWeight = rangeLayout.Weight;
RangeTextBlock.LineHeight = rangeLayout.LineHeight;
ConditionTextBlock.MaxWidth = ConditionInfoBadge.MaxWidth;
RangeTextBlock.MaxWidth = ConditionInfoBadge.MaxWidth;
ConditionTextBlock.FontSize = Math.Clamp(19 * topScale, 7, 78);
RangeTextBlock.FontSize = Math.Clamp(21 * topScale, 7, 84);
ConditionTextBlock.FontWeight = ToVariableWeight(Lerp(580, 660, emphasis));
RangeTextBlock.FontWeight = ToVariableWeight(Lerp(600, 680, emphasis));
ConditionTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.24, 26, 320);
RangeTextBlock.MaxWidth = Math.Clamp(innerWidth * 0.31, 32, 360);
BottomInfoStack.Spacing = Math.Clamp(2.0 * topScale, 0.4, 14);
WeatherIconImage.Width = iconSize;
@@ -1231,34 +1173,14 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
for (var i = 0; i < _hourlyTimeBlocks.Length; i++)
{
var hourlyTimeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_hourlyTimeBlocks[i].Text,
Math.Clamp(hourlyCellWidth, 12, 260),
Math.Max(10, bottomZoneHeight * 0.34),
1,
1,
6,
forecastLabelSize,
[ToVariableWeight(Lerp(500, 600, emphasis))],
1.02);
var hourlyTempLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
_hourlyTempBlocks[i].Text,
Math.Clamp(hourlyCellWidth, 12, 260),
Math.Max(10, bottomZoneHeight * 0.42),
1,
1,
6,
forecastRangeSize,
[ToVariableWeight(Lerp(580, 690, emphasis))],
1.02);
_hourlyTimeBlocks[i].FontSize = hourlyTimeLayout.FontSize;
_hourlyTempBlocks[i].FontSize = hourlyTempLayout.FontSize;
_hourlyTimeBlocks[i].FontSize = forecastLabelSize;
_hourlyTempBlocks[i].FontSize = forecastRangeSize;
_hourlyIconBlocks[i].Width = forecastIconSize;
_hourlyIconBlocks[i].Height = forecastIconSize;
_hourlyTimeBlocks[i].MaxWidth = Math.Clamp(hourlyCellWidth, 12, 260);
_hourlyTempBlocks[i].MaxWidth = Math.Clamp(hourlyCellWidth, 12, 260);
_hourlyTimeBlocks[i].FontWeight = hourlyTimeLayout.Weight;
_hourlyTempBlocks[i].FontWeight = hourlyTempLayout.Weight;
_hourlyTimeBlocks[i].FontWeight = ToVariableWeight(Lerp(500, 600, emphasis));
_hourlyTempBlocks[i].FontWeight = ToVariableWeight(Lerp(580, 690, emphasis));
_hourlyTimeBlocks[i].TextAlignment = TextAlignment.Center;
_hourlyTempBlocks[i].TextAlignment = TextAlignment.Center;
if (_hourlyTimeBlocks[i].Parent is StackPanel hourlyStack)

View File

@@ -12,7 +12,6 @@ using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentIcons.Common;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -124,7 +123,6 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
NextIcon.FontSize = Math.Clamp(18 * scale, 13, 24);
FavoriteIcon.FontSize = Math.Clamp(16 * scale, 11, 21);
UpdateTypography();
UpdateProgressVisual(_progressRatio, _isProgressIndeterminate);
}
@@ -420,7 +418,6 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
SetCoverImage(state.ThumbnailBytes);
ApplyActionButtonState(state);
UpdateTypography();
UpdateSourceAppButtonTooltip();
}
@@ -555,67 +552,6 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
};
}
private void UpdateTypography()
{
var scale = ResolveScale();
var rootWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 10.5;
var rootHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 4.2;
var headerWidth = Math.Max(120, rootWidth - Math.Max(84, SourceAppButton.MinWidth) - 86);
var titleWidth = Math.Max(96, headerWidth);
var metaWidth = Math.Max(96, headerWidth);
var timelineWidth = Math.Max(52, rootWidth * 0.18);
var statusWidth = Math.Max(72, Math.Min(headerWidth, rootWidth * 0.26));
TitleTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
TitleTextBlock.Text,
titleWidth,
Math.Max(24, rootHeight * 0.12),
1,
12,
Math.Clamp(20 * scale, 12, 28),
FontWeight.SemiBold,
1.06d);
var artistMaxLines = ArtistTextBlock.MaxLines <= 0 ? 1 : ArtistTextBlock.MaxLines;
ArtistTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
ArtistTextBlock.Text,
metaWidth,
artistMaxLines > 1 ? Math.Max(32, rootHeight * 0.12) : Math.Max(20, rootHeight * 0.08),
artistMaxLines,
9,
Math.Clamp(14 * scale, 9, 18),
FontWeight.SemiBold,
1.06d);
PositionTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
PositionTextBlock.Text,
timelineWidth,
18,
1,
8,
Math.Clamp(13 * scale, 8, 15),
FontWeight.SemiBold,
1.05d);
DurationTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
DurationTextBlock.Text,
timelineWidth,
18,
1,
8,
Math.Clamp(13 * scale, 8, 15),
FontWeight.SemiBold,
1.05d);
StatusTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
StatusTextBlock.Text,
statusWidth,
18,
1,
8,
Math.Clamp(13 * scale, 8, 15),
FontWeight.Medium,
1.05d);
}
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);

View File

@@ -5,8 +5,6 @@ using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -75,6 +73,8 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
Resources["OfficeRecentDocumentsAccentSize"] = accentSize;
Resources["OfficeRecentDocumentsAccentCornerRadius"] = new CornerRadius(accentSize / 2d);
Resources["OfficeRecentDocumentsHeaderFontSize"] = Math.Clamp(18 * scale, 12, 24);
Resources["OfficeRecentDocumentsStatusFontSize"] = Math.Clamp(14 * scale, 10, 18);
Resources["OfficeRecentDocumentsDocumentSpacing"] = ComponentChromeCornerRadiusHelper.SafeValue(8 * scale, 4, 12, null, 0.40d);
var cardWidth = Math.Clamp(130 * scale, 96, 180);
@@ -83,7 +83,8 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
Resources["OfficeRecentDocumentsDocumentCardHeight"] = cardHeight;
Resources["OfficeRecentDocumentsCardCornerRadius"] = ComponentChromeCornerRadiusHelper.Scale(16 * scale, 10, 24);
Resources["OfficeRecentDocumentsCardPadding"] = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(10 * scale, 6, 16, null, 0.50d));
UpdateTypographyResources();
Resources["OfficeRecentDocumentsDocumentTitleFontSize"] = Math.Clamp(12 * scale, 10, 18);
Resources["OfficeRecentDocumentsDocumentTimeFontSize"] = Math.Clamp(10 * scale, 8, 14);
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
@@ -127,19 +128,16 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
{
StatusTextBlock.Text = "\u6682\u65e0\u6700\u8fd1\u6587\u6863";
StatusTextBlock.IsVisible = true;
UpdateTypographyResources();
return;
}
UpdateDisplay();
UpdateTypographyResources();
}
catch (Exception ex)
{
AppLogger.Warn("OfficeRecentDocsWidget", "Failed to load recent Office documents.", ex);
StatusTextBlock.Text = "\u52a0\u8f7d\u5931\u8d25";
StatusTextBlock.IsVisible = true;
UpdateTypographyResources();
}
finally
{
@@ -165,7 +163,6 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
}).ToList();
DocumentsItemsControl.ItemsSource = displayItems;
UpdateTypographyResources();
}
private static string GetTimeAgo(DateTime dateTime)
@@ -218,70 +215,4 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen
}
}
}
private void UpdateTypographyResources()
{
var width = Bounds.Width > 1 ? Bounds.Width : 640d;
var cardWidth = (double?)Resources["OfficeRecentDocumentsDocumentCardWidth"] ?? 130d;
var cardHeight = (double?)Resources["OfficeRecentDocumentsDocumentCardHeight"] ?? 90d;
var cardPadding = (Thickness?)Resources["OfficeRecentDocumentsCardPadding"] ?? new Thickness(10);
var rootPadding = (Thickness?)Resources["OfficeRecentDocumentsRootPadding"] ?? new Thickness(12, 10, 12, 10);
var contentMargin = (Thickness?)Resources["OfficeRecentDocumentsContentMargin"] ?? new Thickness(16, 14, 16, 14);
var innerWidth = Math.Max(180, width - rootPadding.Left - rootPadding.Right - contentMargin.Left - contentMargin.Right);
var headerWidth = Math.Max(120, innerWidth * 0.48);
var statusWidth = Math.Max(120, innerWidth * 0.40);
Resources["OfficeRecentDocumentsHeaderFontSize"] = ComponentTypographyLayoutService.FitFontSize(
HeaderTextBlock.Text,
headerWidth,
24,
1,
12,
24,
FontWeight.SemiBold,
1.05d);
Resources["OfficeRecentDocumentsStatusFontSize"] = ComponentTypographyLayoutService.FitFontSize(
StatusTextBlock.Text,
statusWidth,
22,
1,
10,
18,
FontWeight.Normal,
1.06d);
var documentTexts = _documents.Count == 0
? new[] { "Sample Office Document" }
: _documents.Select(item => item.FileName).Where(text => !string.IsNullOrWhiteSpace(text)).ToArray();
var longestDocumentText = documentTexts.Length == 0
? "Sample Office Document"
: documentTexts.OrderByDescending(ComponentTypographyLayoutService.CountTextDisplayUnits).First();
var titleWidth = Math.Max(72, cardWidth - cardPadding.Left - cardPadding.Right);
var titleHeight = Math.Max(28, cardHeight - cardPadding.Top - cardPadding.Bottom - 18);
Resources["OfficeRecentDocumentsDocumentTitleFontSize"] = ComponentTypographyLayoutService.FitFontSize(
longestDocumentText,
titleWidth,
titleHeight,
2,
10,
18,
FontWeight.Medium,
1.08d);
var timeSamples = _documents.Count == 0
? new[] { "00/00" }
: _documents.Select(item => GetTimeAgo(item.LastModifiedTime)).ToArray();
var longestTimeText = timeSamples.OrderByDescending(ComponentTypographyLayoutService.CountTextDisplayUnits).First();
Resources["OfficeRecentDocumentsDocumentTimeFontSize"] = ComponentTypographyLayoutService.FitFontSize(
longestTimeText,
Math.Max(56, titleWidth * 0.72),
18,
1,
8,
14,
FontWeight.Normal,
1.06d);
}
}

View File

@@ -10,7 +10,6 @@ using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -109,8 +108,10 @@ public partial class RecordingWidget : UserControl, IDesktopComponentWidget, IDe
HintTextBlock.Margin = new Thickness(0, Math.Clamp(8 * contentScale, 4, 10), 0, 0);
WaveformBarsPanel.Spacing = Math.Clamp(3 * contentScale, 1.6, 3.4);
TitleTextBlock.FontSize = Math.Clamp(19 * contentScale, 12, 20);
TimerTextBlock.FontSize = Math.Clamp(66 * contentScale, 34, 66);
HintTextBlock.FontSize = Math.Clamp(13 * contentScale, 9, 13);
UpdateTypography();
UpdateWaveformVisual();
}
@@ -378,43 +379,49 @@ public partial class RecordingWidget : UserControl, IDesktopComponentWidget, IDe
PauseGlyphIcon.IsVisible = snapshot.State == AudioRecorderRuntimeState.Recording;
PlayGlyphIcon.IsVisible = snapshot.State == AudioRecorderRuntimeState.Paused;
string hintText;
if (!isSupported)
{
hintText = L("recording.widget.hint.unsupported", "Microphone is unavailable");
HintTextBlock.Text = L("recording.widget.hint.unsupported", "Microphone is unavailable");
return;
}
else if (snapshot.State == AudioRecorderRuntimeState.Recording)
if (snapshot.State == AudioRecorderRuntimeState.Recording)
{
hintText = L("recording.widget.hint.recording", "Recording");
HintTextBlock.Text = L("recording.widget.hint.recording", "Recording");
return;
}
else if (snapshot.State == AudioRecorderRuntimeState.Paused)
if (snapshot.State == AudioRecorderRuntimeState.Paused)
{
hintText = L("recording.widget.hint.paused", "Paused");
HintTextBlock.Text = L("recording.widget.hint.paused", "Paused");
return;
}
else if (snapshot.State == AudioRecorderRuntimeState.Error)
if (snapshot.State == AudioRecorderRuntimeState.Error)
{
hintText = string.IsNullOrWhiteSpace(snapshot.LastError)
HintTextBlock.Text = string.IsNullOrWhiteSpace(snapshot.LastError)
? L("recording.widget.hint.error", "Recording failed")
: snapshot.LastError;
return;
}
else
if (!string.IsNullOrWhiteSpace(snapshot.LastSavedFilePath) &&
!string.Equals(snapshot.LastSavedFilePath, _lastSavedFilePath, StringComparison.OrdinalIgnoreCase))
{
if (!string.IsNullOrWhiteSpace(snapshot.LastSavedFilePath) &&
!string.Equals(snapshot.LastSavedFilePath, _lastSavedFilePath, StringComparison.OrdinalIgnoreCase))
{
_lastSavedFilePath = snapshot.LastSavedFilePath;
}
hintText = !string.IsNullOrWhiteSpace(_lastSavedFilePath)
? string.Format(
CultureInfo.InvariantCulture,
L("recording.widget.hint.saved_format", "Saved {0}"),
Path.GetFileName(_lastSavedFilePath))
: L("recording.widget.hint.ready", "Tap red button to record");
_lastSavedFilePath = snapshot.LastSavedFilePath;
}
HintTextBlock.Text = hintText;
UpdateTypography();
if (!string.IsNullOrWhiteSpace(_lastSavedFilePath))
{
var fileName = Path.GetFileName(_lastSavedFilePath);
HintTextBlock.Text = string.Format(
CultureInfo.InvariantCulture,
L("recording.widget.hint.saved_format", "Saved {0}"),
fileName);
return;
}
HintTextBlock.Text = L("recording.widget.hint.ready", "Tap red button to record");
}
private bool TryStartRecordingWithMonitoringHandoff()
@@ -567,51 +574,6 @@ public partial class RecordingWidget : UserControl, IDesktopComponentWidget, IDe
return duration.ToString(@"mm\:ss", CultureInfo.InvariantCulture);
}
private void UpdateTypography()
{
var contentWidth = RecorderContentGrid.Bounds.Width > 1 ? RecorderContentGrid.Bounds.Width : 252d;
var contentHeight = RecorderContentGrid.Bounds.Height > 1 ? RecorderContentGrid.Bounds.Height : 240d;
var timerWidth = Math.Max(88, contentWidth * 0.84);
var timerHeight = Math.Max(34, contentHeight * 0.24);
var hintWidth = Math.Max(120, contentWidth * 0.86);
var hintHeight = Math.Max(24, contentHeight * 0.12);
TitleTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
TitleTextBlock.Text,
Math.Max(96, contentWidth * 0.62),
20,
1,
12,
20,
FontWeight.SemiBold,
1.05d);
TimerTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
TimerTextBlock.Text,
timerWidth,
timerHeight,
1,
34,
66,
FontWeight.SemiBold,
1.0d);
var hintLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
HintTextBlock.Text,
hintWidth,
hintHeight,
1,
2,
9,
13,
[FontWeight.Medium, FontWeight.Normal],
1.10d);
HintTextBlock.FontSize = hintLayout.FontSize;
HintTextBlock.FontWeight = hintLayout.Weight;
HintTextBlock.MaxLines = hintLayout.MaxLines;
HintTextBlock.TextWrapping = hintLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
}
private static IBrush CreateBrush(string colorHex)
{
return new SolidColorBrush(Color.Parse(colorHex));

View File

@@ -9,7 +9,6 @@ using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentIcons.Avalonia;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
@@ -365,6 +364,11 @@ public partial class RemovableStorageWidget : UserControl, IDesktopComponentWidg
IconBadge.CornerRadius = new CornerRadius(badgeSize * 0.5);
DriveIcon.FontSize = Math.Clamp(24 * scale, 20, 32);
DriveNameTextBlock.FontSize = Math.Clamp(16 * scale, 13, 24);
DriveDetailTextBlock.FontSize = Math.Clamp(11.5 * scale, 10, 16);
StatusTextBlock.FontSize = Math.Clamp(12 * scale, 10, 17);
StatusTextBlock.MaxWidth = Math.Max(96, width - (RootBorder.Padding.Left + RootBorder.Padding.Right));
var buttonHeight = Math.Clamp(42 * scale, 38, 54);
var buttonPadding = Math.Clamp(14 * scale, 10, 20);
var buttonCornerRadius = Math.Clamp(buttonHeight * 0.5, 18, 999);
@@ -379,14 +383,14 @@ public partial class RemovableStorageWidget : UserControl, IDesktopComponentWidg
OpenButtonIcon.FontSize = Math.Clamp(16 * scale, 14, 20);
EjectButtonIcon.FontSize = Math.Clamp(16 * scale, 14, 20);
OpenButtonTextBlock.FontSize = Math.Clamp(13 * scale, 11.5, 18);
EjectButtonTextBlock.FontSize = Math.Clamp(13 * scale, 11.5, 18);
AccentOrb.Width = Math.Clamp(width * 0.44, 96, 176);
AccentOrb.Height = AccentOrb.Width;
AccentOrb.CornerRadius = new CornerRadius(AccentOrb.Width * 0.5);
AccentGlow.Height = Math.Clamp(76 * scale, 52, 110);
AccentGlow.CornerRadius = new CornerRadius(AccentGlow.Height * 0.5);
UpdateTypography();
}
private RemovableStorageDrive? GetSelectedDrive()
@@ -518,67 +522,6 @@ public partial class RemovableStorageWidget : UserControl, IDesktopComponentWidg
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale)), 0.72, 2.2);
}
private void UpdateTypography()
{
var scale = ResolveScale();
var width = Bounds.Width > 1 ? Bounds.Width : 220d;
var rootPadding = RootBorder.Padding;
var headerWidth = Math.Max(96, width - rootPadding.Left - rootPadding.Right - Math.Max(44, IconBadge.Width) - HeaderGrid.ColumnSpacing);
var statusWidth = Math.Max(120, width - rootPadding.Left - rootPadding.Right);
var buttonWidth = Math.Max(88, width - rootPadding.Left - rootPadding.Right);
DriveNameTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
DriveNameTextBlock.Text,
headerWidth,
22,
1,
13,
24,
FontWeight.SemiBold,
1.05d);
DriveDetailTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
DriveDetailTextBlock.Text,
headerWidth,
18,
1,
10,
16,
FontWeight.Normal,
1.05d);
StatusTextBlock.FontSize = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
StatusTextBlock.Text,
statusWidth,
Math.Max(28, 40 * scale),
1,
3,
10,
17,
[FontWeight.Medium, FontWeight.Normal],
1.10d).FontSize;
StatusTextBlock.MaxWidth = statusWidth;
StatusTextBlock.MaxLines = 3;
StatusTextBlock.TextWrapping = TextWrapping.Wrap;
OpenButtonTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
OpenButtonTextBlock.Text,
buttonWidth - 32,
Math.Max(18, OpenButton.Height - 8),
1,
11,
18,
FontWeight.SemiBold,
1.04d);
EjectButtonTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
EjectButtonTextBlock.Text,
buttonWidth - 32,
Math.Max(18, EjectButton.Height - 8),
1,
11,
18,
FontWeight.SemiBold,
1.04d);
}
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);

View File

@@ -15,7 +15,6 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -676,32 +675,9 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I
visual.AvatarHost.Height = avatarSize;
visual.AvatarHost.CornerRadius = new CornerRadius(avatarSize / 2d);
var avatarGlyphBox = ComponentTypographyLayoutService.ResolveGlyphBox(
avatarSize,
avatarSize,
preferredSizeScale: 0.60d,
minSize: 12,
maxSize: 18);
visual.AvatarFallbackText.Margin = avatarGlyphBox.Margin;
visual.AvatarFallbackText.FontSize = Math.Min(avatarFont, Math.Max(avatarGlyphBox.Width * 0.55d, 10));
visual.AvatarFallbackText.MaxWidth = avatarGlyphBox.Width;
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
visual.TitleTextBlock.Text,
titleMaxWidth,
Math.Max(avatarSize, rowPaddingVertical * 2d + 18),
minLines: 1,
maxLines: ComponentTypographyLayoutService.CountTextDisplayUnits(visual.TitleTextBlock.Text) > 24 ? 2 : 1,
minFontSize: Math.Clamp(titleFont * 0.72, 10, 14),
maxFontSize: titleFont,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Bold },
lineHeightFactor: 1.08d,
fontFamily: MiSansFontFamily);
visual.TitleTextBlock.FontSize = titleLayout.FontSize;
visual.TitleTextBlock.LineHeight = titleLayout.LineHeight;
visual.AvatarFallbackText.FontSize = avatarFont;
visual.TitleTextBlock.FontSize = titleFont;
visual.TitleTextBlock.MaxWidth = titleMaxWidth;
visual.TitleTextBlock.MaxLines = titleLayout.MaxLines;
visual.TitleTextBlock.FontWeight = titleLayout.Weight;
}
StatusTextBlock.FontSize = Math.Clamp(14 * softScale, 10, 18);

View File

@@ -6,7 +6,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -285,88 +284,6 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
ApplyVariableWeights(scale);
ApplyLocalizedLabels();
var contentWidth = Math.Max(120, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 8) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(78, (Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 3) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TitleTextBlock.Text,
Math.Max(120, contentWidth * 0.44),
Math.Max(18, contentHeight * 0.22),
1,
1,
9,
Math.Clamp(20 * scale, 9, 20),
[TitleTextBlock.FontWeight],
1.05);
TitleTextBlock.FontSize = titleLayout.FontSize;
TitleTextBlock.FontWeight = titleLayout.Weight;
TitleTextBlock.MaxLines = 1;
TitleTextBlock.TextWrapping = TextWrapping.NoWrap;
TitleTextBlock.LineHeight = titleLayout.LineHeight;
var modeBadgeBox = ComponentTypographyLayoutService.ResolveBadgeBox(
Math.Max(64, contentWidth * 0.24),
Math.Max(20, contentHeight * 0.14),
preferredSizeScale: 0.48d,
minSize: 18,
maxSize: 42,
insetScale: 0.18d);
ModeBadgeBorder.Padding = modeBadgeBox.Padding;
ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(modeBadgeBox.Size * 0.36, 5, 12));
var modeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ModeTextBlock.Text,
Math.Max(54, modeBadgeBox.Width),
Math.Max(18, modeBadgeBox.Height),
1,
1,
8,
Math.Clamp(16 * scale, 8, 16),
[ModeTextBlock.FontWeight],
1.02);
ModeTextBlock.FontSize = modeLayout.FontSize;
ModeTextBlock.FontWeight = modeLayout.Weight;
ModeTextBlock.MaxLines = 1;
ModeTextBlock.TextWrapping = TextWrapping.NoWrap;
ModeTextBlock.LineHeight = modeLayout.LineHeight;
foreach (var block in new[] { SustainedReasonTextBlock, TimeReasonTextBlock, SegmentReasonTextBlock })
{
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
Math.Max(84, contentWidth * 0.34),
Math.Max(18, contentHeight * 0.14),
1,
_isUltraCompactMode ? 1 : 2,
9,
Math.Clamp(18 * scale, 9, 18),
[block.FontWeight],
1.05);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = layout.MaxLines;
block.TextWrapping = layout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
foreach (var block in new[] { SustainedMetricTextBlock, TimeMetricTextBlock, SegmentMetricTextBlock, TotalLossTextBlock, ScoreTextBlock })
{
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
Math.Max(72, contentWidth * 0.22),
Math.Max(16, contentHeight * 0.10),
1,
1,
8,
Math.Clamp(16 * scale, 8, 16),
[block.FontWeight],
1.02);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = 1;
block.TextWrapping = TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
}
private void ApplyTypographyByBackground(Color panelColor)

View File

@@ -5,7 +5,6 @@ using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
@@ -252,77 +251,6 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
LayoutGrid.ColumnSpacing = hideStatusLabel
? Math.Clamp(6 * scale, 4, 10)
: Math.Clamp(10 * scale, 7, 14);
var availableWidth = Math.Max(72, (width > 0 ? width : _currentCellSize * 4) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var availableHeight = Math.Max(34, (height > 0 ? height : _currentCellSize * 2) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var statusTitleWidth = Math.Max(50, availableWidth * 0.32);
var statusValueWidth = Math.Max(78, availableWidth - statusTitleWidth - Math.Clamp(6 * scale, 3, 8));
var valueHeight = Math.Max(18, availableHeight * 0.44);
var statusTitleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
StatusTitleTextBlock.Text,
statusTitleWidth,
valueHeight,
1,
1,
9,
Math.Clamp(18 * scale, 9, 18),
[StatusTitleTextBlock.FontWeight],
1.04);
StatusTitleTextBlock.FontSize = statusTitleLayout.FontSize;
StatusTitleTextBlock.FontWeight = statusTitleLayout.Weight;
StatusTitleTextBlock.MaxLines = 1;
StatusTitleTextBlock.TextWrapping = TextWrapping.NoWrap;
StatusTitleTextBlock.LineHeight = statusTitleLayout.LineHeight;
var statusValueLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
StatusValueTextBlock.Text,
statusValueWidth,
valueHeight,
1,
1,
12,
Math.Clamp(34 * scale, 12, 34),
[StatusValueTextBlock.FontWeight],
1.04);
StatusValueTextBlock.FontSize = statusValueLayout.FontSize;
StatusValueTextBlock.FontWeight = statusValueLayout.Weight;
StatusValueTextBlock.MaxLines = 1;
StatusValueTextBlock.TextWrapping = TextWrapping.NoWrap;
StatusValueTextBlock.LineHeight = statusValueLayout.LineHeight;
var noiseValueLines = _showDisplayDb && _showDbfs ? 2 : 1;
var noiseValueLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
NoiseValueTextBlock.Text,
Math.Max(92, availableWidth * 0.48),
Math.Max(22, availableHeight * 0.56),
1,
noiseValueLines,
12,
Math.Clamp(38 * scale, 12, 38),
[NoiseValueTextBlock.FontWeight],
1.06);
NoiseValueTextBlock.FontSize = noiseValueLayout.FontSize;
NoiseValueTextBlock.FontWeight = noiseValueLayout.Weight;
NoiseValueTextBlock.MaxLines = noiseValueLayout.MaxLines;
NoiseValueTextBlock.TextWrapping = noiseValueLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
NoiseValueTextBlock.LineHeight = noiseValueLayout.LineHeight;
var noiseSubValueLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
NoiseSubValueTextBlock.Text,
Math.Max(72, availableWidth * 0.34),
Math.Max(18, availableHeight * 0.24),
1,
1,
9,
Math.Clamp(18 * scale, 9, 18),
[NoiseSubValueTextBlock.FontWeight],
1.04);
NoiseSubValueTextBlock.FontSize = noiseSubValueLayout.FontSize;
NoiseSubValueTextBlock.FontWeight = noiseSubValueLayout.Weight;
NoiseSubValueTextBlock.MaxLines = 1;
NoiseSubValueTextBlock.TextWrapping = TextWrapping.NoWrap;
NoiseSubValueTextBlock.LineHeight = noiseSubValueLayout.LineHeight;
}
private string ResolveStatusText(StudyAnalyticsSnapshot snapshot)

View File

@@ -5,7 +5,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -311,92 +310,6 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
ApplyVariableWeights(scale);
ApplyLocalizedLabels();
var contentWidth = Math.Max(120, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 8) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(78, (Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 3) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TitleTextBlock.Text,
Math.Max(120, contentWidth * 0.38),
Math.Max(18, contentHeight * 0.18),
1,
1,
9,
Math.Clamp(20 * scale, 9, 20),
[TitleTextBlock.FontWeight],
1.05);
TitleTextBlock.FontSize = titleLayout.FontSize;
TitleTextBlock.FontWeight = titleLayout.Weight;
TitleTextBlock.MaxLines = 1;
TitleTextBlock.TextWrapping = TextWrapping.NoWrap;
TitleTextBlock.LineHeight = titleLayout.LineHeight;
var modeBadgeBox = ComponentTypographyLayoutService.ResolveBadgeBox(
Math.Max(64, contentWidth * 0.22),
Math.Max(20, contentHeight * 0.14),
preferredSizeScale: 0.46d,
minSize: 18,
maxSize: 42,
insetScale: 0.18d);
ModeBadgeBorder.Padding = modeBadgeBox.Padding;
ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(modeBadgeBox.Size * 0.36, 4, 12));
var modeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ModeTextBlock.Text,
Math.Max(52, modeBadgeBox.Width),
Math.Max(18, modeBadgeBox.Height),
1,
1,
8,
Math.Clamp(16 * scale, 8, 16),
[ModeTextBlock.FontWeight],
1.02);
ModeTextBlock.FontSize = modeLayout.FontSize;
ModeTextBlock.FontWeight = modeLayout.Weight;
ModeTextBlock.MaxLines = 1;
ModeTextBlock.TextWrapping = TextWrapping.NoWrap;
ModeTextBlock.LineHeight = modeLayout.LineHeight;
foreach (var block in new[] { DensityValueTextBlock, CountValueTextBlock, DurationValueTextBlock })
{
var minFont = block == DensityValueTextBlock ? 18 : 10;
var maxFont = block == DensityValueTextBlock ? Math.Clamp(94 * scale, 18, 94) : Math.Clamp(36 * scale, 10, 36);
var maxWidth = block == DensityValueTextBlock ? Math.Max(86, contentWidth * 0.24) : Math.Max(64, contentWidth * 0.18);
var maxHeight = block == DensityValueTextBlock ? Math.Max(24, contentHeight * 0.26) : Math.Max(18, contentHeight * 0.18);
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
maxWidth,
maxHeight,
1,
1,
minFont,
maxFont,
[block.FontWeight],
1.02);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = 1;
block.TextWrapping = TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
foreach (var block in new[] { DensityUnitTextBlock, DensityLevelTextBlock, CountLabelTextBlock, DurationLabelTextBlock, ThresholdTextBlock })
{
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
Math.Max(64, contentWidth * 0.18),
Math.Max(16, contentHeight * 0.14),
1,
1,
8,
Math.Clamp(18 * scale, 8, 18),
[block.FontWeight],
1.02);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = 1;
block.TextWrapping = TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
}
private void ApplyTypographyByBackground(Color panelColor)

View File

@@ -5,7 +5,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -129,50 +128,6 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
XLeftTextBlock.FontSize = axisFontSize;
XCenterTextBlock.FontSize = axisFontSize;
XRightTextBlock.FontSize = axisFontSize;
var contentWidth = Math.Max(110, (_currentCellSize * 4) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(72, (_currentCellSize * 2) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var statusLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
StatusTextBlock.Text,
Math.Max(72, contentWidth * 0.34),
Math.Max(18, contentHeight * 0.18),
1,
1,
12,
Math.Clamp(30 * scale, 12, 30),
[StatusTextBlock.FontWeight],
1.03);
StatusTextBlock.FontSize = statusLayout.FontSize;
StatusTextBlock.FontWeight = statusLayout.Weight;
StatusTextBlock.MaxLines = 1;
StatusTextBlock.TextWrapping = TextWrapping.NoWrap;
StatusTextBlock.LineHeight = statusLayout.LineHeight;
var realtimeValueLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
RealtimeValueTextBlock.Text,
Math.Max(82, contentWidth * 0.40),
Math.Max(20, contentHeight * 0.22),
1,
1,
12,
Math.Clamp(34 * scale, 12, 34),
[RealtimeValueTextBlock.FontWeight],
1.03);
RealtimeValueTextBlock.FontSize = realtimeValueLayout.FontSize;
RealtimeValueTextBlock.FontWeight = realtimeValueLayout.Weight;
RealtimeValueTextBlock.MaxLines = 1;
RealtimeValueTextBlock.TextWrapping = TextWrapping.NoWrap;
RealtimeValueTextBlock.LineHeight = realtimeValueLayout.LineHeight;
var badgeBox = ComponentTypographyLayoutService.ResolveBadgeBox(
Math.Max(64, contentWidth * 0.20),
Math.Max(20, contentHeight * 0.12),
preferredSizeScale: 0.46d,
minSize: 18,
maxSize: 40,
insetScale: 0.18d);
StatusBadgeBorder.Padding = badgeBox.Padding;
StatusBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(badgeBox.Size * 0.36, 5, 12));
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)

View File

@@ -6,7 +6,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -359,85 +358,6 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
SummaryTextBlock.IsVisible = true;
ApplyVariableWeights(scale);
var contentWidth = Math.Max(120, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 8) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(78, (Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 3) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TitleTextBlock.Text,
Math.Max(120, contentWidth * 0.38),
Math.Max(18, contentHeight * 0.18),
1,
1,
9,
Math.Clamp(22 * scale, 9, 22),
[TitleTextBlock.FontWeight],
1.05);
TitleTextBlock.FontSize = titleLayout.FontSize;
TitleTextBlock.FontWeight = titleLayout.Weight;
TitleTextBlock.MaxLines = 1;
TitleTextBlock.TextWrapping = TextWrapping.NoWrap;
TitleTextBlock.LineHeight = titleLayout.LineHeight;
var summaryLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
SummaryTextBlock.Text,
Math.Max(104, contentWidth * 0.44),
Math.Max(18, contentHeight * 0.16),
1,
_isUltraCompactMode ? 1 : 2,
8,
Math.Clamp(20 * scale, 8, 20),
[SummaryTextBlock.FontWeight],
1.06);
SummaryTextBlock.FontSize = summaryLayout.FontSize;
SummaryTextBlock.FontWeight = summaryLayout.Weight;
SummaryTextBlock.MaxLines = summaryLayout.MaxLines;
SummaryTextBlock.TextWrapping = summaryLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
SummaryTextBlock.LineHeight = summaryLayout.LineHeight;
var modeBadgeBox = ComponentTypographyLayoutService.ResolveBadgeBox(
Math.Max(64, contentWidth * 0.22),
Math.Max(20, contentHeight * 0.14),
preferredSizeScale: 0.46d,
minSize: 18,
maxSize: 42,
insetScale: 0.18d);
ModeBadgeBorder.Padding = modeBadgeBox.Padding;
ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(modeBadgeBox.Size * 0.36, 4, 12));
var modeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ModeTextBlock.Text,
Math.Max(52, modeBadgeBox.Width),
Math.Max(18, modeBadgeBox.Height),
1,
1,
8,
Math.Clamp(18 * scale, 8, 18),
[ModeTextBlock.FontWeight],
1.02);
ModeTextBlock.FontSize = modeLayout.FontSize;
ModeTextBlock.FontWeight = modeLayout.Weight;
ModeTextBlock.MaxLines = 1;
ModeTextBlock.TextWrapping = TextWrapping.NoWrap;
ModeTextBlock.LineHeight = modeLayout.LineHeight;
foreach (var block in new[] { YExtremeTextBlock, YNoisyTextBlock, YNormalTextBlock, YQuietTextBlock, XLeftTextBlock, XCenterTextBlock, XRightTextBlock })
{
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
Math.Max(36, contentWidth * 0.12),
Math.Max(14, contentHeight * 0.08),
1,
1,
8,
Math.Clamp(16 * scale, 8, 16),
[block.FontWeight],
1.02);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = 1;
block.TextWrapping = TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
}
private void ApplyTypographyByBackground(Color panelColor)

View File

@@ -6,7 +6,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -337,92 +336,6 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
ApplyVariableWeights(scale);
ApplyLocalizedLabels();
var contentWidth = Math.Max(140, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 8) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(96, (Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 4) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TitleTextBlock.Text,
Math.Max(120, contentWidth * 0.42),
Math.Max(18, contentHeight * 0.14),
1,
1,
9,
Math.Clamp(30 * scale * labelFactor, 9, 30),
[TitleTextBlock.FontWeight],
1.05);
TitleTextBlock.FontSize = titleLayout.FontSize;
TitleTextBlock.FontWeight = titleLayout.Weight;
TitleTextBlock.MaxLines = 1;
TitleTextBlock.TextWrapping = TextWrapping.NoWrap;
TitleTextBlock.LineHeight = titleLayout.LineHeight;
var modeBadgeBox = ComponentTypographyLayoutService.ResolveBadgeBox(
Math.Max(64, contentWidth * 0.22),
Math.Max(20, contentHeight * 0.12),
preferredSizeScale: 0.46d,
minSize: 18,
maxSize: 42,
insetScale: 0.18d);
ModeBadgeBorder.Padding = modeBadgeBox.Padding;
ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(modeBadgeBox.Size * 0.36, 5, 14));
var modeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ModeTextBlock.Text,
Math.Max(52, modeBadgeBox.Width),
Math.Max(18, modeBadgeBox.Height),
1,
1,
8,
Math.Clamp(22 * scale * labelFactor, 8, 22),
[ModeTextBlock.FontWeight],
1.02);
ModeTextBlock.FontSize = modeLayout.FontSize;
ModeTextBlock.FontWeight = modeLayout.Weight;
ModeTextBlock.MaxLines = 1;
ModeTextBlock.TextWrapping = TextWrapping.NoWrap;
ModeTextBlock.LineHeight = modeLayout.LineHeight;
foreach (var block in new[] { CurrentLabelTextBlock, AverageLabelTextBlock, MinimumLabelTextBlock, MaximumLabelTextBlock })
{
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
Math.Max(64, contentWidth * 0.16),
Math.Max(14, contentHeight * 0.10),
1,
1,
8,
Math.Clamp(22 * scale * labelFactor, 8, 22),
[block.FontWeight],
1.02);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = 1;
block.TextWrapping = TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
foreach (var block in new[] { CurrentScoreTextBlock, AverageValueTextBlock, MinimumValueTextBlock, MaximumValueTextBlock })
{
var minFont = block == CurrentScoreTextBlock ? 22 : 11;
var maxFont = block == CurrentScoreTextBlock ? Math.Clamp(190 * scale * headlineFactor, 22, 190) : Math.Clamp(64 * scale * statFactor, 11, 64);
var maxWidth = block == CurrentScoreTextBlock ? Math.Max(96, contentWidth * 0.30) : Math.Max(72, contentWidth * 0.18);
var maxHeight = block == CurrentScoreTextBlock ? Math.Max(30, contentHeight * 0.24) : Math.Max(20, contentHeight * 0.14);
var layout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
block.Text,
maxWidth,
maxHeight,
1,
1,
minFont,
maxFont,
[block.FontWeight],
1.02);
block.FontSize = layout.FontSize;
block.FontWeight = layout.Weight;
block.MaxLines = 1;
block.TextWrapping = TextWrapping.NoWrap;
block.LineHeight = layout.LineHeight;
}
}
private void PushRealtimeScore(double score, DateTimeOffset now)

View File

@@ -4,7 +4,6 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -294,40 +293,6 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
ActionIcon.Height = Math.Clamp(buttonSize * 0.44, 14, 30);
SecondaryTextBlock.IsVisible = !_isUltraCompactMode;
var contentWidth = Math.Max(96, (Bounds.Width > 1 ? Bounds.Width : _currentCellSize * 4) - RootBorder.Padding.Left - RootBorder.Padding.Right);
var contentHeight = Math.Max(44, (Bounds.Height > 1 ? Bounds.Height : _currentCellSize * 2) - RootBorder.Padding.Top - RootBorder.Padding.Bottom);
var primaryLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
PrimaryTextBlock.Text,
Math.Max(72, contentWidth * 0.58),
Math.Max(18, contentHeight * 0.28),
1,
1,
10,
Math.Clamp(30 * scale, 10, 30),
[PrimaryTextBlock.FontWeight],
1.04);
PrimaryTextBlock.FontSize = primaryLayout.FontSize;
PrimaryTextBlock.FontWeight = primaryLayout.Weight;
PrimaryTextBlock.MaxLines = 1;
PrimaryTextBlock.TextWrapping = TextWrapping.NoWrap;
PrimaryTextBlock.LineHeight = primaryLayout.LineHeight;
var secondaryLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
SecondaryTextBlock.Text,
Math.Max(64, contentWidth * 0.58),
Math.Max(16, contentHeight * 0.22),
1,
_isUltraCompactMode ? 1 : 2,
8,
Math.Clamp(18 * scale, 8, 18),
[SecondaryTextBlock.FontWeight],
1.04);
SecondaryTextBlock.FontSize = secondaryLayout.FontSize;
SecondaryTextBlock.FontWeight = secondaryLayout.Weight;
SecondaryTextBlock.MaxLines = secondaryLayout.MaxLines;
SecondaryTextBlock.TextWrapping = secondaryLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
SecondaryTextBlock.LineHeight = secondaryLayout.LineHeight;
}
private void ApplyTypographyByBackground(Color panelColor)

View File

@@ -9,7 +9,6 @@ using Avalonia.Media;
using Avalonia.Threading;
using FluentIcons.Avalonia;
using FluentIcons.Common;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -196,7 +195,6 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
StatusTextBlock.Text = _transientStatus ?? L("study.session_history.empty", "No session history");
StatusTextBlock.Foreground = CreateAdaptiveBrush(panelSamples, SecondaryColorCandidates, MinTextContrast);
ApplyHistoryTypographyLayout();
UpdateDialogVisual(snapshot, panelColor);
return;
}
@@ -221,7 +219,6 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
StatusTextBlock.Text = _transientStatus ?? string.Empty;
StatusTextBlock.Foreground = CreateAdaptiveBrush(panelSamples, SecondaryColorCandidates, MinTextContrast);
ApplyHistoryTypographyLayout();
UpdateDialogVisual(snapshot, panelColor);
}
@@ -264,25 +261,15 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
{
Spacing = _isUltraCompactMode ? 0 : 2
};
var rowTitleTextBlock = new TextBlock
textStack.Children.Add(new TextBlock
{
Text = entry.Label,
FontSize = Math.Clamp(12 * (_isCompactMode ? 0.92 : 1.0), 10, 17),
FontWeight = FontWeight.SemiBold,
MaxLines = 1,
TextTrimming = TextTrimming.CharacterEllipsis,
Foreground = rowPrimaryBrush
};
ApplyTextLayout(
rowTitleTextBlock,
ComponentTypographyLayoutService.FitAdaptiveTextLayout(
entry.Label,
ResolveHistoryContentWidth(),
_isUltraCompactMode ? 18 : 22,
1,
1,
10,
17,
new[] { FontWeight.SemiBold, FontWeight.Medium },
1.08d),
TextWrapping.NoWrap);
textStack.Children.Add(rowTitleTextBlock);
});
if (!_isUltraCompactMode)
{
@@ -294,25 +281,14 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
FormatDuration(entry.Duration),
entry.AverageScore);
var metaTextBlock = new TextBlock
textStack.Children.Add(new TextBlock
{
Text = metaText,
FontSize = Math.Clamp(10.5 * (_isCompactMode ? 0.94 : 1.0), 9, 14),
MaxLines = 1,
TextTrimming = TextTrimming.CharacterEllipsis,
Foreground = rowSecondaryBrush
};
ApplyTextLayout(
metaTextBlock,
ComponentTypographyLayoutService.FitAdaptiveTextLayout(
metaText,
ResolveHistoryContentWidth(),
_isUltraCompactMode ? 16 : 20,
1,
1,
9,
14,
new[] { FontWeight.Normal, FontWeight.Medium },
1.08d),
TextWrapping.NoWrap);
textStack.Children.Add(metaTextBlock);
});
}
rowGrid.Children.Add(textStack);
@@ -590,8 +566,6 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
DialogConfirmButton.Content = L("study.session_history.dialog.delete_confirm", "Delete");
DialogCancelButton.Content = L("study.session_history.rename_cancel", "Cancel rename");
}
ApplyHistoryTypographyLayout();
}
private void SetTransientStatus(string status, double seconds = 2.2)
@@ -625,6 +599,8 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
? Math.Clamp(4 * scale, 2, 6)
: Math.Clamp(7 * scale, 4, 10);
TitleTextBlock.FontSize = Math.Clamp(13 * scale, 10, 22);
StatusTextBlock.FontSize = Math.Clamp(11 * scale, 9, 18);
SessionListPanel.Spacing = _isUltraCompactMode
? Math.Clamp(4 * scale, 2, 5)
: Math.Clamp(6 * scale, 3, 8);
@@ -636,108 +612,13 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
DialogCardBorder.Padding = new Thickness(
ComponentChromeCornerRadiusHelper.SafeValue(12 * scale, 9, 20),
ComponentChromeCornerRadiusHelper.SafeValue(11 * scale, 8, 18));
DialogTitleTextBlock.FontSize = Math.Clamp(14 * scale, 11, 20);
DialogMessageTextBlock.FontSize = Math.Clamp(12 * scale, 10, 17);
DialogRenameTextBox.FontSize = Math.Clamp(11.5 * scale, 10, 16);
DialogCancelButton.FontSize = Math.Clamp(11 * scale, 10, 16);
DialogConfirmButton.FontSize = Math.Clamp(11 * scale, 10, 16);
DialogCancelButton.Height = Math.Clamp(30 * scale, 26, 38);
DialogConfirmButton.Height = Math.Clamp(30 * scale, 26, 38);
ApplyHistoryTypographyLayout();
}
private void ApplyHistoryTypographyLayout()
{
var contentWidth = ResolveHistoryContentWidth();
var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TitleTextBlock.Text,
contentWidth,
_isUltraCompactMode ? 18 : 24,
1,
1,
10,
22,
new[] { FontWeight.SemiBold, FontWeight.Medium },
1.08d);
ApplyTextLayout(TitleTextBlock, titleLayout, TextWrapping.NoWrap);
var statusLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
StatusTextBlock.Text,
contentWidth,
_isUltraCompactMode ? 18 : 28,
1,
_isUltraCompactMode ? 1 : 2,
9,
18,
new[] { FontWeight.Normal, FontWeight.Medium },
1.10d);
ApplyTextLayout(StatusTextBlock, statusLayout, statusLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap);
if (!DialogOverlayBorder.IsVisible)
{
return;
}
var dialogWidth = DialogCardBorder.Bounds.Width > 1
? Math.Max(1, DialogCardBorder.Bounds.Width - DialogCardBorder.Padding.Left - DialogCardBorder.Padding.Right)
: Math.Max(180, contentWidth);
var dialogTitleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
DialogTitleTextBlock.Text,
dialogWidth,
_isUltraCompactMode ? 18 : 24,
1,
1,
11,
20,
new[] { FontWeight.SemiBold, FontWeight.Medium },
1.08d);
ApplyTextLayout(DialogTitleTextBlock, dialogTitleLayout, TextWrapping.NoWrap);
var dialogMessageLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
DialogMessageTextBlock.Text,
dialogWidth,
_isUltraCompactMode ? 42 : 56,
1,
3,
10,
17,
new[] { FontWeight.Normal, FontWeight.Medium },
1.12d);
ApplyTextLayout(DialogMessageTextBlock, dialogMessageLayout, dialogMessageLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap);
var renameLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
DialogRenameTextBox.Text ?? DialogRenameTextBox.Watermark,
dialogWidth,
_isUltraCompactMode ? 30 : 36,
1,
1,
10,
16,
new[] { FontWeight.Normal },
1.08d);
DialogRenameTextBox.FontSize = renameLayout.FontSize;
DialogRenameTextBox.FontWeight = renameLayout.Weight;
}
private double ResolveHistoryContentWidth()
{
if (Bounds.Width <= 1)
{
return Math.Max(90, _currentCellSize * 4.5);
}
var reservedWidth = _isUltraCompactMode ? 126 : 150;
return Math.Max(90, Bounds.Width - reservedWidth);
}
private static void ApplyTextLayout(TextBlock textBlock, ComponentAdaptiveTextLayout layout, TextWrapping wrapping)
{
textBlock.FontSize = layout.FontSize;
textBlock.FontWeight = layout.Weight;
textBlock.LineHeight = layout.LineHeight;
textBlock.MaxLines = layout.MaxLines;
textBlock.TextWrapping = wrapping;
textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
}
private static StudySessionHistoryEntry? FindHistoryEntry(IReadOnlyList<StudySessionHistoryEntry> history, string? sessionId)

View File

@@ -7,7 +7,6 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
namespace LanMountainDesktop.Views.Components;
@@ -123,7 +122,6 @@ public partial class TimerWidget : UserControl, IDesktopComponentWidget
MainNumberTextBlock.Text = current.ToString(CultureInfo.InvariantCulture);
NextNumberTextBlock.Text = next.ToString(CultureInfo.InvariantCulture);
NextNextNumberTextBlock.Text = nextNext.ToString(CultureInfo.InvariantCulture);
UpdateTypography();
}
private void UpdateHandGeometry()
@@ -208,7 +206,6 @@ public partial class TimerWidget : UserControl, IDesktopComponentWidget
PlayButtonBorder.Height = Math.Clamp(42 * scale, 28, 58);
PlayButtonBorder.CornerRadius = new CornerRadius(PlayButtonBorder.Width / 2d);
UpdateTypography();
ApplyModeVisualIfNeeded();
}
@@ -220,51 +217,6 @@ public partial class TimerWidget : UserControl, IDesktopComponentWidget
return Math.Clamp(Math.Min(cellScale, Math.Min(heightScale, widthScale) * 1.05), 0.58, 1.95);
}
private void UpdateTypography()
{
var panelWidth = TimerPanelBorder.Bounds.Width > 1 ? TimerPanelBorder.Bounds.Width : 224d;
var panelHeight = TimerPanelBorder.Bounds.Height > 1 ? TimerPanelBorder.Bounds.Height : 224d;
var leftColumnWidth = Math.Max(72, panelWidth * 0.38);
var leftColumnHeight = Math.Max(120, panelHeight - 36);
TopNumberTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
TopNumberTextBlock.Text,
leftColumnWidth,
Math.Max(20, leftColumnHeight * 0.20),
1,
18,
38,
FontWeight.SemiBold,
1.02d);
MainNumberTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
MainNumberTextBlock.Text,
leftColumnWidth,
Math.Max(28, leftColumnHeight * 0.30),
1,
28,
64,
FontWeight.Bold,
1.00d);
NextNumberTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
NextNumberTextBlock.Text,
leftColumnWidth,
Math.Max(18, leftColumnHeight * 0.16),
1,
14,
34,
FontWeight.Medium,
1.02d);
NextNextNumberTextBlock.FontSize = ComponentTypographyLayoutService.FitFontSize(
NextNextNumberTextBlock.Text,
leftColumnWidth,
Math.Max(16, leftColumnHeight * 0.12),
1,
12,
26,
FontWeight.Medium,
1.02d);
}
private bool ResolveIsNightMode()
{
if (ActualThemeVariant == ThemeVariant.Dark)

View File

@@ -10,7 +10,6 @@ using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
@@ -219,36 +218,14 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
}
var leftWidthFactor = Math.Clamp(leftContentWidth / 122d, 0.48, 1.35);
var timeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TimeTextBlock.Text,
leftContentWidth,
Math.Max(12, contentHeight * 0.42),
1,
1,
10,
Math.Clamp((metrics.PrimaryTemperatureFont * 0.74) * scale * compactFactor * leftWidthFactor, 10, 62),
[ToVariableWeight(Lerp(620, 760, Math.Clamp((scale - 0.68) / 1.35, 0, 1)))],
1.04);
var dateLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
DateTextBlock.Text,
Math.Max(12, leftContentWidth),
Math.Max(10, contentHeight * 0.24),
1,
1,
8,
Math.Clamp(metrics.SecondaryTextFont * scale * compactFactor * leftWidthFactor, 8, 30),
[ToVariableWeight(Lerp(540, 680, Math.Clamp((scale - 0.68) / 1.35, 0, 1)))],
1.04);
TimeTextBlock.FontSize = timeLayout.FontSize;
DateTextBlock.FontSize = dateLayout.FontSize;
TimeTextBlock.FontSize = Math.Clamp((metrics.PrimaryTemperatureFont * 0.74) * scale * compactFactor * leftWidthFactor, 10, 62);
DateTextBlock.FontSize = Math.Clamp(metrics.SecondaryTextFont * scale * compactFactor * leftWidthFactor, 8, 30);
var weatherIconSize = Math.Clamp(metrics.IconFont * scale * compactFactor * leftWidthFactor, 9, 32);
WeatherIconImage.Width = weatherIconSize;
WeatherIconImage.Height = weatherIconSize;
TimeTextBlock.FontWeight = timeLayout.Weight;
DateTextBlock.FontWeight = dateLayout.Weight;
TimeTextBlock.LineHeight = timeLayout.LineHeight;
DateTextBlock.LineHeight = dateLayout.LineHeight;
TimeTextBlock.FontWeight = ToVariableWeight(Lerp(620, 760, Math.Clamp((scale - 0.68) / 1.35, 0, 1)));
DateTextBlock.FontWeight = ToVariableWeight(Lerp(540, 680, Math.Clamp((scale - 0.68) / 1.35, 0, 1)));
LeftStack.Width = leftContentWidth;
LeftStack.MaxWidth = leftContentWidth;

View File

@@ -11,7 +11,6 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Host.Abstractions;
using LanMountainDesktop.Models;
@@ -926,21 +925,12 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
var temperatureSample = string.IsNullOrWhiteSpace(TemperatureTextBlock.Text)
? "00°"
: TemperatureTextBlock.Text.Trim();
var temperatureGlyphCount = Math.Clamp(ComponentTypographyLayoutService.CountTextDisplayUnits(temperatureSample), 3, 6);
var temperatureGlyphCount = Math.Clamp(temperatureSample.Length, 3, 6);
var temperatureMaxWidth = Math.Max(34, innerWidth - iconSize - TopRowGrid.ColumnSpacing - 2);
var rawTemperatureSize = Math.Clamp(Lerp(94, 118, iconGrowth) * topScale, 22, 340);
var temperatureLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
TemperatureTextBlock.Text,
temperatureMaxWidth,
Math.Max(18, topZoneHeight * 0.84),
1,
1,
Math.Max(10, rawTemperatureSize * 0.42),
rawTemperatureSize,
[ToVariableWeight(Lerp(300, 360, emphasis))],
1.02);
TemperatureTextBlock.FontSize = temperatureLayout.FontSize;
TemperatureTextBlock.FontWeight = temperatureLayout.Weight;
var fitTemperatureSize = temperatureMaxWidth / (temperatureGlyphCount * 0.62);
TemperatureTextBlock.FontSize = Math.Clamp(Math.Min(rawTemperatureSize, fitTemperatureSize), 10, 340);
TemperatureTextBlock.FontWeight = ToVariableWeight(Lerp(300, 360, emphasis));
TemperatureTextBlock.Margin = new Thickness(Math.Clamp(-1.4 * topScale, -6, 0), Math.Clamp(-7.6 * topScale, -16, -1), 0, 0);
TemperatureTextBlock.MaxWidth = Math.Clamp(temperatureMaxWidth, 34, Math.Max(34, innerWidth * 0.76));
@@ -971,76 +961,26 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
}
var infoFontWeight = ToVariableWeight(Lerp(580, 690, emphasis));
var conditionBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
bottomTextMaxWidth,
Math.Max(16, bottomZoneHeight * 0.34),
preferredSizeScale: 0.26d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
ConditionStack.Margin = new Thickness(
conditionBadge.Padding.Left,
conditionBadge.Padding.Top,
conditionBadge.Padding.Right,
conditionBadge.Padding.Bottom);
var conditionContentMaxWidth = Math.Max(24, bottomTextMaxWidth - conditionBadge.Padding.Left - conditionBadge.Padding.Right);
var conditionLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
ConditionTextBlock.Text,
conditionContentMaxWidth,
Math.Max(12, bottomZoneHeight * 0.30),
1,
1,
7,
Math.Max(6, infoFontSize * 0.96),
[infoFontWeight],
infoLineHeightFactor);
var rangeLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
RangeTextBlock.Text,
conditionContentMaxWidth,
Math.Max(12, bottomZoneHeight * 0.30),
1,
1,
7,
Math.Max(6, infoFontSize * 1.03),
[infoFontWeight],
infoLineHeightFactor);
ConditionTextBlock.FontSize = conditionLayout.FontSize;
ConditionTextBlock.FontWeight = conditionLayout.Weight;
ConditionTextBlock.LineHeight = conditionLayout.LineHeight;
ConditionTextBlock.FontSize = Math.Max(6, infoFontSize * 0.96);
ConditionTextBlock.FontWeight = infoFontWeight;
ConditionTextBlock.LineHeight = ConditionTextBlock.FontSize * infoLineHeightFactor;
ConditionTextBlock.MaxWidth = bottomTextMaxWidth;
RangeTextBlock.FontSize = rangeLayout.FontSize;
RangeTextBlock.FontWeight = rangeLayout.Weight;
RangeTextBlock.LineHeight = rangeLayout.LineHeight;
RangeTextBlock.FontSize = Math.Max(6, infoFontSize * 1.03);
RangeTextBlock.FontWeight = infoFontWeight;
RangeTextBlock.LineHeight = RangeTextBlock.FontSize * infoLineHeightFactor;
RangeTextBlock.MaxWidth = bottomTextMaxWidth;
var cityBadge = ComponentTypographyLayoutService.ResolveBadgeBox(
bottomTextMaxWidth,
Math.Max(16, bottomZoneHeight * 0.38),
preferredSizeScale: 0.28d,
minSize: 10,
maxSize: 24,
insetScale: 0.18d);
CityInfoBadge.Padding = cityBadge.Padding;
CityInfoBadge.CornerRadius = new CornerRadius(cityBadge.Size / 2d);
CityInfoBadge.Padding = new Thickness(0);
CityInfoBadge.CornerRadius = new CornerRadius(0);
CityInfoBadge.MaxWidth = bottomTextMaxWidth;
LocationIcon.FontSize = Math.Clamp(
12 * bottomScale,
6,
34);
LocationIcon.FontSize = Math.Min(LocationIcon.FontSize, infoFontSize * 0.72);
var cityLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
CityTextBlock.Text,
Math.Max(24, bottomTextMaxWidth - cityBadge.Padding.Left - cityBadge.Padding.Right),
Math.Max(12, bottomZoneHeight * 0.36),
1,
1,
6,
Math.Max(6, infoFontSize * 0.84),
[ToVariableWeight(Lerp(500, 620, emphasis))],
infoLineHeightFactor);
CityTextBlock.FontSize = cityLayout.FontSize;
CityTextBlock.FontWeight = cityLayout.Weight;
CityTextBlock.LineHeight = cityLayout.LineHeight;
CityTextBlock.FontSize = Math.Max(6, infoFontSize * 0.84);
CityTextBlock.FontWeight = ToVariableWeight(Lerp(500, 620, emphasis));
CityTextBlock.LineHeight = CityTextBlock.FontSize * infoLineHeightFactor;
CityTextBlock.MaxWidth = bottomTextMaxWidth;
}

View File

@@ -8,7 +8,6 @@ using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.DesktopComponents.Runtime;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services;
@@ -102,7 +101,6 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
private TimeZoneService? _timeZoneService;
private string _languageCode = "zh-CN";
private double _currentCellSize = BaseCellSize;
private double _layoutScale = 1d;
private DateTime _nextLanguageProbeUtc = DateTime.MinValue;
private string _secondHandMode = ClockSecondHandMode.Tick;
private bool _isNightVisual = true;
@@ -165,16 +163,14 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
{
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
var chromeScale = ComponentChromeCornerRadiusHelper.ResolveScale();
_layoutScale = Math.Clamp(scale * (0.9d + (chromeScale * 0.1d)), 0.58d, 2.0d);
var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells;
var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells;
var horizontalPadding = Math.Clamp(10 * _layoutScale, 4, 26);
var verticalPadding = Math.Clamp(8 * _layoutScale, 3, 22);
var horizontalPadding = Math.Clamp(10 * scale, 4, 26);
var verticalPadding = Math.Clamp(8 * scale, 3, 22);
RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(horizontalPadding, verticalPadding, null, 0.55d);
RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(24 * _layoutScale, 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);
@@ -183,8 +179,11 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
ClockHostGrid.ColumnSpacing = columnSpacing;
var widthPerClock = Math.Max(18, (usableWidth - columnSpacing * 3) / WorldClockTimeZoneCatalog.ClockCount);
var textSpacing = Math.Clamp(2.8 * _layoutScale, 1, 7);
var estimatedTextHeight = Math.Clamp(78 * _layoutScale, 42, 128);
var secondaryFont = Math.Clamp(10.5 * scale * (widthPerClock / 46d), 7, 18);
var cityFont = Math.Clamp(secondaryFont * 1.42, 9, 24);
var textSpacing = Math.Clamp(2.8 * scale, 1, 7);
var estimatedTextHeight = cityFont * 1.2 + secondaryFont * 2.35 + textSpacing * 3;
var dialSize = Math.Clamp(Math.Min(widthPerClock, usableHeight - estimatedTextHeight), 18, 108);
if (dialSize < 18)
{
@@ -198,13 +197,15 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
entry.DialBorder.Height = dialSize;
entry.DialBorder.CornerRadius = new CornerRadius(dialSize / 2d);
entry.CityTextBlock.FontSize = cityFont;
entry.DayTextBlock.FontSize = secondaryFont;
entry.OffsetTextBlock.FontSize = secondaryFont;
var maxTextWidth = Math.Max(16, widthPerClock + 10);
entry.CityTextBlock.MaxWidth = maxTextWidth;
entry.DayTextBlock.MaxWidth = maxTextWidth;
entry.OffsetTextBlock.MaxWidth = maxTextWidth;
}
RefreshDialArtwork(_isNightVisual);
}
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
@@ -475,7 +476,6 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
private static void BuildDialTicks(ClockEntryVisual entry, bool isNight)
{
entry.TickCanvas.Children.Clear();
var scale = Math.Clamp(entry.Host.Spacing / 3d, 0.78d, 1.22d);
var majorColor = isNight ? "#E3E7F2" : "#2D3341";
var minorColor = isNight ? "#9EA7B8" : "#9AA4B3";
@@ -483,8 +483,8 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
{
var isMajor = i % 5 == 0;
var angle = (i * 6 - 90) * Math.PI / 180d;
var outerRadius = DialCenter - (6.5 * scale);
var innerRadius = outerRadius - (isMajor ? 9 * scale : 4.5 * scale);
var outerRadius = DialCenter - 6.5;
var innerRadius = outerRadius - (isMajor ? 9 : 4.5);
var x1 = DialCenter + Math.Cos(angle) * innerRadius;
var y1 = DialCenter + Math.Sin(angle) * innerRadius;
@@ -496,7 +496,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
StartPoint = new Point(x1, y1),
EndPoint = new Point(x2, y2),
Stroke = CreateBrush(isMajor ? majorColor : minorColor),
StrokeThickness = (isMajor ? 1.9 : 0.8) * scale,
StrokeThickness = isMajor ? 1.9 : 0.8,
StrokeLineCap = PenLineCap.Round
});
}
@@ -505,9 +505,8 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
private static void BuildDialNumbers(ClockEntryVisual entry, bool isNight)
{
entry.NumberCanvas.Children.Clear();
var scale = Math.Clamp(entry.Host.Spacing / 3d, 0.78d, 1.22d);
var numberColor = isNight ? "#F2F5FB" : "#1B202A";
var radius = 36 * scale;
var radius = 36;
for (var number = 1; number <= 12; number++)
{
var angle = (number * 30 - 90) * Math.PI / 180d;
@@ -515,37 +514,22 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
var y = DialCenter + Math.Sin(angle) * radius;
var text = number.ToString(CultureInfo.InvariantCulture);
var isDoubleDigit = number >= 10;
var glyphBox = ComponentTypographyLayoutService.ResolveGlyphBox(
isDoubleDigit ? 16 * scale : 12 * scale,
12 * scale,
preferredSizeScale: 0.98d,
minSize: 8,
maxSize: 16,
insetScale: 0d);
var fontSize = ComponentTypographyLayoutService.FitFontSize(
text,
glyphBox.Width,
glyphBox.Height,
maxLines: 1,
minFontSize: 7 * scale,
maxFontSize: 11 * scale,
weight: FontWeight.SemiBold,
lineHeightFactor: 1d,
fontFamily: MiSansFontFamily);
var width = isDoubleDigit ? 14 : 10;
var height = 12;
var numberText = new TextBlock
{
Text = text,
Width = glyphBox.Width,
Height = glyphBox.Height,
Width = width,
Height = height,
FontFamily = MiSansFontFamily,
FontSize = fontSize,
FontSize = 9,
FontWeight = FontWeight.SemiBold,
Foreground = CreateBrush(numberColor),
TextAlignment = TextAlignment.Center
};
Canvas.SetLeft(numberText, x - glyphBox.Width / 2d);
Canvas.SetTop(numberText, y - glyphBox.Height / 2d);
Canvas.SetLeft(numberText, x - width / 2d);
Canvas.SetTop(numberText, y - height / 2d);
entry.NumberCanvas.Children.Add(numberText);
}
}
@@ -600,69 +584,18 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
var minuteAngle = minuteValue * 6d;
var secondAngle = secondValue * 6d;
SetHandGeometry(entry.HourHand, hourAngle, forwardLength: 24 * _layoutScale, backwardLength: 4.8 * _layoutScale);
SetHandGeometry(entry.MinuteHand, minuteAngle, forwardLength: 33 * _layoutScale, backwardLength: 6 * _layoutScale);
SetHandGeometry(entry.SecondHand, secondAngle, forwardLength: 37 * _layoutScale, backwardLength: 8.5 * _layoutScale);
SetHandGeometry(entry.HourHand, hourAngle, forwardLength: 24, backwardLength: 4.8);
SetHandGeometry(entry.MinuteHand, minuteAngle, forwardLength: 33, backwardLength: 6);
SetHandGeometry(entry.SecondHand, secondAngle, forwardLength: 37, backwardLength: 8.5);
entry.CityTextBlock.Text = ResolveCityName(zone);
entry.DayTextBlock.Text = ResolveRelativeDayLabel((zonedNow.Date - baseNow.Date).Days);
var offsetDelta = zone.GetUtcOffset(utcNow) - baseOffset;
entry.OffsetTextBlock.Text = ResolveOffsetLabel(offsetDelta);
ApplyEntryTypography(entry);
}
}
private void ApplyEntryTypography(ClockEntryVisual entry)
{
var hostWidth = entry.Host.Bounds.Width > 1 ? entry.Host.Bounds.Width : Math.Max(18, _currentCellSize * 0.74);
var hostHeight = entry.Host.Bounds.Height > 1 ? entry.Host.Bounds.Height : Math.Max(18, _currentCellSize * 1.7);
var textWidth = Math.Max(16, hostWidth);
var cityHeight = Math.Clamp(hostHeight * 0.18, 12, 28);
var secondaryHeight = Math.Clamp(hostHeight * 0.14, 10, 22);
var cityLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
entry.CityTextBlock.Text,
textWidth,
cityHeight,
minLines: 1,
maxLines: 1,
minFontSize: 9,
maxFontSize: 24,
weightCandidates: new[] { FontWeight.SemiBold, FontWeight.Medium },
lineHeightFactor: 1d,
fontFamily: MiSansFontFamily);
var dayLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
entry.DayTextBlock.Text,
textWidth,
secondaryHeight,
minLines: 1,
maxLines: 1,
minFontSize: 8,
maxFontSize: 18,
weightCandidates: new[] { FontWeight.Medium, FontWeight.Normal },
lineHeightFactor: 1d,
fontFamily: MiSansFontFamily);
var offsetLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout(
entry.OffsetTextBlock.Text,
textWidth,
secondaryHeight,
minLines: 1,
maxLines: 1,
minFontSize: 8,
maxFontSize: 18,
weightCandidates: new[] { FontWeight.Medium, FontWeight.Normal },
lineHeightFactor: 1d,
fontFamily: MiSansFontFamily);
entry.CityTextBlock.FontSize = cityLayout.FontSize;
entry.CityTextBlock.FontWeight = cityLayout.Weight;
entry.DayTextBlock.FontSize = dayLayout.FontSize;
entry.DayTextBlock.FontWeight = dayLayout.Weight;
entry.OffsetTextBlock.FontSize = offsetLayout.FontSize;
entry.OffsetTextBlock.FontWeight = offsetLayout.Weight;
}
private static void ApplyDialTheme(ClockEntryVisual entry, bool isNight)
{
if (entry.IsNightApplied.HasValue && entry.IsNightApplied.Value == isNight)
@@ -682,21 +615,6 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
BuildDialNumbers(entry, isNight);
}
private void RefreshDialArtwork(bool isNight)
{
for (var index = 0; index < _entryVisuals.Length; index++)
{
var entry = _entryVisuals[index];
if (entry is null)
{
continue;
}
BuildDialTicks(entry, isNight);
BuildDialNumbers(entry, isNight);
}
}
private void ProbeLanguageCodeIfNeeded(DateTime utcNow)
{
if (utcNow < _nextLanguageProbeUtc)