修天气,时钟,每日图片....
This commit is contained in:
lincube
2026-03-05 17:09:46 +08:00
parent 24f1b896e1
commit 8768fa1ed2
5 changed files with 175 additions and 25 deletions

View File

@@ -18,7 +18,8 @@
<Border x:Name="ArtworkPanel"
Grid.Column="0"
ClipToBounds="True"
Background="#B8AE9A">
Background="#B8AE9A"
PointerPressed="OnArtworkPanelPointerPressed">
<Grid>
<Image x:Name="ArtworkImage"
Stretch="UniformToFill" />
@@ -34,12 +35,14 @@
FontSize="44"
FontWeight="Bold"
FontFeatures="tnum"
TextTrimming="CharacterEllipsis"
LineHeight="46" />
<TextBlock x:Name="WeekdayTextBlock"
Text="星期二"
Foreground="#F9F9F9"
FontSize="44"
FontWeight="Bold"
TextTrimming="CharacterEllipsis"
LineHeight="46" />
</StackPanel>
</Grid>
@@ -48,7 +51,8 @@
<Border Grid.Column="1"
x:Name="InfoPanel"
Background="#111418"
Padding="18,14,18,14">
Padding="18,14,18,14"
PointerPressed="OnInfoPanelPointerPressed">
<Grid>
<Canvas x:Name="BrickPatternCanvas"
IsHitTestVisible="False"
@@ -76,6 +80,7 @@
FontSize="44"
FontWeight="Bold"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
MaxLines="2"
Margin="0,0,0,8" />
@@ -96,6 +101,7 @@
FontSize="26"
FontWeight="SemiBold"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
MaxLines="2" />
<TextBlock x:Name="YearTextBlock"
Text="1754"
@@ -104,6 +110,7 @@
FontWeight="Medium"
FontFeatures="tnum"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
MaxLines="1" />
</StackPanel>
</Grid>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Http;
@@ -8,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
@@ -62,6 +64,8 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
private double _currentCellSize = BaseCellSize;
private bool _isAttached;
private bool _isRefreshing;
private string? _currentArtworkSourceUrl;
private string? _currentArtworkImageUrl;
public DailyArtworkWidget()
{
@@ -102,7 +106,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
0,
0,
Math.Clamp(16 * scale, 8, 26));
DateInfoStack.Spacing = Math.Clamp(2 * scale, 1, 6);
DateInfoStack.Spacing = Math.Clamp(4 * scale, 2, 10);
StatusTextBlock.FontSize = Math.Clamp(16 * scale, 10, 24);
@@ -154,6 +158,28 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
await RefreshArtworkAsync(forceRefresh: false);
}
private void OnArtworkPanelPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}
_ = RefreshArtworkAsync(forceRefresh: true);
e.Handled = true;
}
private void OnInfoPanelPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}
TryOpenArtworkSourceUrl();
e.Handled = true;
}
private async Task RefreshArtworkAsync(bool forceRefresh)
{
if (!_isAttached || _isRefreshing)
@@ -222,6 +248,8 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
ArtistTextBlock.Text = NormalizeCompactText(artist);
YearTextBlock.Text = ResolveYearText(snapshot);
_currentArtworkSourceUrl = snapshot.ArtworkUrl;
_currentArtworkImageUrl = snapshot.ImageUrl;
StatusTextBlock.IsVisible = false;
UpdateAdaptiveLayout();
@@ -352,6 +380,8 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
private void ApplyLoadingState()
{
_currentArtworkSourceUrl = null;
_currentArtworkImageUrl = null;
StatusTextBlock.IsVisible = true;
StatusTextBlock.Text = L("artwork.widget.loading", "Loading...");
PaintingTitleTextBlock.Text = BuildQuotedTitle(L("artwork.widget.loading_title", "Daily Artwork"));
@@ -362,6 +392,8 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
private void ApplyFailedState()
{
_currentArtworkSourceUrl = null;
_currentArtworkImageUrl = null;
StatusTextBlock.IsVisible = true;
StatusTextBlock.Text = L("artwork.widget.fetch_failed", "Artwork fetch failed");
PaintingTitleTextBlock.Text = BuildQuotedTitle(L("artwork.widget.fallback_title", "Daily Artwork"));
@@ -384,71 +416,94 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
var rightContentWidth = Math.Max(58, rightPanelWidth - InfoPanel.Padding.Left - InfoPanel.Padding.Right);
var leftPanelWidth = Math.Max(84, totalWidth - rightPanelWidth);
var leftContentWidth = Math.Max(52, leftPanelWidth - DateInfoStack.Margin.Left - 10);
var leftContentHeight = Math.Max(30, totalHeight - DateInfoStack.Margin.Bottom - 10);
var dateStackSpacing = Math.Clamp(4 * scale, 2, 10);
DateInfoStack.Spacing = dateStackSpacing;
DateInfoStack.MaxWidth = leftContentWidth;
var leftSingleLineHeight = Math.Max(12, (leftContentHeight - dateStackSpacing) / 2d);
var dateBase = Math.Clamp(44 * scale, 16, 62);
DateTextBlock.FontSize = FitFontSize(
DateTextBlock.Text,
leftContentWidth,
Math.Max(18, totalHeight * 0.20),
leftSingleLineHeight,
maxLines: 1,
minFontSize: Math.Max(12, dateBase * 0.68),
maxFontSize: dateBase,
weight: FontWeight.Bold,
lineHeightFactor: 1.00);
DateTextBlock.LineHeight = DateTextBlock.FontSize * 1.00;
lineHeightFactor: 1.10);
DateTextBlock.LineHeight = DateTextBlock.FontSize * 1.10;
WeekdayTextBlock.FontSize = FitFontSize(
WeekdayTextBlock.Text,
leftContentWidth,
Math.Max(18, totalHeight * 0.21),
leftSingleLineHeight,
maxLines: 1,
minFontSize: Math.Max(12, dateBase * 0.68),
maxFontSize: dateBase,
weight: FontWeight.Bold,
lineHeightFactor: 1.00);
WeekdayTextBlock.LineHeight = WeekdayTextBlock.FontSize * 1.00;
lineHeightFactor: 1.10);
WeekdayTextBlock.LineHeight = WeekdayTextBlock.FontSize * 1.10;
var rightContentHeight = Math.Max(42, totalHeight - InfoPanel.Padding.Top - InfoPanel.Padding.Bottom);
var titleBottomMargin = Math.Clamp(8 * scale, 4, 14);
var separatorBottomMargin = Math.Clamp(10 * scale, 4, 14);
var bottomStackSpacing = Math.Clamp(3 * scale, 2, 8);
var reservedHeight = titleBottomMargin + separatorBottomMargin + bottomStackSpacing + 3;
var textHeightBudget = Math.Max(24, rightContentHeight - reservedHeight);
var titleHeightBudget = Math.Max(16, textHeightBudget * 0.54);
var bottomTextBudget = Math.Max(10, textHeightBudget - titleHeightBudget);
var artistHeightBudget = Math.Max(8, bottomTextBudget * 0.66);
var yearHeightBudget = Math.Max(8, bottomTextBudget - artistHeightBudget);
var titleBase = Math.Clamp(44 * scale, 16, 58);
PaintingTitleTextBlock.MaxWidth = rightContentWidth;
PaintingTitleTextBlock.Margin = new Thickness(0, 0, 0, titleBottomMargin);
PaintingTitleTextBlock.FontSize = FitFontSize(
PaintingTitleTextBlock.Text,
rightContentWidth,
Math.Max(20, totalHeight * 0.34),
titleHeightBudget,
maxLines: 2,
minFontSize: Math.Max(12, titleBase * 0.62),
maxFontSize: titleBase,
weight: FontWeight.Bold,
lineHeightFactor: 1.08);
PaintingTitleTextBlock.LineHeight = PaintingTitleTextBlock.FontSize * 1.08;
lineHeightFactor: 1.12);
PaintingTitleTextBlock.LineHeight = PaintingTitleTextBlock.FontSize * 1.12;
var artistBase = Math.Clamp(26 * scale, 11, 34);
if (ArtistTextBlock.Parent is StackPanel artistInfoStack)
{
artistInfoStack.Spacing = bottomStackSpacing;
}
ArtistTextBlock.MaxWidth = rightContentWidth;
ArtistTextBlock.FontSize = FitFontSize(
ArtistTextBlock.Text,
rightContentWidth,
Math.Max(18, totalHeight * 0.24),
artistHeightBudget,
maxLines: 2,
minFontSize: Math.Max(10, artistBase * 0.72),
maxFontSize: artistBase,
weight: FontWeight.SemiBold,
lineHeightFactor: 1.12);
ArtistTextBlock.LineHeight = ArtistTextBlock.FontSize * 1.12;
lineHeightFactor: 1.14);
ArtistTextBlock.LineHeight = ArtistTextBlock.FontSize * 1.14;
var yearBase = Math.Clamp(22 * scale, 10, 30);
YearTextBlock.MaxWidth = rightContentWidth;
YearTextBlock.FontSize = FitFontSize(
YearTextBlock.Text,
rightContentWidth,
Math.Max(14, totalHeight * 0.12),
yearHeightBudget,
maxLines: 1,
minFontSize: Math.Max(9.5, yearBase * 0.78),
maxFontSize: yearBase,
weight: FontWeight.Medium,
lineHeightFactor: 1.04);
YearTextBlock.LineHeight = YearTextBlock.FontSize * 1.04;
lineHeightFactor: 1.08);
YearTextBlock.LineHeight = YearTextBlock.FontSize * 1.08;
RightPanelSeparator.Width = Math.Clamp(rightContentWidth * 0.58, 42, 136);
RightPanelSeparator.Margin = new Thickness(0, 0, 0, Math.Clamp(10 * scale, 4, 14));
RightPanelSeparator.Margin = new Thickness(0, 0, 0, separatorBottomMargin);
BrickPatternCanvas.Opacity = totalWidth < _currentCellSize * 4.2
? 0.34
@@ -478,6 +533,54 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
_currentArtworkBitmap = null;
}
private void TryOpenArtworkSourceUrl()
{
var candidate = _currentArtworkSourceUrl;
if (!TryNormalizeHttpUrl(candidate, out var normalizedUrl) &&
!TryNormalizeHttpUrl(_currentArtworkImageUrl, out normalizedUrl))
{
return;
}
try
{
var startInfo = new ProcessStartInfo
{
FileName = normalizedUrl,
UseShellExecute = true
};
Process.Start(startInfo);
}
catch
{
// Ignore malformed URLs or shell launch failures.
}
}
private static bool TryNormalizeHttpUrl(string? rawUrl, out string normalizedUrl)
{
normalizedUrl = string.Empty;
if (string.IsNullOrWhiteSpace(rawUrl))
{
return false;
}
var candidate = rawUrl.Trim();
if (!Uri.TryCreate(candidate, UriKind.Absolute, out var uri))
{
return false;
}
if (!string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
return false;
}
normalizedUrl = uri.ToString();
return true;
}
private void UpdateLanguageCode()
{
try

View File

@@ -43,6 +43,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
private readonly TextBlock[] _dailyHighBlocks;
private readonly TextBlock[] _dailyLowBlocks;
private readonly Image[] _dailyIconBlocks;
private readonly HyperOS3WeatherVisualKind[] _dailyIconKinds;
public ExtendedWeatherWidget()
{
@@ -76,6 +77,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
[
DailyIcon0, DailyIcon1, DailyIcon2, DailyIcon3, DailyIcon4
];
_dailyIconKinds = Enumerable.Repeat(HyperOS3WeatherVisualKind.CloudyDay, _dailyIconBlocks.Length).ToArray();
ConfigureTextOverflowGuards();
_refreshTimer.Tick += OnRefreshTimerTick;
_animationTimer.Tick += OnAnimationTick;
@@ -344,6 +346,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
_dailyLabelBlocks[i].Text = $"{ResolveDayLabel(date, i + 1)}·{dayText}";
_dailyHighBlocks[i].Text = FormatTemperatureValue(daily?.HighTemperatureC);
_dailyLowBlocks[i].Text = FormatTemperatureValue(daily?.LowTemperatureC);
_dailyIconKinds[i] = dayKind;
_dailyIconBlocks[i].Source = HyperOS3WeatherAssetLoader.LoadImage(HyperOS3WeatherTheme.ResolveMiniIconAsset(dayKind));
}
}
@@ -371,6 +374,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
_dailyLabelBlocks[i].Text = $"{ResolveDayLabel(DateOnly.FromDateTime(DateTime.Now).AddDays(i + 1), i + 1)}·{L("weather.widget.condition_cloudy", "Cloudy")}";
_dailyHighBlocks[i].Text = "--";
_dailyLowBlocks[i].Text = "--";
_dailyIconKinds[i] = HyperOS3WeatherVisualKind.CloudyDay;
_dailyIconBlocks[i].Source = HyperOS3WeatherAssetLoader.LoadImage(HyperOS3WeatherTheme.ResolveMiniIconAsset(HyperOS3WeatherVisualKind.CloudyDay));
}
}
@@ -523,7 +527,9 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
3.80);
var hourlyTempSize = Math.Clamp(19 * hourlyCellScale, 6, 72);
var hourlyTimeSize = Math.Clamp(14 * hourlyCellScale, 6, 52);
var hourlyIconSize = Math.Clamp(34 * hourlyCellScale, 8, 114);
var hourlyIconSize = Math.Clamp(42 * hourlyCellScale, 9, 140);
hourlyIconSize = Math.Min(hourlyIconSize, Math.Max(10, hourlyCellWidth * 0.86));
hourlyIconSize = Math.Min(hourlyIconSize, Math.Max(10, hourlyHeight * 0.56));
var hourlyStackSpacing = Math.Clamp(2 * hourlyCellScale, 0.2, 10);
for (var i = 0; i < _hourlyTempBlocks.Length; i++)
{
@@ -548,7 +554,9 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
var dailyLabelSize = Math.Clamp(18.5 * dailyRowScale, 6, 70);
var dailyTempSize = Math.Clamp(19 * dailyRowScale, 6, 72);
var dailyIconSize = Math.Clamp(30 * dailyRowScale, 8, 102);
var dailyIconSize = Math.Clamp(43 * dailyRowScale, 9, 132);
dailyIconSize = Math.Min(dailyIconSize, Math.Max(10, dailyRowHeight * 0.92));
dailyIconSize = Math.Min(dailyIconSize, Math.Max(10, innerWidth * 0.14));
var dailyLabelMaxWidth = Math.Clamp(innerWidth * 0.52, 28, 460);
var dailyHighWidth = Math.Clamp(innerWidth * 0.14, 14, 140);
var dailyLowWidth = Math.Clamp(innerWidth * 0.11, 12, 120);
@@ -570,8 +578,21 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
_dailyLowBlocks[i].HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right;
_dailyHighBlocks[i].TextAlignment = TextAlignment.Right;
_dailyLowBlocks[i].TextAlignment = TextAlignment.Right;
_dailyIconBlocks[i].Width = dailyIconSize;
_dailyIconBlocks[i].Height = dailyIconSize;
if (_dailyIconBlocks[i].Parent is Grid dailyRowGrid)
{
dailyRowGrid.ColumnSpacing = Math.Clamp(9 * dailyRowScale, 4, 18);
}
var dailyKind = i < _dailyIconKinds.Length
? _dailyIconKinds[i]
: HyperOS3WeatherVisualKind.CloudyDay;
var dailyIconVisualSize = Math.Clamp(
dailyIconSize * ResolveDailyMiniIconScaleBoost(dailyKind),
8,
148);
dailyIconVisualSize = Math.Min(dailyIconVisualSize, Math.Max(10, dailyRowHeight * 0.94));
_dailyIconBlocks[i].Width = dailyIconVisualSize;
_dailyIconBlocks[i].Height = dailyIconVisualSize;
}
}
@@ -905,6 +926,21 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
HyperOS3WeatherVisualKind.ClearNight or HyperOS3WeatherVisualKind.CloudyNight => 1.08,
_ => 1.0
};
private static double ResolveDailyMiniIconScaleBoost(HyperOS3WeatherVisualKind kind) =>
kind switch
{
HyperOS3WeatherVisualKind.CloudyDay => 1.30,
HyperOS3WeatherVisualKind.CloudyNight => 1.28,
HyperOS3WeatherVisualKind.ClearDay => 1.26,
HyperOS3WeatherVisualKind.ClearNight => 1.24,
HyperOS3WeatherVisualKind.Fog => 1.18,
HyperOS3WeatherVisualKind.RainLight => 1.14,
HyperOS3WeatherVisualKind.RainHeavy => 1.12,
HyperOS3WeatherVisualKind.Snow => 1.12,
HyperOS3WeatherVisualKind.Storm => 1.08,
_ => 1.18
};
private static FontWeight ToVariableWeight(double weight) => (FontWeight)(int)Math.Clamp(Math.Round(weight), 1, 1000);
private static IBrush CreateSolidBrush(string colorHex) => new SolidColorBrush(Color.Parse(colorHex));
private static IBrush CreateSolidBrush(string colorHex, byte alpha) { var c = Color.Parse(colorHex); return new SolidColorBrush(Color.FromArgb(alpha, c.R, c.G, c.B)); }

