diff --git a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml index 7325a96..e5d3301 100644 --- a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml +++ b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml @@ -18,7 +18,8 @@ + Background="#B8AE9A" + PointerPressed="OnArtworkPanelPointerPressed"> @@ -34,12 +35,14 @@ FontSize="44" FontWeight="Bold" FontFeatures="tnum" + TextTrimming="CharacterEllipsis" LineHeight="46" /> @@ -48,7 +51,8 @@ + Padding="18,14,18,14" + PointerPressed="OnInfoPanelPointerPressed"> @@ -96,6 +101,7 @@ FontSize="26" FontWeight="SemiBold" TextWrapping="Wrap" + TextTrimming="CharacterEllipsis" MaxLines="2" /> diff --git a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs index 6ae0cdd..79aae00 100644 --- a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs @@ -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 diff --git a/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs index c50640e..48d7dea 100644 --- a/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/ExtendedWeatherWidget.axaml.cs @@ -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)); } diff --git a/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs index 307eb9e..e7489f1 100644 --- a/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/HourlyWeatherWidget.axaml.cs @@ -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++) { diff --git a/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs b/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs index 7b5e15d..62ae895 100644 --- a/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/MultiDayWeatherWidget.axaml.cs @@ -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++) {