天气组件、倒计时组件微调。引入浏览器组件。
This commit is contained in:
lincube
2026-03-04 03:41:59 +08:00
parent e8276c4d1e
commit 3d22c04a04
31 changed files with 3258 additions and 576 deletions

View File

@@ -0,0 +1,73 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:webview="clr-namespace:AvaloniaWebView;assembly=Avalonia.WebView"
mc:Ignorable="d"
d:DesignWidth="480"
d:DesignHeight="480"
x:Class="LanMontainDesktop.Views.Components.BrowserWidget">
<Border x:Name="RootBorder"
Background="#F4F7FC"
CornerRadius="24"
ClipToBounds="True"
Padding="10">
<Grid RowDefinitions="*,Auto"
RowSpacing="8">
<Border x:Name="WebViewHostBorder"
Grid.Row="0"
CornerRadius="16"
ClipToBounds="True"
Background="#FFFFFFFF"
BorderBrush="#22000000"
BorderThickness="1">
<webview:WebView x:Name="BrowserWebView" />
</Border>
<Border x:Name="AddressBarBorder"
Grid.Row="1"
CornerRadius="14"
Background="#ECF2FA"
BorderBrush="#22000000"
BorderThickness="1"
Padding="8,6">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="8">
<Button x:Name="RefreshButton"
Grid.Column="0"
Width="34"
Height="34"
Padding="0"
CornerRadius="17"
ToolTip.Tip="Refresh"
Click="OnRefreshButtonClick">
<fi:SymbolIcon Symbol="ArrowClockwise"
FontSize="15" />
</Button>
<TextBox x:Name="AddressTextBox"
Grid.Column="1"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"
Watermark="https://example.com"
Text="https://www.bing.com"
KeyDown="OnAddressTextBoxKeyDown" />
<Button x:Name="GoButton"
Grid.Column="2"
Width="34"
Height="34"
Padding="0"
CornerRadius="17"
ToolTip.Tip="Go"
Click="OnGoButtonClick">
<fi:SymbolIcon Symbol="ArrowRight"
FontSize="15" />
</Button>
</Grid>
</Border>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,291 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using AvaloniaWebView;
using WebViewCore.Events;
namespace LanMontainDesktop.Views.Components;
public partial class BrowserWidget : UserControl, IDesktopComponentWidget
, IDesktopPageVisibilityAwareComponentWidget
{
private static readonly Uri DefaultHomeUri = new("https://www.bing.com");
private double _currentCellSize = 48;
private bool? _isNightModeApplied;
private Uri _lastKnownUri = DefaultHomeUri;
private bool _isOnActiveDesktopPage;
private bool _isEditMode;
private bool _isWebViewActive = true;
public BrowserWidget()
{
InitializeComponent();
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
ApplyCellSize(_currentCellSize);
ApplyTheme(force: true);
BrowserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
UpdateWebViewActiveState();
NavigateTo(DefaultHomeUri);
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 12, 28));
RootBorder.Padding = new Thickness(
Math.Clamp(_currentCellSize * 0.20, 8, 18));
WebViewHostBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.24, 10, 22));
AddressBarBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.22, 10, 20));
AddressBarBorder.Padding = new Thickness(8, 6);
var rowSpacing = 8d;
if (RootBorder.Child is Grid rootGrid)
{
rootGrid.RowSpacing = rowSpacing;
}
var buttonSize = Math.Clamp(_currentCellSize * 0.72, 30, 36);
var buttonCorner = buttonSize * 0.5;
var iconSize = Math.Clamp(buttonSize * 0.44, 14, 16);
foreach (var button in new[] { RefreshButton, GoButton })
{
button.Width = buttonSize;
button.Height = buttonSize;
button.CornerRadius = new CornerRadius(buttonCorner);
}
if (RefreshButton.Content is FluentIcons.Avalonia.SymbolIcon refreshIcon)
{
refreshIcon.FontSize = iconSize;
}
if (GoButton.Content is FluentIcons.Avalonia.SymbolIcon goIcon)
{
goIcon.FontSize = iconSize;
}
AddressTextBox.FontSize = Math.Clamp(_currentCellSize * 0.30, 12, 15);
AddressTextBox.Height = buttonSize;
}
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
ApplyTheme(force: true);
UpdateWebViewActiveState();
}
private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
_isOnActiveDesktopPage = false;
UpdateWebViewActiveState();
}
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
{
ApplyCellSize(_currentCellSize);
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
ApplyTheme(force: false);
}
private void ApplyTheme(bool force)
{
var isNightMode = ResolveIsNightMode();
if (!force && _isNightModeApplied.HasValue && _isNightModeApplied.Value == isNightMode)
{
return;
}
_isNightModeApplied = isNightMode;
RootBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#FF141A24") : Color.Parse("#FFF4F7FC"));
WebViewHostBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#FF0A0E15") : Color.Parse("#FFFFFFFF"));
WebViewHostBorder.BorderBrush = new SolidColorBrush(isNightMode ? Color.Parse("#33FFFFFF") : Color.Parse("#22000000"));
AddressBarBorder.Background = new SolidColorBrush(isNightMode ? Color.Parse("#1BFFFFFF") : Color.Parse("#ECF2FA"));
AddressBarBorder.BorderBrush = new SolidColorBrush(isNightMode ? Color.Parse("#26FFFFFF") : Color.Parse("#22000000"));
var idleBackground = new SolidColorBrush(isNightMode ? Color.Parse("#24FFFFFF") : Color.Parse("#DCE6F5"));
var idleForeground = new SolidColorBrush(isNightMode ? Color.Parse("#FFE5E7EB") : Color.Parse("#FF1E293B"));
foreach (var button in new[] { RefreshButton, GoButton })
{
button.Background = idleBackground;
button.Foreground = idleForeground;
button.BorderThickness = new Thickness(0);
}
AddressTextBox.Background = new SolidColorBrush(isNightMode ? Color.Parse("#1F000000") : Color.Parse("#FFFFFFFF"));
AddressTextBox.BorderBrush = new SolidColorBrush(isNightMode ? Color.Parse("#2FFFFFFF") : Color.Parse("#22000000"));
AddressTextBox.Foreground = idleForeground;
AddressTextBox.CaretBrush = idleForeground;
}
private bool ResolveIsNightMode()
{
if (ActualThemeVariant == ThemeVariant.Dark)
{
return true;
}
if (ActualThemeVariant == ThemeVariant.Light)
{
return false;
}
if (this.TryFindResource("AdaptiveSurfaceBaseBrush", out var value) &&
value is ISolidColorBrush brush)
{
return CalculateRelativeLuminance(brush.Color) < 0.45;
}
return false;
}
private static double CalculateRelativeLuminance(Color color)
{
static double ToLinear(double channel)
{
return channel <= 0.03928
? channel / 12.92
: Math.Pow((channel + 0.055) / 1.055, 2.4);
}
var red = ToLinear(color.R / 255d);
var green = ToLinear(color.G / 255d);
var blue = ToLinear(color.B / 255d);
return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
}
private void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
{
if (!_isWebViewActive)
{
return;
}
if (BrowserWebView.Url is not null)
{
BrowserWebView.Reload();
return;
}
NavigateTo(DefaultHomeUri);
}
private void OnGoButtonClick(object? sender, RoutedEventArgs e)
{
NavigateFromAddressBar();
}
private void OnAddressTextBoxKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key != Key.Enter)
{
return;
}
NavigateFromAddressBar();
e.Handled = true;
}
private void NavigateFromAddressBar()
{
var target = TryNormalizeUri(AddressTextBox.Text);
if (target is null)
{
return;
}
NavigateTo(target);
}
private void NavigateTo(Uri uri)
{
_lastKnownUri = uri;
AddressTextBox.Text = uri.ToString();
if (_isWebViewActive)
{
BrowserWebView.Url = uri;
}
}
private void OnBrowserWebViewNavigationStarting(object? sender, WebViewUrlLoadingEventArg e)
{
if (e.Url is null)
{
return;
}
_lastKnownUri = e.Url;
AddressTextBox.Text = e.Url.ToString();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{
_isOnActiveDesktopPage = isOnActivePage;
_isEditMode = isEditMode;
UpdateWebViewActiveState();
}
private void UpdateWebViewActiveState()
{
var shouldBeActive = _isOnActiveDesktopPage && !_isEditMode && IsVisible;
if (_isWebViewActive == shouldBeActive)
{
return;
}
_isWebViewActive = shouldBeActive;
if (!_isWebViewActive)
{
if (BrowserWebView.Url is Uri currentUri)
{
_lastKnownUri = currentUri;
}
BrowserWebView.IsHitTestVisible = false;
BrowserWebView.IsVisible = false;
BrowserWebView.Url = null;
return;
}
BrowserWebView.IsVisible = true;
BrowserWebView.IsHitTestVisible = true;
BrowserWebView.Url = _lastKnownUri;
}
private static Uri? TryNormalizeUri(string? rawText)
{
if (string.IsNullOrWhiteSpace(rawText))
{
return null;
}
var candidate = rawText.Trim();
if (!candidate.Contains("://", StringComparison.Ordinal))
{
candidate = $"https://{candidate}";
}
if (!Uri.TryCreate(candidate, UriKind.Absolute, out var uri))
{
return null;
}
return uri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
uri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
? uri
: null;
}
}

