mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user