View File

@@ -1264,7 +1264,9 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
var stackSpacing = Math.Clamp(2 * hourlyCellScale, 0.2, 10);
var hourlyTempSize = Math.Clamp(19.5 * hourlyCellScale, 6, 72);
var hourlyTimeSize = Math.Clamp(14.5 * hourlyCellScale, 6, 50);
var hourlyIconSize = Math.Clamp(34 * hourlyCellScale, 8, 108);
var hourlyIconSize = Math.Clamp(42 * hourlyCellScale, 9, 136);
hourlyIconSize = Math.Min(hourlyIconSize, Math.Max(10, hourlyCellWidth * 0.86));
hourlyIconSize = Math.Min(hourlyIconSize, Math.Max(10, bottomZoneHeight * 0.52));
for (var i = 0; i < _hourlyTimeBlocks.Length; i++)
{

View File

@@ -1112,7 +1112,9 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
var stackSpacing = Math.Clamp(2 * hourlyCellScale, 0.2, 10);
var forecastRangeSize = Math.Clamp(18.0 * hourlyCellScale, 6, 62);
var forecastLabelSize = Math.Clamp(13.8 * hourlyCellScale, 6, 48);
var forecastIconSize = Math.Clamp(32 * hourlyCellScale, 8, 100);
var forecastIconSize = Math.Clamp(40 * hourlyCellScale, 9, 124);
forecastIconSize = Math.Min(forecastIconSize, Math.Max(10, hourlyCellWidth * 0.88));
forecastIconSize = Math.Min(forecastIconSize, Math.Max(10, bottomZoneHeight * 0.50));
for (var i = 0; i < _hourlyTimeBlocks.Length; i++)
{