View File

@@ -185,6 +185,11 @@ public sealed class DesktopComponentRuntimeRegistry
"component.blackboard_landscape",
() => new WhiteboardWidget(baseWidthCells: 4),
cellSize => Math.Clamp(cellSize * 0.24, 10, 24)),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.DesktopBrowser,
"component.browser",
() => new BrowserWidget(),
cellSize => Math.Clamp(cellSize * 0.24, 10, 24)),
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.HolidayCalendar,
"component.holiday_calendar",

View File

@@ -96,7 +96,7 @@
VerticalAlignment="Center"
Margin="0,2,0,0"
RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,*"
ColumnDefinitions="*,Auto"
RowSpacing="2"
ColumnSpacing="8">
<Border x:Name="CityInfoBadge"
@@ -115,12 +115,27 @@
MaxLines="1" />
</Border>
<Border x:Name="RangeInfoBadge"
<Border x:Name="ConditionInfoBadge"
Grid.Row="1"
Grid.Column="0"
Background="Transparent"
CornerRadius="0"
Padding="0">
<TextBlock x:Name="ConditionTextBlock"
Text="雾"
FontSize="20"
FontWeight="SemiBold"
TextAlignment="Left"
TextTrimming="CharacterEllipsis"
MaxLines="1" />
</Border>
<Border x:Name="RangeInfoBadge"
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
CornerRadius="0"
Padding="0">
<TextBlock x:Name="RangeTextBlock"
Text="11°/4°"
FontSize="20"
@@ -131,21 +146,6 @@
MaxLines="1"
Opacity="0.92" />
</Border>
<Border x:Name="ConditionInfoBadge"
Grid.Row="1"
Grid.Column="1"
Background="Transparent"
CornerRadius="0"
Padding="0">
<TextBlock x:Name="ConditionTextBlock"
Text="雾"
FontSize="20"
FontWeight="SemiBold"
TextAlignment="Left"
TextTrimming="CharacterEllipsis"
MaxLines="1" />
</Border>
</Grid>
<Image x:Name="WeatherIconImage"

View File

@@ -426,22 +426,30 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
var compactness = Math.Clamp((0.90 - scale) / 0.55, 0, 1);
LayoutRoot.RowSpacing = Math.Clamp(height * 0.012, 5, 13);
SummaryGrid.ColumnSpacing = Math.Clamp(width * 0.016, 8, 22);
SummaryInfoGrid.ColumnSpacing = Math.Clamp(width * 0.010, 6, 14);
SummaryInfoGrid.RowSpacing = Math.Clamp(height * 0.003, 1, 4);
HourlyGrid.ColumnSpacing = Math.Clamp(width * 0.007, 3, 10);
DailyGrid.RowSpacing = Math.Clamp(height * 0.009, 4, 10);
TemperatureTextBlock.FontSize = Math.Clamp(height * 0.18, 52, 154);
TemperatureTextBlock.FontWeight = ToVariableWeight(Lerp(300, 370, Math.Clamp((scale - 0.50) / 1.2, 0, 1)));
CityTextBlock.FontSize = Math.Clamp(height * 0.040, 12, 30);
ConditionTextBlock.FontSize = Math.Clamp(height * 0.046, 13, 34);
RangeTextBlock.FontSize = Math.Clamp(height * 0.043, 12, 32);
var cityFontSize = Math.Clamp(height * 0.040, 12, 30);
var topInfoFontSize = Math.Clamp(height * 0.044, 12, 32);
CityTextBlock.FontSize = cityFontSize;
ConditionTextBlock.FontSize = topInfoFontSize;
RangeTextBlock.FontSize = topInfoFontSize;
CityTextBlock.FontWeight = ToVariableWeight(Lerp(520, 590, Math.Clamp((scale - 0.50) / 1.2, 0, 1)));
ConditionTextBlock.FontWeight = ToVariableWeight(Lerp(560, 630, Math.Clamp((scale - 0.50) / 1.2, 0, 1)));
RangeTextBlock.FontWeight = ToVariableWeight(Lerp(560, 620, Math.Clamp((scale - 0.50) / 1.2, 0, 1)));
var topInfoWeight = ToVariableWeight(Lerp(570, 630, Math.Clamp((scale - 0.50) / 1.2, 0, 1)));
ConditionTextBlock.FontWeight = topInfoWeight;
RangeTextBlock.FontWeight = topInfoWeight;
CityTextBlock.LineHeight = cityFontSize * 1.10;
ConditionTextBlock.LineHeight = topInfoFontSize * 1.08;
RangeTextBlock.LineHeight = topInfoFontSize * 1.08;
var iconSize = Math.Clamp(height * 0.116, 36, 102);
WeatherIconImage.Width = iconSize;
WeatherIconImage.Height = iconSize;
ConditionTextBlock.MaxWidth = Math.Clamp(width * 0.20, 80, 240);
RangeTextBlock.MaxWidth = Math.Clamp(width * 0.20, 80, 240);
CityTextBlock.MaxWidth = Math.Clamp(width * 0.28, 90, 290);
ConditionTextBlock.MaxWidth = Math.Clamp(width * 0.24, 88, 280);
RangeTextBlock.MaxWidth = Math.Clamp(width * 0.17, 72, 210);
CityTextBlock.MaxWidth = Math.Clamp(width * 0.30, 96, 320);
HourlyPanelBorder.Padding = new Thickness(0);
HourlyPanelBorder.CornerRadius = new CornerRadius(0);

View File

@@ -12,34 +12,46 @@
CornerRadius="34"
ClipToBounds="True"
Padding="14">
<Viewbox Stretch="Uniform">
<Grid x:Name="LayoutRoot"
Width="300"
Height="300"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
RowSpacing="8">
<TextBlock x:Name="TitleTextBlock"
Grid.Row="0"
Text="Holiday countdown"
FontSize="24"
FontWeight="SemiBold"
Foreground="#61697C"
HorizontalAlignment="Center"
Margin="0,10,0,0" />
<Grid x:Name="LayoutRoot"
RowDefinitions="1.1*,2.3*,0.62*,0.78*,0.95*"
RowSpacing="8">
<TextBlock x:Name="TitleTextBlock"
Grid.Row="0"
Text="Holiday countdown"
FontSize="24"
FontWeight="SemiBold"
Foreground="#61697C"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
TextAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxLines="1"
Margin="8,0,8,0" />
<Viewbox Grid.Row="1"
Stretch="Uniform"
StretchDirection="DownOnly"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Margin="8,0,8,0">
<TextBlock x:Name="CountTextBlock"
Grid.Row="1"
Text="0"
FontFeatures="tnum"
FontSize="120"
FontSize="132"
FontWeight="Bold"
Foreground="#0A0A0A"
HorizontalAlignment="Center" />
HorizontalAlignment="Center"
TextAlignment="Center" />
</Viewbox>
<Canvas Grid.Row="2"
Width="260"
Height="40"
HorizontalAlignment="Center">
<Viewbox Grid.Row="2"
Stretch="Uniform"
StretchDirection="DownOnly"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Margin="14,0,14,0">
<Canvas Width="260"
Height="40">
<Path Data="M 10,16 C 68,11 192,11 250,16"
Stroke="#1A73F0"
StrokeThickness="12"
@@ -60,38 +72,46 @@
Canvas.Top="27.5"
Opacity="0.35" />
</Canvas>
</Viewbox>
<Grid Grid.Row="3"
ColumnDefinitions="*,Auto,*"
Margin="8,0,8,0"
VerticalAlignment="Center">
<Border Grid.Column="0"
Height="2"
Margin="0,0,10,0"
VerticalAlignment="Center"
Background="#B0B9CB" />
<TextBlock x:Name="DayUnitTextBlock"
Grid.Column="1"
Text="Days"
FontSize="56"
Foreground="#7D869A"
FontWeight="Medium"
VerticalAlignment="Center" />
<Border Grid.Column="2"
Height="2"
Margin="10,0,0,0"
VerticalAlignment="Center"
Background="#B0B9CB" />
</Grid>
<TextBlock x:Name="DateTextBlock"
Grid.Row="4"
Text="2024-10-01"
FontSize="34"
Foreground="#596177"
HorizontalAlignment="Center"
Margin="0,2,0,8" />
<Grid Grid.Row="3"
ColumnDefinitions="*,Auto,*"
Margin="8,0,8,0"
VerticalAlignment="Center">
<Border Grid.Column="0"
Height="2"
Margin="0,0,10,0"
VerticalAlignment="Center"
Background="#B0B9CB" />
<TextBlock x:Name="DayUnitTextBlock"
Grid.Column="1"
Text="Days"
FontSize="52"
Foreground="#7D869A"
FontWeight="Medium"
TextAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxLines="1"
VerticalAlignment="Center" />
<Border Grid.Column="2"
Height="2"
Margin="10,0,0,0"
VerticalAlignment="Center"
Background="#B0B9CB" />
</Grid>
</Viewbox>
<TextBlock x:Name="DateTextBlock"
Grid.Row="4"
Text="2024-10-01"
FontSize="32"
Foreground="#596177"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
TextAlignment="Center"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
MaxLines="1"
Margin="8,0,8,0" />
</Grid>
</Border>
</UserControl>

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using LanMontainDesktop.Services;
@@ -32,6 +33,7 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ApplyCellSize(_currentCellSize);
TriggerContentRefresh();
}
@@ -142,6 +144,7 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge
CountTextBlock.Text = "--";
DayUnitTextBlock.Text = isZh ? "\u5929" : "Days";
DateTextBlock.Text = "--";
ApplyCellSize(_currentCellSize);
return;
}
@@ -195,28 +198,216 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge
? $"{holidayDateText} - make-up workday"
: holidayDateText;
}
ApplyCellSize(_currentCellSize);
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);
var scale = ResolveScale();
var width = Bounds.Width > 1 ? Bounds.Width : 220;
var height = Bounds.Height > 1 ? Bounds.Height : 220;
var shortSide = Math.Min(width, height);
var scale = ResolveScale(width, height);
var isCompact = width < 170 || height < 170;
var isUltraCompact = width < 130 || height < 130;
var titleUnits = GetDisplayUnits(TitleTextBlock.Text);
var dateUnits = GetDisplayUnits(DateTextBlock.Text);
var titleNeedsTwoLines = isUltraCompact || titleUnits >= (isCompact ? 13 : 17);
var dateNeedsTwoLines = isUltraCompact || dateUnits >= (isCompact ? 15 : 20);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 15, 50));
RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 7, 22));
LayoutRoot.RowSpacing = Math.Clamp(8 * scale, 4, 14);
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(shortSide * 0.13, 10, 46));
var padding = Math.Clamp(shortSide * 0.05, 4.5, 21);
RootBorder.Padding = new Thickness(padding);
LayoutRoot.RowSpacing = Math.Clamp(shortSide * 0.028, 2.2, 12);
var rowWeights = ApplyAdaptiveRowHeights(isCompact, isUltraCompact, titleNeedsTwoLines, dateNeedsTwoLines);
TitleTextBlock.FontSize = Math.Clamp(24 * scale, 11, 36);
CountTextBlock.FontSize = Math.Clamp(120 * scale, 36, 160);
DayUnitTextBlock.FontSize = Math.Clamp(56 * scale, 16, 78);
DateTextBlock.FontSize = Math.Clamp(34 * scale, 12, 50);
var innerWidth = Math.Max(1, width - padding * 2);
var innerHeight = Math.Max(1, height - padding * 2);
var totalWeight = Math.Max(0.001, rowWeights[0] + rowWeights[1] + rowWeights[2] + rowWeights[3] + rowWeights[4]);
var row0Height = innerHeight * (rowWeights[0] / totalWeight);
var row1Height = innerHeight * (rowWeights[1] / totalWeight);
var row3Height = innerHeight * (rowWeights[3] / totalWeight);
var row4Height = innerHeight * (rowWeights[4] / totalWeight);
var horizontalMargin = Math.Clamp(8 * scale, 4, 14);
var titleMaxWidth = Math.Max(24, innerWidth - horizontalMargin * 2);
var dateMaxWidth = titleMaxWidth;
var titlePreferred = Math.Clamp(24 * scale, 8.8, 34);
var titleHeightCap = Math.Max(10, row0Height * 0.94);
var titleLineCount = titleNeedsTwoLines ? 2 : 1;
TitleTextBlock.MaxLines = titleLineCount;
TitleTextBlock.TextWrapping = titleLineCount > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
TitleTextBlock.Margin = new Thickness(horizontalMargin, 0, horizontalMargin, 0);
TitleTextBlock.FontSize = FitTextSize(
TitleTextBlock.Text,
TitleTextBlock.FontWeight,
Math.Min(titlePreferred, Math.Max(8.8, row0Height * 0.62)),
8.6,
titleMaxWidth,
titleHeightCap,
titleLineCount,
lineHeightFactor: 1.10);
TitleTextBlock.LineHeight = TitleTextBlock.FontSize * 1.10;
var digitCount = Math.Max(1, CountTextBlock.Text?.Trim().Length ?? 1);
var digitCompression = digitCount switch
{
>= 5 => 0.68,
4 => 0.8,
3 => 0.9,
_ => 1.0
};
var countCompactFactor = isUltraCompact ? 0.86 : isCompact ? 0.93 : 1.0;
var countPreferred = Math.Clamp(132 * scale * digitCompression * countCompactFactor, 28, 170);
var countHeightCap = Math.Max(30, row1Height * 0.96);
CountTextBlock.FontSize = FitTextSize(
CountTextBlock.Text,
CountTextBlock.FontWeight,
Math.Min(countPreferred, Math.Max(28, row1Height * 0.9)),
24,
titleMaxWidth,
countHeightCap,
maxLines: 1,
lineHeightFactor: 1.08);
CountTextBlock.LineHeight = CountTextBlock.FontSize * 1.08;
var unitCompactFactor = isUltraCompact ? 0.8 : isCompact ? 0.9 : 1.0;
DayUnitTextBlock.FontSize = Math.Clamp(52 * scale * unitCompactFactor, 10, 72);
DayUnitTextBlock.FontSize = Math.Min(DayUnitTextBlock.FontSize, Math.Max(10, row3Height * 0.64));
DayUnitTextBlock.LineHeight = DayUnitTextBlock.FontSize * 1.02;
var dateCompactFactor = isUltraCompact ? 0.84 : isCompact ? 0.92 : 1.0;
var datePreferred = Math.Clamp(32 * scale * dateCompactFactor, 9, 46);
var dateHeightCap = Math.Max(10, row4Height * 0.96);
var dateLineCount = dateNeedsTwoLines ? 2 : 1;
DateTextBlock.MaxLines = dateLineCount;
DateTextBlock.TextWrapping = dateLineCount > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap;
DateTextBlock.Margin = new Thickness(horizontalMargin, 0, horizontalMargin, 0);
DateTextBlock.FontSize = FitTextSize(
DateTextBlock.Text,
DateTextBlock.FontWeight,
Math.Min(datePreferred, Math.Max(9, row4Height * 0.58)),
8.5,
dateMaxWidth,
dateHeightCap,
dateLineCount,
lineHeightFactor: 1.12);
DateTextBlock.LineHeight = DateTextBlock.FontSize * 1.12;
}
private double ResolveScale()
private double[] ApplyAdaptiveRowHeights(
bool isCompact,
bool isUltraCompact,
bool titleNeedsTwoLines,
bool dateNeedsTwoLines)
{
var cellScale = Math.Clamp(_currentCellSize / 44d, 0.60, 1.95);
var heightScale = Bounds.Height > 1 ? Math.Clamp(Bounds.Height / 300d, 0.58, 2.0) : 1;
var widthScale = Bounds.Width > 1 ? Math.Clamp(Bounds.Width / 300d, 0.58, 2.0) : 1;
return Math.Clamp(Math.Min(cellScale, Math.Min(heightScale, widthScale) * 1.05), 0.58, 1.95);
var weights = isUltraCompact
? new[] { 1.35, 2.55, 0.48, 0.6, 0.82 }
: isCompact
? new[] { 1.2, 2.45, 0.56, 0.7, 0.9 }
: new[] { 1.1, 2.3, 0.62, 0.78, 0.95 };
if (titleNeedsTwoLines)
{
weights[0] += 0.36;
weights[1] -= 0.21;
weights[2] -= 0.08;
weights[3] -= 0.07;
}
if (dateNeedsTwoLines)
{
weights[4] += 0.42;
weights[1] -= 0.23;
weights[2] -= 0.10;
weights[3] -= 0.09;
}
weights[0] = Math.Max(0.92, weights[0]);
weights[1] = Math.Max(1.45, weights[1]);
weights[2] = Math.Max(0.34, weights[2]);
weights[3] = Math.Max(0.44, weights[3]);
weights[4] = Math.Max(0.72, weights[4]);
if (LayoutRoot.RowDefinitions.Count < 5)
{
return weights;
}
for (var i = 0; i < 5; i++)
{
LayoutRoot.RowDefinitions[i].Height = new GridLength(weights[i], GridUnitType.Star);
}
return weights;
}
private static int GetDisplayUnits(string? text)
{
if (string.IsNullOrWhiteSpace(text))
{
return 0;
}
var units = 0;
foreach (var ch in text.Trim())
{
if (char.IsWhiteSpace(ch))
{
continue;
}
units += ch > 0x7F ? 2 : 1;
}
return units;
}
private static double FitTextSize(
string? text,
FontWeight fontWeight,
double preferredSize,
double minSize,
double maxWidth,
double maxHeight,
int maxLines,
double lineHeightFactor)
{
var safeText = string.IsNullOrWhiteSpace(text) ? " " : text.Trim();
var safeMaxWidth = Math.Max(1, maxWidth);
var safeMaxHeight = Math.Max(1, maxHeight);
var safeMaxLines = Math.Max(1, maxLines);
var probe = new TextBlock
{
Text = safeText,
FontWeight = fontWeight,
MaxLines = safeMaxLines,
TextWrapping = safeMaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap
};
for (var size = preferredSize; size >= minSize; size -= 0.5)
{
probe.FontSize = size;
probe.LineHeight = size * lineHeightFactor;
probe.Measure(new Size(safeMaxWidth, double.PositiveInfinity));
var desired = probe.DesiredSize;
if (desired.Width <= safeMaxWidth + 0.6 &&
desired.Height <= safeMaxHeight + 0.6)
{
return size;
}
}
return minSize;
}
private double ResolveScale(double width, double height)
{
var cellScale = Math.Clamp(_currentCellSize / 44d, 0.56, 2.0);
var widthScale = Math.Clamp(width / 220d, 0.5, 2.0);
var heightScale = Math.Clamp(height / 220d, 0.5, 2.0);
return Math.Clamp(Math.Min(cellScale, Math.Min(widthScale, heightScale) * 1.02), 0.5, 2.0);
}
}

View File

@@ -17,3 +17,8 @@ public interface IWeatherInfoAwareComponentWidget
{
void SetWeatherInfoService(IWeatherInfoService weatherInfoService);
}
public interface IDesktopPageVisibilityAwareComponentWidget
{
void SetDesktopPageContext(bool isOnActivePage, bool isEditMode);
}

View File

@@ -825,10 +825,10 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, ITime
TopRowGrid.ColumnSpacing = Math.Clamp(7.5 * scaleX, 4, 13);
var availableHeight = Math.Max(80, innerHeight - (ContentGrid.RowSpacing * 2));
var topZoneRatio = Math.Clamp(0.55 + ((1 - compactness) * 0.04), 0.52, 0.60);
var bottomZoneRatio = Math.Clamp(0.29 - (compactness * 0.03), 0.24, 0.32);
var topZoneHeight = Math.Clamp(availableHeight * topZoneRatio, 48, availableHeight - 28);
var bottomZoneHeight = Math.Clamp(availableHeight * bottomZoneRatio, 26, availableHeight - topZoneHeight - 6);
var topZoneRatio = Math.Clamp(0.52 + ((1 - compactness) * 0.03), 0.48, 0.56);
var bottomZoneRatio = Math.Clamp(0.36 - (compactness * 0.02), 0.32, 0.40);
var topZoneHeight = Math.Clamp(availableHeight * topZoneRatio, 44, availableHeight - 30);
var bottomZoneHeight = Math.Clamp(availableHeight * bottomZoneRatio, 34, availableHeight - topZoneHeight - 6);
if (topZoneHeight + bottomZoneHeight > availableHeight - 6)
{
bottomZoneHeight = Math.Max(24, availableHeight - topZoneHeight - 6);
@@ -842,11 +842,11 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, ITime
ContentGrid.RowDefinitions[2].Height = new GridLength(bottomZoneHeight, GridUnitType.Pixel);
}
var topScaleH = Math.Clamp(topZoneHeight / 112d, 0.60, 2.2);
var topScaleH = Math.Clamp(topZoneHeight / 112d, 0.58, 2.2);
var topScaleW = Math.Clamp(innerWidth / 288d, 0.60, 2.2);
var topScale = Math.Clamp((topScaleH * 0.72) + (topScaleW * 0.28), 0.60, 2.2);
var bottomScaleH = Math.Clamp(bottomZoneHeight / 94d, 0.52, 2.1);
var bottomScale = Math.Clamp((bottomScaleH * 0.76) + (scaleX * 0.24), 0.52, 2.1);
var topScale = Math.Clamp((topScaleH * 0.70) + (topScaleW * 0.30), 0.58, 2.2);
var bottomScaleH = Math.Clamp(bottomZoneHeight / 80d, 0.62, 2.2);
var bottomScale = Math.Clamp((bottomScaleH * 0.80) + (scaleX * 0.20), 0.62, 2.2);
var iconSize = Math.Clamp(
Math.Max(52, topZoneHeight * 0.50) * (0.76 + (topScale * 0.24)),
@@ -857,9 +857,9 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, ITime
WeatherIconImage.Margin = new Thickness(0, Math.Clamp(-5 * topScale, -12, 0), 0, 0);
TemperatureTextBlock.FontSize = Math.Clamp(
Math.Max(56, topZoneHeight * 0.74) * (0.72 + (topScale * 0.28)),
52,
156);
Math.Max(52, topZoneHeight * 0.69) * (0.74 + (topScale * 0.24)),
50,
146);
TemperatureTextBlock.FontWeight = ToVariableWeight(310);
TemperatureTextBlock.Margin = new Thickness(Math.Clamp(-2 * topScale, -5, 0), Math.Clamp(-8 * topScale, -14, -3), 0, 0);
var temperatureMaxWidthLimit = Math.Max(90, innerWidth * 0.70);
@@ -868,33 +868,57 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, ITime
90,
temperatureMaxWidthLimit);
BottomInfoStack.Spacing = Math.Clamp(1.0 * bottomScale, 0, 3);
var bottomStackSpacing = Math.Clamp(1.2 * bottomScale, 1, 4);
BottomInfoStack.Spacing = bottomStackSpacing;
BottomInfoStack.Margin = new Thickness(0, 0, 0, Math.Clamp(1.8 * scaleY, 0, 4));
BottomInfoStack.MaxHeight = Math.Max(24, bottomZoneHeight);
BottomInfoStack.MaxHeight = Math.Max(32, bottomZoneHeight);
var bottomTextMaxWidth = Math.Min(innerWidth, Math.Max(48, innerWidth * 0.78));
ConditionStack.Spacing = Math.Clamp(1.0 + (1.6 * bottomScale), 1, 5);
var bottomTextMaxWidth = Math.Min(innerWidth, Math.Max(56, innerWidth * 0.84));
var conditionStackSpacing = Math.Clamp(1.4 + (2.1 * bottomScale), 1.2, 7);
ConditionStack.Spacing = conditionStackSpacing;
ConditionStack.Margin = new Thickness(0);
var infoFontSize = Math.Clamp(
Math.Max(12, bottomZoneHeight * 0.30) * (0.78 + (bottomScale * 0.22)),
12,
34);
var infoFontWeight = ToVariableWeight(560);
var infoFontSizeRaw = Math.Clamp(
Math.Max(14, bottomZoneHeight * 0.38) * (0.82 + (bottomScale * 0.24)),
15,
42);
var infoFontSize = infoFontSizeRaw;
const double infoLineHeightFactor = 1.10;
var estimatedBottomUsedHeight =
(infoFontSize * infoLineHeightFactor * 3) +
conditionStackSpacing +
bottomStackSpacing +
2;
if (estimatedBottomUsedHeight > bottomZoneHeight)
{
var shrink = Math.Clamp(bottomZoneHeight / estimatedBottomUsedHeight, 0.58, 1.0);
infoFontSize = Math.Max(11, infoFontSize * shrink);
conditionStackSpacing = Math.Max(0.8, conditionStackSpacing * shrink);
bottomStackSpacing = Math.Max(0.8, bottomStackSpacing * shrink);
ConditionStack.Spacing = conditionStackSpacing;
BottomInfoStack.Spacing = bottomStackSpacing;
}
var infoFontWeight = ToVariableWeight(590);
ConditionTextBlock.FontSize = infoFontSize;
ConditionTextBlock.FontWeight = infoFontWeight;
ConditionTextBlock.LineHeight = infoFontSize * infoLineHeightFactor;
ConditionTextBlock.MaxWidth = bottomTextMaxWidth;
RangeTextBlock.FontSize = infoFontSize;
RangeTextBlock.FontWeight = infoFontWeight;
RangeTextBlock.LineHeight = infoFontSize * infoLineHeightFactor;
RangeTextBlock.MaxWidth = bottomTextMaxWidth;
CityInfoBadge.Padding = new Thickness(0);
CityInfoBadge.CornerRadius = new CornerRadius(0);
CityInfoBadge.MaxWidth = bottomTextMaxWidth;
LocationIcon.FontSize = Math.Clamp(
Math.Max(8, bottomZoneHeight * 0.13) * (0.74 + (bottomScale * 0.20)),
8,
14);
Math.Max(9, bottomZoneHeight * 0.16) * (0.76 + (bottomScale * 0.22)),
9,
18);
LocationIcon.FontSize = Math.Min(LocationIcon.FontSize, infoFontSize * 0.72);
CityTextBlock.FontSize = infoFontSize;
CityTextBlock.FontWeight = infoFontWeight;
CityTextBlock.LineHeight = infoFontSize * infoLineHeightFactor;
CityTextBlock.MaxWidth = bottomTextMaxWidth;
}

View File

@@ -934,6 +934,8 @@ public partial class MainWindow
Grid.SetRowSpan(host, heightCells);
pageGrid.Children.Add(host);
}
UpdateDesktopPageAwareComponentContext();
}
private void PlaceDesktopComponentOnPage(string componentId, int pageIndex, int row, int column)
@@ -991,6 +993,7 @@ public partial class MainWindow
pageGrid.Children.Add(host);
_desktopComponentPlacements.Add(placement);
UpdateDesktopPageAwareComponentContext();
PersistSettings();
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
@@ -1291,6 +1294,45 @@ public partial class MainWindow
}
}
private void UpdateDesktopPageAwareComponentContext()
{
var activeDesktopPageIndex = _isSettingsOpen ? -1 : _currentDesktopSurfaceIndex;
var isEditMode = _isComponentLibraryOpen || _isSettingsOpen;
foreach (var pair in _desktopPageComponentGrids)
{
var isOnActivePage = pair.Key == activeDesktopPageIndex;
foreach (var host in pair.Value.Children.OfType<Border>())
{
if (!host.Classes.Contains(DesktopComponentHostClass))
{
continue;
}
if (TryGetContentHost(host)?.Child is Control componentRoot)
{
ApplyDesktopPageContext(componentRoot, isOnActivePage, isEditMode);
}
}
}
}
private static void ApplyDesktopPageContext(Control root, bool isOnActivePage, bool isEditMode)
{
if (root is IDesktopPageVisibilityAwareComponentWidget awareRoot)
{
awareRoot.SetDesktopPageContext(isOnActivePage, isEditMode);
}
foreach (var descendant in root.GetVisualDescendants())
{
if (descendant is IDesktopPageVisibilityAwareComponentWidget awareChild)
{
awareChild.SetDesktopPageContext(isOnActivePage, isEditMode);
}
}
}
private static Border? TryGetResizeHandle(Border host)
{
if (host.Child is Grid hostChrome)
@@ -1368,6 +1410,8 @@ public partial class MainWindow
}
}
}
UpdateDesktopPageAwareComponentContext();
}
private void ApplyDesktopEditStateToHost(Border host, bool isEditMode)

View File

@@ -281,6 +281,8 @@ public partial class MainWindow
{
CloseLauncherFolderOverlay();
}
UpdateDesktopPageAwareComponentContext();
}
private void MoveSurfaceBy(int delta)

View File

@@ -1769,6 +1769,7 @@ public partial class MainWindow
}
_isSettingsOpen = true;
UpdateDesktopPageAwareComponentContext();
UpdateAdaptiveTextSystem();
ApplyWallpaperBrush();
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
@@ -1805,6 +1806,7 @@ public partial class MainWindow
}
_isSettingsOpen = false;
UpdateDesktopPageAwareComponentContext();
UpdateAdaptiveTextSystem();
ApplyWallpaperBrush();
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());