From 65a3cf832ad34ace5bdbfc95e27aeafab9a2f32e Mon Sep 17 00:00:00 2001 From: lincube Date: Fri, 20 Mar 2026 14:22:33 +0800 Subject: [PATCH] Revert "0.7.0.0" This reverts commit aeae4be060f0434d9f3fd5b342b693f24225c64d. --- Directory.Build.props | 1 - .../ComponentTypographyLayoutModels.cs | 30 -- .../ComponentTypographyLayoutService.cs | 314 -------------- .../Services/IMusicControlService.cs | 72 +--- .../WindowsSmtcMusicControlService.cs | 382 +----------------- .../Components/AnalogClockWidget.axaml.cs | 2 +- .../Components/BaiduHotSearchWidget.axaml.cs | 14 +- .../BilibiliHotSearchWidget.axaml.cs | 14 +- .../Views/Components/BrowserWidget.axaml.cs | 7 +- .../Components/ClassScheduleWidget.axaml.cs | 8 +- .../Components/CnrDailyNewsWidget.axaml.cs | 22 +- .../ComponentChromeCornerRadiusHelper.cs | 24 -- .../Components/DailyArtworkWidget.axaml.cs | 29 +- .../Components/DailyPoetryWidget.axaml.cs | 30 +- .../Components/DailyWord2x2Widget.axaml.cs | 128 +++--- .../Views/Components/DailyWordWidget.axaml.cs | 176 ++++---- .../Views/Components/DateWidget.axaml.cs | 59 +-- .../ExchangeRateCalculatorWidget.axaml.cs | 2 +- .../Components/HolidayCalendarWidget.axaml | 15 +- .../Components/HolidayCalendarWidget.axaml.cs | 200 ++++----- .../Views/Components/IfengNewsWidget.axaml.cs | 14 +- .../Components/LunarCalendarWidget.axaml | 24 +- .../Components/LunarCalendarWidget.axaml.cs | 120 ++---- .../Components/MonthCalendarWidget.axaml.cs | 59 +-- .../Views/Components/MusicControlWidget.axaml | 8 +- .../Components/MusicControlWidget.axaml.cs | 279 +++++-------- .../OfficeRecentDocumentsWidget.axaml | 66 +-- .../OfficeRecentDocumentsWidget.axaml.cs | 51 +-- .../Views/Components/RecordingWidget.axaml.cs | 6 +- .../Components/Stcn24ForumWidget.axaml.cs | 22 +- .../StudyDeductionReasonsWidget.axaml.cs | 8 +- .../StudyEnvironmentWidget.axaml.cs | 4 +- .../StudyInterruptDensityWidget.axaml.cs | 8 +- .../Components/StudyNoiseCurveWidget.axaml.cs | 4 +- .../StudyNoiseDistributionWidget.axaml.cs | 4 +- .../StudyScoreOverviewWidget.axaml.cs | 8 +- .../StudySessionControlWidget.axaml.cs | 4 +- .../StudySessionHistoryWidget.axaml.cs | 18 +- .../Views/Components/TimerWidget.axaml.cs | 3 +- .../Components/WorldClockWidget.axaml.cs | 2 +- 40 files changed, 573 insertions(+), 1668 deletions(-) delete mode 100644 LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutModels.cs delete mode 100644 LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutService.cs diff --git a/Directory.Build.props b/Directory.Build.props index 3a012b7..327b321 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,6 +4,5 @@ net10.0 enable enable - $(DefaultItemExcludes);**\obj_audit\** diff --git a/LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutModels.cs b/LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutModels.cs deleted file mode 100644 index e9acce4..0000000 --- a/LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutModels.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Avalonia; -using Avalonia.Media; - -namespace LanMountainDesktop.DesktopComponents.Runtime; - -public readonly record struct ComponentAdaptiveTextLayout( - double FontSize, - FontWeight Weight, - int MaxLines, - double LineHeight, - double OverflowScore, - bool FitsCompletely, - Size MeasuredSize) -{ - public double MeasuredWidth => MeasuredSize.Width; - - public double MeasuredHeight => MeasuredSize.Height; -} - -public readonly record struct ComponentBoxLayout( - double Width, - double Height, - Thickness Margin, - Thickness Padding) -{ - public double Size => Math.Max(Width, Height); - - public bool IsSquare => Math.Abs(Width - Height) <= 0.001d; -} - diff --git a/LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutService.cs b/LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutService.cs deleted file mode 100644 index a8da07a..0000000 --- a/LanMountainDesktop.DesktopComponents.Runtime/ComponentTypographyLayoutService.cs +++ /dev/null @@ -1,314 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Media; -using System.Text; - -namespace LanMountainDesktop.DesktopComponents.Runtime; - -public static class ComponentTypographyLayoutService -{ - public static Size MeasureTextSize( - string? text, - double fontSize, - FontWeight weight, - double maxWidth, - double lineHeight, - FontFamily? fontFamily = null) - { - var probe = new TextBlock - { - Text = NormalizeText(text), - FontSize = Math.Max(1, fontSize), - FontWeight = weight, - TextWrapping = TextWrapping.Wrap, - LineHeight = Math.Max(1, lineHeight) - }; - - if (fontFamily is not null) - { - probe.FontFamily = fontFamily; - } - - probe.Measure(new Size(Math.Max(1, maxWidth), double.PositiveInfinity)); - return probe.DesiredSize; - } - - public static double FitFontSize( - string? text, - double maxWidth, - double maxHeight, - int maxLines, - double minFontSize, - double maxFontSize, - FontWeight weight, - double lineHeightFactor, - FontFamily? fontFamily = null) - { - var content = NormalizeText(text); - 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, fontFamily); - var lineCount = ResolveLineCount(size.Height, lineHeight); - var fits = size.Height <= maxHeight + 0.6d && lineCount <= Math.Max(1, maxLines); - - if (fits) - { - best = candidate; - low = candidate; - } - else - { - high = candidate; - } - } - - return best; - } - - public static ComponentAdaptiveTextLayout FitAdaptiveTextLayout( - string? text, - double maxWidth, - double maxHeight, - int minLines, - int maxLines, - double minFontSize, - double maxFontSize, - IEnumerable? weightCandidates = null, - double lineHeightFactor = 1.1d, - FontFamily? fontFamily = null) - { - var content = NormalizeText(text); - var safeMinLines = Math.Max(1, minLines); - var safeMaxLines = Math.Max(safeMinLines, maxLines); - var linesByHeight = ResolveMaxLinesByHeight(maxHeight, minFontSize, lineHeightFactor, safeMinLines, safeMaxLines); - - var candidates = weightCandidates?.ToArray(); - if (candidates is null || candidates.Length == 0) - { - candidates = new[] { FontWeight.Normal }; - } - - ComponentAdaptiveTextLayout? 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, - fontFamily); - - var lineHeight = fontSize * lineHeightFactor; - var measuredSize = MeasureTextSize(content, fontSize, weight, Math.Max(1, maxWidth), lineHeight, fontFamily); - 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 candidate = new ComponentAdaptiveTextLayout( - fontSize, - weight, - lineLimit, - lineHeight, - overflowScore, - overflowLines == 0 && overflowHeight <= 0.6d, - measuredSize); - - 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 ComponentAdaptiveTextLayout( - fallbackFontSize, - FontWeight.Normal, - safeMinLines, - fallbackFontSize * lineHeightFactor, - double.MaxValue, - false, - MeasureTextSize(content, fallbackFontSize, FontWeight.Normal, Math.Max(1, maxWidth), fallbackFontSize * lineHeightFactor, fontFamily)); - } - - public 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.6d); - var linesByHeight = (int)Math.Floor(maxHeightWithTolerance / lineHeight); - return Math.Clamp(linesByHeight, safeMinLines, safeMaxLines); - } - - public static int ResolveLineCount(double measuredHeight, double lineHeight) - { - return Math.Max(1, (int)Math.Ceiling(measuredHeight / Math.Max(1, lineHeight))); - } - - public static int EstimateDisplayUnits( - double availableWidth, - double unitWidth, - double gapWidth = 0, - double reservedWidth = 0, - int minUnits = 1, - int maxUnits = int.MaxValue) - { - var safeMinUnits = Math.Max(1, minUnits); - var safeMaxUnits = Math.Max(safeMinUnits, maxUnits); - var usableWidth = Math.Max(0, availableWidth - reservedWidth); - var safeGapWidth = Math.Max(0, gapWidth); - var raw = safeGapWidth > 0 - ? (usableWidth + safeGapWidth) / Math.Max(1, unitWidth + safeGapWidth) - : usableWidth / Math.Max(1, unitWidth); - - return Math.Clamp((int)Math.Floor(raw), safeMinUnits, safeMaxUnits); - } - - public static int CountTextDisplayUnits(string? text) - { - if (string.IsNullOrWhiteSpace(text)) - { - return 0; - } - - var total = 0; - foreach (var rune in text.EnumerateRunes()) - { - if (Rune.IsWhiteSpace(rune)) - { - continue; - } - - total += IsCjkRune(rune) ? 2 : 1; - } - - return total; - } - - public static ComponentBoxLayout ResolveBadgeBox( - double availableWidth, - double availableHeight, - double preferredSizeScale = 0.42d, - double minSize = 10, - double maxSize = 24, - double insetScale = 0.2d) - { - var edge = Math.Min(Math.Max(1, availableWidth), Math.Max(1, availableHeight)); - var size = Math.Clamp(edge * preferredSizeScale, minSize, maxSize); - var inset = Math.Clamp(size * insetScale, 0, size * 0.35d); - return new ComponentBoxLayout(size, size, new Thickness(0, inset, 0, 0), new Thickness(inset)); - } - - public static ComponentBoxLayout ResolveGlyphBox( - double availableWidth, - double availableHeight, - double preferredSizeScale = 0.50d, - double minSize = 12, - double maxSize = 28, - double insetScale = 0.18d) - { - var edge = Math.Min(Math.Max(1, availableWidth), Math.Max(1, availableHeight)); - var size = Math.Clamp(edge * preferredSizeScale, minSize, maxSize); - var inset = Math.Clamp(size * insetScale, 0, size * 0.30d); - return new ComponentBoxLayout(size, size, new Thickness(inset), new Thickness(inset)); - } - - private static string NormalizeText(string? text) - { - if (string.IsNullOrWhiteSpace(text)) - { - return " "; - } - - return string.Join(" ", text.Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)); - } - - private static bool IsCjkRune(Rune rune) - { - var value = rune.Value; - return (value >= 0x4E00 && value <= 0x9FFF) || // CJK Unified Ideographs - (value >= 0x3400 && value <= 0x4DBF) || // CJK Unified Ideographs Extension A - (value >= 0x20000 && value <= 0x2A6DF) || // CJK Unified Ideographs Extension B - (value >= 0x2A700 && value <= 0x2B73F) || // CJK Unified Ideographs Extension C - (value >= 0x2B740 && value <= 0x2B81F) || // CJK Unified Ideographs Extension D - (value >= 0x2B820 && value <= 0x2CEAF) || // CJK Unified Ideographs Extension E/F - (value >= 0xF900 && value <= 0xFAFF) || // CJK Compatibility Ideographs - (value >= 0x2F800 && value <= 0x2FA1F) || // CJK Compatibility Ideographs Supplement - (value >= 0x3040 && value <= 0x309F) || // Hiragana - (value >= 0x30A0 && value <= 0x30FF) || // Katakana - (value >= 0xAC00 && value <= 0xD7AF); // Hangul Syllables - } - - private static bool IsBetterAdaptiveTextCandidate(ComponentAdaptiveTextLayout candidate, ComponentAdaptiveTextLayout 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.12d) - { - return true; - } - - if (Math.Abs(candidate.FontSize - best.FontSize) <= 0.12d && candidate.MaxLines < best.MaxLines) - { - return true; - } - - return false; - } - - if (candidate.OverflowScore < best.OverflowScore - 0.2d) - { - return true; - } - - if (Math.Abs(candidate.OverflowScore - best.OverflowScore) <= 0.2d && - candidate.FontSize > best.FontSize + 0.12d) - { - return true; - } - - if (Math.Abs(candidate.OverflowScore - best.OverflowScore) <= 0.2d && - Math.Abs(candidate.FontSize - best.FontSize) <= 0.12d && - candidate.MaxLines > best.MaxLines) - { - return true; - } - - return false; - } -} diff --git a/LanMountainDesktop/Services/IMusicControlService.cs b/LanMountainDesktop/Services/IMusicControlService.cs index 91cbc28..307642b 100644 --- a/LanMountainDesktop/Services/IMusicControlService.cs +++ b/LanMountainDesktop/Services/IMusicControlService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -29,9 +28,7 @@ public sealed record MusicPlaybackState( MusicPlaybackStatus PlaybackStatus, bool CanPlayPause, bool CanSkipPrevious, - bool CanSkipNext, - bool CanToggleFavorite, - bool IsFavorite) + bool CanSkipNext) { public static MusicPlaybackState Unsupported() { @@ -49,9 +46,7 @@ public sealed record MusicPlaybackState( PlaybackStatus: MusicPlaybackStatus.Unknown, CanPlayPause: false, CanSkipPrevious: false, - CanSkipNext: false, - CanToggleFavorite: false, - IsFavorite: false); + CanSkipNext: false); } public static MusicPlaybackState NoSession(bool isSupported = true) @@ -70,35 +65,7 @@ public sealed record MusicPlaybackState( PlaybackStatus: MusicPlaybackStatus.Unknown, CanPlayPause: false, CanSkipPrevious: false, - CanSkipNext: false, - CanToggleFavorite: false, - IsFavorite: false); - } -} - -public sealed record MusicQueueItem( - string Id, - string Title, - string Artist, - string AlbumTitle, - byte[]? ThumbnailBytes, - TimeSpan Duration, - bool IsCurrentItem); - -public sealed record MusicQueueState( - bool IsSupported, - IReadOnlyList Items, - int CurrentIndex, - bool HasMoreItems) -{ - public static MusicQueueState Unsupported() - { - return new MusicQueueState(false, Array.Empty(), -1, false); - } - - public static MusicQueueState Empty() - { - return new MusicQueueState(true, Array.Empty(), -1, false); + CanSkipNext: false); } } @@ -113,18 +80,6 @@ public interface IMusicControlService Task SkipPreviousAsync(CancellationToken cancellationToken = default); Task LaunchSourceAppAsync(CancellationToken cancellationToken = default); - - Task ToggleFavoriteAsync(CancellationToken cancellationToken = default); - - Task GetPlaybackQueueAsync(int maxItems = 20, CancellationToken cancellationToken = default); - - event EventHandler? PlaybackStateChanged; - - event EventHandler? QueueChanged; - - void StartListening(); - - void StopListening(); } public static class MusicControlServiceFactory @@ -163,25 +118,4 @@ internal sealed class NoOpMusicControlService : IMusicControlService { return Task.FromResult(false); } - - public Task ToggleFavoriteAsync(CancellationToken cancellationToken = default) - { - return Task.FromResult(false); - } - - public Task GetPlaybackQueueAsync(int maxItems = 20, CancellationToken cancellationToken = default) - { - return Task.FromResult(MusicQueueState.Unsupported()); - } - - public event EventHandler? PlaybackStateChanged; - public event EventHandler? QueueChanged; - - public void StartListening() - { - } - - public void StopListening() - { - } } diff --git a/LanMountainDesktop/Services/WindowsSmtcMusicControlService.cs b/LanMountainDesktop/Services/WindowsSmtcMusicControlService.cs index 88d5290..bdf5b54 100644 --- a/LanMountainDesktop/Services/WindowsSmtcMusicControlService.cs +++ b/LanMountainDesktop/Services/WindowsSmtcMusicControlService.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -10,9 +9,8 @@ using System.Threading.Tasks; namespace LanMountainDesktop.Services; -public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisposable +public sealed class WindowsSmtcMusicControlService : IMusicControlService { - // WinRT Type Resolution private static readonly Type? SessionManagerType = ResolveWinRtType("Windows.Media.Control.GlobalSystemMediaTransportControlsSessionManager"); private static readonly Type? AppInfoType = ResolveWinRtType("Windows.ApplicationModel.AppInfo"); private static readonly MethodInfo? RequestSessionManagerAsyncMethod = @@ -20,250 +18,15 @@ public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisp private static readonly MethodInfo? AsTaskGenericMethodDefinition = ResolveAsTaskGenericMethod(); private static readonly MethodInfo? AsStreamForReadMethod = ResolveAsStreamForReadMethod(); - // Synchronization private static readonly SemaphoreSlim ManagerLock = new(1, 1); private static object? _sessionManager; - // Instance State private readonly ConcurrentDictionary _sourceAppNameCache = new(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _stateGate = new(1, 1); - private readonly object _sessionLock = new(); - // Event State - private object? _currentSession; - private bool _isListening; - private readonly List _eventHandlers = new(); - - // Thumbnail Cache private string _thumbnailKey = string.Empty; private byte[]? _thumbnailBytesCache; - // Events - public event EventHandler? PlaybackStateChanged; - public event EventHandler? QueueChanged; - - public void StartListening() - { - if (_isListening || !IsRuntimeSupported()) - { - return; - } - - _isListening = true; - _ = InitializeSessionManagerAsync(); - } - - public void StopListening() - { - if (!_isListening) - { - return; - } - - _isListening = false; - UnsubscribeFromSessionEvents(); - } - - private async Task InitializeSessionManagerAsync() - { - try - { - var manager = await GetSessionManagerAsync(CancellationToken.None); - if (manager is null) - { - return; - } - - // Subscribe to CurrentSessionChanged event - var currentSessionChangedEvent = SessionManagerType?.GetEvent("CurrentSessionChanged"); - if (currentSessionChangedEvent is not null) - { - var handler = CreateTypedEventHandler( - currentSessionChangedEvent.EventHandlerType, - OnCurrentSessionChanged); - currentSessionChangedEvent.AddEventHandler(manager, handler); - _eventHandlers.Add(handler); - } - - // Get initial session and subscribe to its events - await UpdateCurrentSessionAsync(); - } - catch (Exception ex) - { - AppLogger.Warn("MusicControl", "Failed to initialize SMTC session manager", ex); - } - } - - private async Task OnCurrentSessionChanged(object? sender, object? args) - { - await UpdateCurrentSessionAsync(); - await RaisePlaybackStateChangedAsync(); - } - - private async Task UpdateCurrentSessionAsync() - { - lock (_sessionLock) - { - UnsubscribeFromSessionEvents(); - _currentSession = null; - } - - var session = await GetCurrentSessionAsync(CancellationToken.None); - if (session is null) - { - return; - } - - lock (_sessionLock) - { - _currentSession = session; - SubscribeToSessionEvents(session); - } - } - - private void SubscribeToSessionEvents(object session) - { - if (!_isListening) - { - return; - } - - try - { - // MediaPropertiesChanged event - var mediaPropertiesChanged = session.GetType().GetEvent("MediaPropertiesChanged"); - if (mediaPropertiesChanged is not null) - { - var handler = CreateTypedEventHandler( - mediaPropertiesChanged.EventHandlerType, - async (s, e) => await RaisePlaybackStateChangedAsync()); - mediaPropertiesChanged.AddEventHandler(session, handler); - _eventHandlers.Add(handler); - } - - // PlaybackInfoChanged event - var playbackInfoChanged = session.GetType().GetEvent("PlaybackInfoChanged"); - if (playbackInfoChanged is not null) - { - var handler = CreateTypedEventHandler( - playbackInfoChanged.EventHandlerType, - async (s, e) => await RaisePlaybackStateChangedAsync()); - playbackInfoChanged.AddEventHandler(session, handler); - _eventHandlers.Add(handler); - } - - // TimelinePropertiesChanged event - var timelinePropertiesChanged = session.GetType().GetEvent("TimelinePropertiesChanged"); - if (timelinePropertiesChanged is not null) - { - var handler = CreateTypedEventHandler( - timelinePropertiesChanged.EventHandlerType, - async (s, e) => await RaisePlaybackStateChangedAsync()); - timelinePropertiesChanged.AddEventHandler(session, handler); - _eventHandlers.Add(handler); - } - } - catch (Exception ex) - { - AppLogger.Warn("MusicControl", "Failed to subscribe to session events", ex); - } - } - - private void UnsubscribeFromSessionEvents() - { - if (_currentSession is null) - { - return; - } - - try - { - var sessionType = _currentSession.GetType(); - - // Remove MediaPropertiesChanged - var mediaPropertiesChanged = sessionType.GetEvent("MediaPropertiesChanged"); - if (mediaPropertiesChanged is not null) - { - foreach (var handler in _eventHandlers) - { - try - { - mediaPropertiesChanged.RemoveEventHandler(_currentSession, handler); - } - catch { } - } - } - - // Remove PlaybackInfoChanged - var playbackInfoChanged = sessionType.GetEvent("PlaybackInfoChanged"); - if (playbackInfoChanged is not null) - { - foreach (var handler in _eventHandlers) - { - try - { - playbackInfoChanged.RemoveEventHandler(_currentSession, handler); - } - catch { } - } - } - - // Remove TimelinePropertiesChanged - var timelinePropertiesChanged = sessionType.GetEvent("TimelinePropertiesChanged"); - if (timelinePropertiesChanged is not null) - { - foreach (var handler in _eventHandlers) - { - try - { - timelinePropertiesChanged.RemoveEventHandler(_currentSession, handler); - } - catch { } - } - } - } - catch { } - - _eventHandlers.Clear(); - } - - private Delegate CreateTypedEventHandler(Type eventHandlerType, Func asyncAction) - { - // Create a delegate that wraps the async action - var handler = new EventHandler((sender, args) => - { - _ = asyncAction(sender, args); - }); - - return handler; - } - - private async Task RaisePlaybackStateChangedAsync() - { - try - { - var state = await GetCurrentStateAsync(CancellationToken.None); - PlaybackStateChanged?.Invoke(this, state); - } - catch (Exception ex) - { - AppLogger.Warn("MusicControl", "Failed to raise playback state changed event", ex); - } - } - - private async Task RaiseQueueChangedAsync() - { - try - { - var queue = await GetPlaybackQueueAsync(20, CancellationToken.None); - QueueChanged?.Invoke(this, queue); - } - catch (Exception ex) - { - AppLogger.Warn("MusicControl", "Failed to raise queue changed event", ex); - } - } - public async Task GetCurrentStateAsync(CancellationToken cancellationToken = default) { if (!IsRuntimeSupported()) @@ -293,17 +56,10 @@ public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisp var canSkipNext = ReadBoolProperty(controls, "IsNextEnabled"); var canSkipPrevious = ReadBoolProperty(controls, "IsPreviousEnabled"); - // Check for AutoRepeatModeChange and ShuffleEnabledChange support (indicates advanced SMTC) - var canToggleFavorite = ReadBoolProperty(controls, "IsChannelDownEnabled") || ReadBoolProperty(controls, "IsChannelUpEnabled"); - - // Try to get IsFavorite from mediaProperties (some apps support this) - var isFavorite = ReadBoolProperty(mediaProperties, "IsFavorite"); - var sourceAppId = ReadStringProperty(session, "SourceAppUserModelId"); var sourceAppName = await ResolveSourceAppDisplayNameAsync(sourceAppId, cancellationToken); - // Use async method to get timeline properties - var timeline = await TryGetTimelinePropertiesAsync(session, cancellationToken); + var timeline = InvokeMethod(session, "GetTimelineProperties"); var position = ReadTimeSpanProperty(timeline, "Position"); var start = ReadTimeSpanProperty(timeline, "StartTime"); var end = ReadTimeSpanProperty(timeline, "EndTime"); @@ -347,9 +103,7 @@ public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisp PlaybackStatus: MapPlaybackStatus(playbackStatusRaw), CanPlayPause: canPlayPause, CanSkipPrevious: canSkipPrevious, - CanSkipNext: canSkipNext, - CanToggleFavorite: canToggleFavorite, - IsFavorite: isFavorite); + CanSkipNext: canSkipNext); } catch { @@ -445,113 +199,6 @@ public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisp return await AwaitBooleanWinRtOperationAsync(InvokeMethod(session, "TrySkipPreviousAsync"), cancellationToken); } - public async Task ToggleFavoriteAsync(CancellationToken cancellationToken = default) - { - if (!IsRuntimeSupported()) - { - return false; - } - - var session = await GetCurrentSessionAsync(cancellationToken); - if (session is null) - { - return false; - } - - // Try to toggle favorite using RateAndReview (some apps support this) - try - { - var playbackInfo = GetPropertyValue(session, "PlaybackInfo") ?? InvokeMethod(session, "GetPlaybackInfo"); - var controls = GetPropertyValue(playbackInfo, "Controls"); - - // Check if RateAndReview is supported - if (ReadBoolProperty(controls, "IsRateEnabled")) - { - var operation = InvokeMethod(session, "TryRateAsync"); - return await AwaitBooleanWinRtOperationAsync(operation, cancellationToken); - } - - // Fallback: Try ChannelUp/ChannelDown as favorite toggle - if (ReadBoolProperty(controls, "IsChannelUpEnabled")) - { - var operation = InvokeMethod(session, "TryChannelUpAsync"); - return await AwaitBooleanWinRtOperationAsync(operation, cancellationToken); - } - } - catch { } - - return false; - } - - public async Task GetPlaybackQueueAsync(int maxItems = 20, CancellationToken cancellationToken = default) - { - if (!IsRuntimeSupported()) - { - return MusicQueueState.Unsupported(); - } - - var session = await GetCurrentSessionAsync(cancellationToken); - if (session is null) - { - return MusicQueueState.Empty(); - } - - try - { - // Try to get playback queue using GetPlaybackInfo - var playbackInfo = GetPropertyValue(session, "PlaybackInfo") ?? InvokeMethod(session, "GetPlaybackInfo"); - - // Check if shuffle/repeat controls exist (indicates queue support) - var controls = GetPropertyValue(playbackInfo, "Controls"); - var canShuffle = ReadBoolProperty(controls, "IsShuffleEnabled"); - var canRepeat = ReadBoolProperty(controls, "IsRepeatEnabled"); - - // Since SMTC doesn't expose the actual queue directly, we'll return a simplified state - // indicating whether queue navigation is supported - var items = new List(); - - // Try to get current media properties as the current item - var mediaProperties = await TryGetMediaPropertiesAsync(session, cancellationToken); - if (mediaProperties is not null) - { - var title = ReadStringProperty(mediaProperties, "Title"); - var artist = ReadStringProperty(mediaProperties, "Artist"); - var albumTitle = ReadStringProperty(mediaProperties, "AlbumTitle"); - var thumbnailBytes = await ResolveThumbnailBytesAsync( - mediaProperties, - ReadStringProperty(session, "SourceAppUserModelId"), - title, artist, albumTitle, - cancellationToken); - - // Get duration - var timeline = await TryGetTimelinePropertiesAsync(session, cancellationToken); - var duration = ReadTimeSpanProperty(timeline, "EndTime") - ReadTimeSpanProperty(timeline, "StartTime"); - - items.Add(new MusicQueueItem( - Id: "current", - Title: title, - Artist: artist, - AlbumTitle: albumTitle, - ThumbnailBytes: thumbnailBytes, - Duration: duration > TimeSpan.Zero ? duration : TimeSpan.Zero, - IsCurrentItem: true)); - } - - // If shuffle or repeat is supported, we assume there's a queue - var hasMoreItems = canShuffle || canRepeat || ReadBoolProperty(controls, "IsNextEnabled"); - - return new MusicQueueState( - IsSupported: true, - Items: items, - CurrentIndex: 0, - HasMoreItems: hasMoreItems); - } - catch - { - return MusicQueueState.Empty(); - } - } - public async Task LaunchSourceAppAsync(CancellationToken cancellationToken = default) { if (!IsRuntimeSupported()) @@ -612,20 +259,6 @@ public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisp return await AwaitWinRtOperationAsync(operation, cancellationToken); } - private async Task TryGetTimelinePropertiesAsync(object session, CancellationToken cancellationToken) - { - // Use the async method TryGetTimelinePropertiesAsync if available - var tryGetTimelineMethod = session.GetType().GetMethod("TryGetTimelinePropertiesAsync"); - if (tryGetTimelineMethod is not null) - { - var operation = tryGetTimelineMethod.Invoke(session, null); - return await AwaitWinRtOperationAsync(operation, cancellationToken); - } - - // Fallback to synchronous method - return InvokeMethod(session, "GetTimelineProperties"); - } - private async Task ResolveThumbnailBytesAsync( object? mediaProperties, string sourceAppId, @@ -943,11 +576,4 @@ public sealed class WindowsSmtcMusicControlService : IMusicControlService, IDisp _ => MusicPlaybackStatus.Unknown }; } - - public void Dispose() - { - StopListening(); - _stateGate.Dispose(); - ManagerLock.Dispose(); - } } diff --git a/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs b/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs index 30e769b..27a2137 100644 --- a/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/AnalogClockWidget.axaml.cs @@ -327,7 +327,7 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(42 * scale, 16, 56); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(14 * scale, 14 * scale, null, 0.55d); + RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 8, 26)); ApplyModeVisualIfNeeded(); } diff --git a/LanMountainDesktop/Views/Components/BaiduHotSearchWidget.axaml.cs b/LanMountainDesktop/Views/Components/BaiduHotSearchWidget.axaml.cs index d667c91..e9187e8 100644 --- a/LanMountainDesktop/Views/Components/BaiduHotSearchWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/BaiduHotSearchWidget.axaml.cs @@ -382,21 +382,15 @@ public partial class BaiduHotSearchWidget : UserControl, IDesktopComponentWidget var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 10 * softScale, - 8 * softScale, - null, - 0.45d); + RootBorder.Padding = new Thickness(0); var horizontalPadding = Math.Clamp(16 * softScale, 8, 24); var verticalPadding = Math.Clamp(14 * softScale, 7, 20); CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52); - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(horizontalPadding, verticalPadding, null, 0.55d); + CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); - var rootPadding = RootBorder.Padding; - var cardPadding = CardBorder.Padding; - var innerWidth = Math.Max(120, totalWidth - rootPadding.Left - rootPadding.Right - cardPadding.Left - cardPadding.Right); - var innerHeight = Math.Max(72, totalHeight - rootPadding.Top - rootPadding.Bottom - cardPadding.Top - cardPadding.Bottom); + var innerWidth = Math.Max(120, totalWidth - (horizontalPadding * 2d)); + var innerHeight = Math.Max(72, totalHeight - (verticalPadding * 2d)); var rowSpacing = Math.Clamp(6 * softScale, 2, 9); ContentGrid.RowSpacing = rowSpacing; HeaderGrid.ColumnSpacing = Math.Clamp(10 * softScale, 6, 16); diff --git a/LanMountainDesktop/Views/Components/BilibiliHotSearchWidget.axaml.cs b/LanMountainDesktop/Views/Components/BilibiliHotSearchWidget.axaml.cs index 1940596..c0f800f 100644 --- a/LanMountainDesktop/Views/Components/BilibiliHotSearchWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/BilibiliHotSearchWidget.axaml.cs @@ -387,21 +387,15 @@ public partial class BilibiliHotSearchWidget : UserControl, IDesktopComponentWid var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 10 * softScale, - 8 * softScale, - null, - 0.45d); + RootBorder.Padding = new Thickness(0); var horizontalPadding = Math.Clamp(16 * softScale, 8, 24); var verticalPadding = Math.Clamp(14 * softScale, 7, 20); CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * softScale, 16, 52); - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(horizontalPadding, verticalPadding, null, 0.55d); + CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); - var rootPadding = RootBorder.Padding; - var cardPadding = CardBorder.Padding; - var innerWidth = Math.Max(120, totalWidth - rootPadding.Left - rootPadding.Right - cardPadding.Left - cardPadding.Right); - var innerHeight = Math.Max(72, totalHeight - rootPadding.Top - rootPadding.Bottom - cardPadding.Top - cardPadding.Bottom); + var innerWidth = Math.Max(120, totalWidth - (horizontalPadding * 2d)); + var innerHeight = Math.Max(72, totalHeight - (verticalPadding * 2d)); var rowSpacing = Math.Clamp(6 * softScale, 2, 9); ContentGrid.RowSpacing = rowSpacing; HeaderGrid.ColumnSpacing = Math.Clamp(10 * softScale, 6, 16); diff --git a/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs b/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs index b552955..591f2d9 100644 --- a/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/BrowserWidget.axaml.cs @@ -80,14 +80,11 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget, _currentCellSize = Math.Max(1, cellSize); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 12, 28); - RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(_currentCellSize * 0.20, 8, 18)); + RootBorder.Padding = new Thickness(Math.Clamp(_currentCellSize * 0.20, 8, 18)); WebViewHostBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.24, 10, 22); AddressBarBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.22, 10, 20); - AddressBarBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(8, 6, 12), - ComponentChromeCornerRadiusHelper.SafeValue(6, 4, 10)); + AddressBarBorder.Padding = new Thickness(8, 6); if (RootBorder.Child is Grid rootGrid) { diff --git a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs index 0108e4e..06d24a3 100644 --- a/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/ClassScheduleWidget.axaml.cs @@ -500,10 +500,10 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, var secondarySize = Math.Clamp(29 * scale, 10, 28); var lineSpacing = Math.Clamp(4 * scale, 1.5, 8); var itemPadding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(6 * scale, 3, 10), - ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8), - ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8), - ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8)); + Math.Clamp(6 * scale, 3, 10), + Math.Clamp(4 * scale, 2, 8), + Math.Clamp(4 * scale, 2, 8), + Math.Clamp(4 * scale, 2, 8)); var maxVisibleItems = ResolveMaxVisibleItems(scale); var primaryBrush = CreateBrush(_isNightVisual ? "#F9FBFF" : "#151821"); diff --git a/LanMountainDesktop/Views/Components/CnrDailyNewsWidget.axaml.cs b/LanMountainDesktop/Views/Components/CnrDailyNewsWidget.axaml.cs index 678cc54..d16b1c1 100644 --- a/LanMountainDesktop/Views/Components/CnrDailyNewsWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/CnrDailyNewsWidget.axaml.cs @@ -546,24 +546,14 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget, var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 10 * scale, - 8 * scale, - null, - 0.45d); + RootBorder.Padding = new Thickness(0); CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52); - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( + CardBorder.Padding = new Thickness( Math.Clamp(16 * scale, 8, 24), Math.Clamp(14 * scale, 7, 22), - null, - 0.55d); - - var rootPadding = RootBorder.Padding; - var cardPadding = CardBorder.Padding; - var contentWidth = Math.Max( - 150, - totalWidth - rootPadding.Left - rootPadding.Right - cardPadding.Left - cardPadding.Right); + Math.Clamp(16 * scale, 8, 24), + Math.Clamp(14 * scale, 7, 22)); var headlineFont = Math.Clamp(24 * scale, 12, 34); BrandPrimaryTextBlock.FontSize = headlineFont; @@ -577,7 +567,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget, RefreshGlyphIcon.FontSize = Math.Clamp(19 * scale, 11, 24); RefreshLabelTextBlock.FontSize = Math.Clamp(22 * scale, 11, 29); - var imageWidth = Math.Clamp(contentWidth * 0.20, 60, 170); + var imageWidth = Math.Clamp(totalWidth * 0.20, 60, 170); var imageHeight = Math.Clamp(imageWidth * 0.56, 38, 94); News1ImageHost.Width = imageWidth; News1ImageHost.Height = imageHeight; @@ -594,7 +584,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget, var availableTextWidth = Math.Max( 84, - contentWidth - imageWidth - columnGap - Math.Clamp(20 * scale, 10, 32)); + totalWidth - imageWidth - columnGap - Math.Clamp(20 * scale, 10, 32)); News1TitleTextBlock.MaxWidth = availableTextWidth; News2TitleTextBlock.MaxWidth = availableTextWidth; diff --git a/LanMountainDesktop/Views/Components/ComponentChromeCornerRadiusHelper.cs b/LanMountainDesktop/Views/Components/ComponentChromeCornerRadiusHelper.cs index 0a2b0c9..355ab95 100644 --- a/LanMountainDesktop/Views/Components/ComponentChromeCornerRadiusHelper.cs +++ b/LanMountainDesktop/Views/Components/ComponentChromeCornerRadiusHelper.cs @@ -69,28 +69,4 @@ internal static class ComponentChromeCornerRadiusHelper var safetyScale = ResolveContentSafetyScale(chromeContext, responsiveness); return Math.Clamp(baseValue * safetyScale, min * safetyScale, max * safetyScale); } - - public static Thickness SafeThickness( - double left, - double top, - double right, - double bottom, - ComponentChromeContext? chromeContext = null, - double responsiveness = 0.45d) - { - return new Thickness( - SafeValue(left, 0, left, chromeContext, responsiveness), - SafeValue(top, 0, top, chromeContext, responsiveness), - SafeValue(right, 0, right, chromeContext, responsiveness), - SafeValue(bottom, 0, bottom, chromeContext, responsiveness)); - } - - public static Thickness SafeThickness( - double horizontal, - double vertical, - ComponentChromeContext? chromeContext = null, - double responsiveness = 0.45d) - { - return SafeThickness(horizontal, vertical, horizontal, vertical, chromeContext, responsiveness); - } } diff --git a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs index 8e59121..e1bb3d3 100644 --- a/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyArtworkWidget.axaml.cs @@ -102,23 +102,18 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 12 * scale, - 10 * scale, - null, - 0.45d); - InfoPanel.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 18 * scale, - 14 * scale, - null, - 0.52d); + InfoPanel.Padding = new Thickness( + Math.Clamp(18 * scale, 10, 28), + Math.Clamp(14 * scale, 8, 22), + Math.Clamp(18 * scale, 10, 28), + Math.Clamp(14 * scale, 8, 22)); DateInfoStack.Margin = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(18 * scale, 8, 30), + Math.Clamp(18 * scale, 8, 30), 0, 0, - ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 8, 26)); + Math.Clamp(16 * scale, 8, 26)); DateInfoStack.Spacing = Math.Clamp(4 * scale, 2, 10); StatusTextBlock.FontSize = Math.Clamp(16 * scale, 10, 24); @@ -430,18 +425,16 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, var scale = ResolveScale(); var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells; var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; - var rootPadding = RootBorder.Padding; var leftStar = totalWidth < _currentCellSize * 4.2 ? 2.0 : 2.08; MainLayoutGrid.ColumnDefinitions[0].Width = new GridLength(leftStar, GridUnitType.Star); MainLayoutGrid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star); - var availableWidth = Math.Max(84, totalWidth - rootPadding.Left - rootPadding.Right); - var rightPanelWidth = Math.Max(84, availableWidth / (leftStar + 1)); + var rightPanelWidth = Math.Max(84, totalWidth / (leftStar + 1)); var rightContentWidth = Math.Max(58, rightPanelWidth - InfoPanel.Padding.Left - InfoPanel.Padding.Right); - var leftPanelWidth = Math.Max(84, availableWidth - rightPanelWidth); + var leftPanelWidth = Math.Max(84, totalWidth - rightPanelWidth); var leftContentWidth = Math.Max(52, leftPanelWidth - DateInfoStack.Margin.Left - 10); - var leftContentHeight = Math.Max(30, totalHeight - rootPadding.Top - rootPadding.Bottom - DateInfoStack.Margin.Bottom - 10); + var leftContentHeight = Math.Max(30, totalHeight - DateInfoStack.Margin.Bottom - 10); var dateStackSpacing = Math.Clamp(4 * scale, 2, 10); DateInfoStack.Spacing = dateStackSpacing; @@ -471,7 +464,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, 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); + 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); diff --git a/LanMountainDesktop/Views/Components/DailyPoetryWidget.axaml.cs b/LanMountainDesktop/Views/Components/DailyPoetryWidget.axaml.cs index 8537497..b03930a 100644 --- a/LanMountainDesktop/Views/Components/DailyPoetryWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyPoetryWidget.axaml.cs @@ -93,25 +93,25 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 20 * scale, - 16 * scale, - null, - 0.55d); + RootBorder.Padding = new Thickness( + Math.Clamp(20 * scale, 10, 34), + Math.Clamp(16 * scale, 8, 28), + Math.Clamp(20 * scale, 10, 34), + Math.Clamp(14 * scale, 7, 24)); QuoteMarkTextBlock.FontSize = Math.Clamp(80 * scale, 32, 120); QuoteMarkTextBlock.LineHeight = Math.Clamp(68 * scale, 26, 100); QuoteMarkTextBlock.Margin = new Thickness(Math.Clamp(1 * scale, 0, 3), 0, 0, 0); PoetryContentTextBlock.Margin = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(8 * scale, 4, 16), - ComponentChromeCornerRadiusHelper.SafeValue(2 * scale, 0, 8), + Math.Clamp(8 * scale, 4, 16), + Math.Clamp(2 * scale, 0, 8), 0, 0); AuthorAccent.Width = Math.Clamp(6 * scale, 3.2, 9.5); AuthorAccent.Height = Math.Clamp(24 * scale, 12, 34); - AuthorAccent.Margin = new Thickness(0, 0, ComponentChromeCornerRadiusHelper.SafeValue(8 * scale, 4, 13), 0); + AuthorAccent.Margin = new Thickness(0, 0, Math.Clamp(8 * scale, 4, 13), 0); AuthorAccent.CornerRadius = new CornerRadius(Math.Clamp(3 * scale, 1.5, 4.5)); StatusTextBlock.FontSize = Math.Clamp(17 * scale, 9, 26); @@ -328,13 +328,15 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I var totalWidth = Bounds.Width > 1 ? Bounds.Width : _currentCellSize * BaseWidthCells; var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; var scale = ResolveScale(); - var rootPadding = isNightMode - ? ComponentChromeCornerRadiusHelper.SafeThickness(20 * scale, 15 * scale, null, 0.55d) - : ComponentChromeCornerRadiusHelper.SafeThickness(20 * scale, 14 * scale, null, 0.55d); if (isNightMode) { RootBorder.Background = CreateBrush("#C5070D"); + RootBorder.Padding = new Thickness( + Math.Clamp(20 * scale, 10, 34), + Math.Clamp(15 * scale, 7, 24), + Math.Clamp(20 * scale, 10, 34), + Math.Clamp(14 * scale, 7, 24)); QuoteMarkTextBlock.IsVisible = true; QuoteMarkTextBlock.Foreground = CreateBrush("#4AF4C5A6"); @@ -358,6 +360,11 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I else { RootBorder.Background = CreateBrush("#F2F2F3"); + RootBorder.Padding = new Thickness( + Math.Clamp(20 * scale, 10, 34), + Math.Clamp(14 * scale, 6, 24), + Math.Clamp(20 * scale, 10, 34), + Math.Clamp(14 * scale, 7, 24)); QuoteMarkTextBlock.IsVisible = false; @@ -378,7 +385,6 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I StatusTextBlock.Foreground = CreateBrush("#8A8F98"); } - RootBorder.Padding = rootPadding; UpdateRefreshButtonState(); ApplyAdaptiveTextLayout(isNightMode, scale, totalWidth, totalHeight); } diff --git a/LanMountainDesktop/Views/Components/DailyWord2x2Widget.axaml.cs b/LanMountainDesktop/Views/Components/DailyWord2x2Widget.axaml.cs index f3596ac..ac45737 100644 --- a/LanMountainDesktop/Views/Components/DailyWord2x2Widget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyWord2x2Widget.axaml.cs @@ -12,7 +12,6 @@ using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; using Avalonia.Threading; -using LanMountainDesktop.DesktopComponents.Runtime; using LanMountainDesktop.Models; using LanMountainDesktop.Services; @@ -330,17 +329,12 @@ public partial class DailyWord2x2Widget : UserControl, IDesktopComponentWidget, var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 14, 40); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 12 * scale, - 10 * scale, - null, - 0.45d); CardBorder.CornerRadius = RootBorder.CornerRadius; - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 12 * scale, - 11 * scale, - null, - 0.55d); + CardBorder.Padding = new Thickness( + Math.Clamp(12 * scale, 8, 18), + Math.Clamp(11 * scale, 7, 16), + Math.Clamp(12 * scale, 8, 18), + Math.Clamp(11 * scale, 7, 16)); var refreshSize = Math.Clamp(30 * scale, 20, 38); RefreshButton.Width = refreshSize; @@ -348,60 +342,44 @@ public partial class DailyWord2x2Widget : UserControl, IDesktopComponentWidget, RefreshButton.CornerRadius = new CornerRadius(refreshSize / 2d); RefreshIcon.FontSize = Math.Clamp(14 * scale, 10, 20); - var contentWidth = Math.Max( - 80, - totalWidth - RootBorder.Padding.Left - RootBorder.Padding.Right - CardBorder.Padding.Left - CardBorder.Padding.Right); + var contentWidth = Math.Max(80, totalWidth - CardBorder.Padding.Left - CardBorder.Padding.Right); var wordWidth = Math.Max(48, contentWidth - refreshSize - Math.Clamp(6 * scale, 4, 10)); WordTextBlock.MaxWidth = wordWidth; - var contentHeight = Math.Max( - 52, - totalHeight - RootBorder.Padding.Top - RootBorder.Padding.Bottom - CardBorder.Padding.Top - CardBorder.Padding.Bottom); + var contentHeight = Math.Max(52, totalHeight - CardBorder.Padding.Top - CardBorder.Padding.Bottom); var wordHeightBudget = Math.Max(18, contentHeight * 0.34); var detailHeightBudget = Math.Max(18, contentHeight - wordHeightBudget - Math.Clamp(8 * scale, 4, 14)); - var wordLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + WordTextBlock.FontSize = FitFontSize( WordTextBlock.Text, wordWidth, wordHeightBudget, - 1, - 1, - Math.Clamp(18 * scale, 12, 22), - Math.Clamp(38 * scale, 20, 50), - [FontWeight.Bold], - 1.02, - MiSansFontFamily); - WordTextBlock.FontSize = wordLayout.FontSize; - WordTextBlock.FontWeight = wordLayout.Weight; - WordTextBlock.MaxLines = wordLayout.MaxLines; - WordTextBlock.TextWrapping = TextWrapping.NoWrap; - WordTextBlock.LineHeight = wordLayout.LineHeight; + maxLines: 1, + minFontSize: Math.Clamp(18 * scale, 12, 22), + maxFontSize: Math.Clamp(38 * scale, 20, 50), + weight: FontWeight.Bold, + lineHeightFactor: 1.02); + WordTextBlock.LineHeight = WordTextBlock.FontSize * 1.02; - var detailLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + var detailFont = FitFontSize( MeaningTextBlock.IsVisible ? MeaningTextBlock.Text : HiddenHintTextBlock.Text, contentWidth, detailHeightBudget, - 1, - MeaningTextBlock.IsVisible ? 5 : 4, - Math.Clamp(12 * scale, 9, 14), - Math.Clamp(18 * scale, 12, 22), - [FontWeight.SemiBold, FontWeight.Medium], - 1.10, - MiSansFontFamily); + maxLines: MeaningTextBlock.IsVisible ? 5 : 4, + minFontSize: Math.Clamp(12 * scale, 9, 14), + maxFontSize: Math.Clamp(18 * scale, 12, 22), + weight: FontWeight.SemiBold, + lineHeightFactor: 1.10); MeaningTextBlock.MaxWidth = contentWidth; - MeaningTextBlock.FontSize = detailLayout.FontSize; - MeaningTextBlock.FontWeight = detailLayout.Weight; - MeaningTextBlock.LineHeight = detailLayout.LineHeight; - MeaningTextBlock.MaxLines = Math.Min(detailLayout.MaxLines, totalHeight < _currentCellSize * 1.8 ? 4 : 5); - MeaningTextBlock.TextWrapping = MeaningTextBlock.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; + MeaningTextBlock.FontSize = detailFont; + MeaningTextBlock.LineHeight = detailFont * 1.10; + MeaningTextBlock.MaxLines = totalHeight < _currentCellSize * 1.8 ? 4 : 5; HiddenHintTextBlock.MaxWidth = contentWidth; - HiddenHintTextBlock.FontSize = detailLayout.FontSize; - HiddenHintTextBlock.FontWeight = detailLayout.Weight; - HiddenHintTextBlock.LineHeight = detailLayout.LineHeight; - HiddenHintTextBlock.MaxLines = Math.Min(detailLayout.MaxLines, totalHeight < _currentCellSize * 1.8 ? 3 : 4); - HiddenHintTextBlock.TextWrapping = HiddenHintTextBlock.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; + HiddenHintTextBlock.FontSize = detailFont; + HiddenHintTextBlock.LineHeight = detailFont * 1.10; + HiddenHintTextBlock.MaxLines = totalHeight < _currentCellSize * 1.8 ? 3 : 4; StatusTextBlock.FontSize = Math.Clamp(14 * scale, 9, 18); } @@ -531,4 +509,58 @@ public partial class DailyWord2x2Widget : 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 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; + } } diff --git a/LanMountainDesktop/Views/Components/DailyWordWidget.axaml.cs b/LanMountainDesktop/Views/Components/DailyWordWidget.axaml.cs index ef466db..9d6389d 100644 --- a/LanMountainDesktop/Views/Components/DailyWordWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DailyWordWidget.axaml.cs @@ -10,7 +10,6 @@ using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; -using LanMountainDesktop.DesktopComponents.Runtime; using LanMountainDesktop.Models; using LanMountainDesktop.Services; @@ -301,18 +300,14 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe var containerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52); RootBorder.CornerRadius = containerRadius; - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 10 * scale, - 8 * scale, - null, - 0.45d); + RootBorder.Padding = new Thickness(0); CardBorder.CornerRadius = containerRadius; - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 16 * scale, - 14 * scale, - null, - 0.55d); + CardBorder.Padding = new Thickness( + Math.Clamp(16 * scale, 8, 24), + Math.Clamp(14 * scale, 7, 22), + Math.Clamp(16 * scale, 8, 24), + Math.Clamp(14 * scale, 7, 22)); var refreshSize = Math.Clamp(38 * scale, 22, 48); RefreshButton.Width = refreshSize; @@ -351,90 +346,65 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe exampleHeightBudget += Math.Clamp(11 * scale, 5, 18); } - var wordLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + var wordBase = Math.Clamp(56 * scale, 18, 72); + WordTextBlock.FontSize = FitFontSize( WordTextBlock.Text, wordWidth, wordHeightBudget, - 1, - 1, - Math.Max(14, Math.Clamp(56 * scale, 18, 72) * 0.56), - Math.Clamp(56 * scale, 18, 72), - [FontWeight.Bold], - 1.04, - MiSansFontFamily); - WordTextBlock.FontSize = wordLayout.FontSize; - WordTextBlock.FontWeight = wordLayout.Weight; - WordTextBlock.MaxLines = wordLayout.MaxLines; - WordTextBlock.TextWrapping = TextWrapping.NoWrap; - WordTextBlock.LineHeight = wordLayout.LineHeight; + maxLines: 1, + minFontSize: Math.Max(14, wordBase * 0.56), + maxFontSize: wordBase, + weight: FontWeight.Bold, + lineHeightFactor: 1.04); + WordTextBlock.LineHeight = WordTextBlock.FontSize * 1.04; - var pronunciationLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + var pronunciationBase = Math.Clamp(27 * scale, 10, 36); + PronunciationTextBlock.FontSize = FitFontSize( PronunciationTextBlock.Text, contentWidth, pronunciationHeightBudget, - 1, - 1, - Math.Max(8.6, Math.Clamp(27 * scale, 10, 36) * 0.62), - Math.Clamp(27 * scale, 10, 36), - [FontWeight.SemiBold, FontWeight.Medium], - 1.08, - MiSansFontFamily); - PronunciationTextBlock.FontSize = pronunciationLayout.FontSize; - PronunciationTextBlock.FontWeight = pronunciationLayout.Weight; - PronunciationTextBlock.MaxLines = pronunciationLayout.MaxLines; - PronunciationTextBlock.TextWrapping = TextWrapping.NoWrap; - PronunciationTextBlock.LineHeight = pronunciationLayout.LineHeight; + maxLines: 1, + minFontSize: Math.Max(8.6, pronunciationBase * 0.62), + maxFontSize: pronunciationBase, + weight: FontWeight.SemiBold, + lineHeightFactor: 1.08); + PronunciationTextBlock.LineHeight = PronunciationTextBlock.FontSize * 1.08; - var meaningLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + var meaningBase = Math.Clamp(25 * scale, 10, 34); + MeaningTextBlock.FontSize = FitFontSize( MeaningTextBlock.Text, contentWidth, meaningHeightBudget, - 1, - Math.Max(1, MeaningTextBlock.MaxLines), - Math.Max(9.2, Math.Clamp(25 * scale, 10, 34) * 0.60), - Math.Clamp(25 * scale, 10, 34), - [FontWeight.SemiBold, FontWeight.Medium], - 1.10, - MiSansFontFamily); - MeaningTextBlock.FontSize = meaningLayout.FontSize; - MeaningTextBlock.FontWeight = meaningLayout.Weight; - MeaningTextBlock.MaxLines = meaningLayout.MaxLines; - MeaningTextBlock.TextWrapping = meaningLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; - MeaningTextBlock.LineHeight = meaningLayout.LineHeight; + maxLines: Math.Max(1, MeaningTextBlock.MaxLines), + minFontSize: Math.Max(9.2, meaningBase * 0.60), + maxFontSize: meaningBase, + weight: FontWeight.SemiBold, + lineHeightFactor: 1.10); + MeaningTextBlock.LineHeight = MeaningTextBlock.FontSize * 1.10; - var exampleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + var exampleBase = Math.Clamp(22 * scale, 9, 30); + ExampleTextBlock.FontSize = FitFontSize( ExampleTextBlock.Text, contentWidth, exampleHeightBudget, - 1, - Math.Max(1, ExampleTextBlock.MaxLines), - Math.Max(8.8, Math.Clamp(22 * scale, 9, 30) * 0.58), - Math.Clamp(22 * scale, 9, 30), - [FontWeight.Medium, FontWeight.SemiBold], - 1.08, - MiSansFontFamily); - ExampleTextBlock.FontSize = exampleLayout.FontSize; - ExampleTextBlock.FontWeight = exampleLayout.Weight; - ExampleTextBlock.MaxLines = exampleLayout.MaxLines; - ExampleTextBlock.TextWrapping = exampleLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; - ExampleTextBlock.LineHeight = exampleLayout.LineHeight; + maxLines: Math.Max(1, ExampleTextBlock.MaxLines), + minFontSize: Math.Max(8.8, exampleBase * 0.58), + maxFontSize: exampleBase, + weight: FontWeight.Medium, + lineHeightFactor: 1.08); + ExampleTextBlock.LineHeight = ExampleTextBlock.FontSize * 1.08; - var translationLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + var translationBase = Math.Clamp(20 * scale, 8, 28); + ExampleTranslationTextBlock.FontSize = FitFontSize( ExampleTranslationTextBlock.Text, contentWidth, Math.Max(10, exampleHeightBudget * 0.44), - 1, - 1, - Math.Max(7.8, Math.Clamp(20 * scale, 8, 28) * 0.62), - Math.Clamp(20 * scale, 8, 28), - [FontWeight.Medium, FontWeight.Normal], - 1.06, - MiSansFontFamily); - ExampleTranslationTextBlock.FontSize = translationLayout.FontSize; - ExampleTranslationTextBlock.FontWeight = translationLayout.Weight; - ExampleTranslationTextBlock.MaxLines = translationLayout.MaxLines; - ExampleTranslationTextBlock.TextWrapping = TextWrapping.NoWrap; - ExampleTranslationTextBlock.LineHeight = translationLayout.LineHeight; + maxLines: 1, + minFontSize: Math.Max(7.8, translationBase * 0.62), + maxFontSize: translationBase, + weight: FontWeight.Medium, + lineHeightFactor: 1.06); + ExampleTranslationTextBlock.LineHeight = ExampleTranslationTextBlock.FontSize * 1.06; StatusTextBlock.FontSize = Math.Clamp(16 * scale, 9, 24); } @@ -617,4 +587,58 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe 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 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; + } } diff --git a/LanMountainDesktop/Views/Components/DateWidget.axaml.cs b/LanMountainDesktop/Views/Components/DateWidget.axaml.cs index e5614ec..1315699 100644 --- a/LanMountainDesktop/Views/Components/DateWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/DateWidget.axaml.cs @@ -6,7 +6,6 @@ using Avalonia.Controls; using Avalonia.Media; using Avalonia.Styling; using Avalonia.Threading; -using LanMountainDesktop.DesktopComponents.Runtime; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; @@ -254,36 +253,8 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon // 4x2 widget has less vertical space than 2x2. Compress only on 6-row months. var rowDensity = _calendarVisibleRows >= 6 ? 0.84 : 1.0; - var availableCalendarHeight = Math.Max( - 1, - 220 - - RootBorder.Padding.Top - - RootBorder.Padding.Bottom - - (GregorianHeadlineTextBlock.FontSize * 1.16) - - LeftPanelGrid.RowSpacing - - (_weekdayFontSize * 1.10) - - WeekdayHeaderGrid.Margin.Top - - WeekdayHeaderGrid.Margin.Bottom - - CalendarGrid.Margin.Top - - CalendarGrid.Margin.Bottom); - var calendarCellHeight = availableCalendarHeight / Math.Max(1, _calendarVisibleRows); - var todayBadge = ComponentTypographyLayoutService.ResolveBadgeBox( - calendarCellHeight, - calendarCellHeight, - preferredSizeScale: 0.92d, - minSize: 14, - maxSize: 32, - insetScale: 0.14d); - var todayDotSize = Math.Min(todayBadge.Width, todayBadge.Height); - var todayGlyphBox = ComponentTypographyLayoutService.ResolveGlyphBox( - todayDotSize, - todayDotSize, - preferredSizeScale: 0.74d, - minSize: 8, - maxSize: 20, - insetScale: 0.12d); - var todayGlyphSize = Math.Min(todayGlyphBox.Width, todayGlyphBox.Height); - var dayFontSize = Math.Clamp(Math.Min(_calendarDayFontSize * rowDensity, calendarCellHeight * 0.46), 8, 22); + var dayFontSize = Math.Clamp(_calendarDayFontSize * rowDensity, 8, 24); + var todayDotSize = Math.Clamp(_calendarTodayDotSize * rowDensity, 13.5, 32); for (var day = 1; day <= daysInMonth; day++) { @@ -315,10 +286,6 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon : Brushes.White; dayText.Foreground = onAccentBrush; - dayText.Width = todayGlyphBox.Width; - dayText.Height = todayGlyphBox.Height; - dayText.TextAlignment = TextAlignment.Center; - dayText.LineHeight = todayGlyphSize * 1.03; var dot = new Border { Width = todayDotSize, @@ -359,28 +326,28 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(28 * scale, 16, 40); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(13 * scale, 8 * scale, null, 0.55d); + RootBorder.Padding = new Thickness(Math.Clamp(11 * scale, 7, 17)); LayoutRoot.ColumnSpacing = Math.Clamp(10 * scale, 6, 16); - LeftPanelGrid.RowSpacing = Math.Clamp(6.2 * scale, 3, 11); + LeftPanelGrid.RowSpacing = Math.Clamp(5.2 * scale, 2.5, 10); WeekdayHeaderGrid.Margin = new Thickness( 0, - Math.Clamp(0.8 * scale, 0, 2.5), + Math.Clamp(0.5 * scale, 0, 2), 0, - Math.Clamp(3.0 * scale, 1.5, 4.5)); - CalendarGrid.Margin = new Thickness(0, 0, 0, Math.Clamp(1.2 * scale, 0.5, 2.5)); + Math.Clamp(2.4 * scale, 1, 4)); + CalendarGrid.Margin = new Thickness(0, 0, 0, Math.Clamp(0.8 * scale, 0, 2)); LunarCardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(24 * scale, 14, 34); - LunarCardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(15 * scale, 9 * scale, null, 0.55d); - RightPanelGrid.RowSpacing = Math.Clamp(8.2 * scale, 4, 12); + LunarCardBorder.Padding = new Thickness(Math.Clamp(14 * scale, 8, 20)); + RightPanelGrid.RowSpacing = Math.Clamp(7.5 * scale, 3.5, 11); DividerBorder.Margin = new Thickness(0, Math.Clamp(1 * scale, 0, 2), 0, Math.Clamp(1 * scale, 0, 2)); var isZh = CultureInfo.CurrentCulture.TwoLetterISOLanguageName.Equals("zh", StringComparison.OrdinalIgnoreCase); - var headerTextLength = Math.Max(1, ComponentTypographyLayoutService.CountTextDisplayUnits(GregorianHeadlineTextBlock.Text)); + var headerTextLength = Math.Max(1, GregorianHeadlineTextBlock.Text?.Length ?? (isZh ? 5 : 6)); var headerCompression = headerTextLength >= 8 ? 0.90 : headerTextLength >= 6 ? 0.95 : 1.0; var densityBoost = scale <= 0.74 ? 0.90 : scale <= 0.90 ? 0.95 : scale >= 1.45 ? 1.05 : 1.0; - GregorianHeadlineTextBlock.FontSize = Math.Clamp(29 * scale * headerCompression * densityBoost, 12.5, 40); + GregorianHeadlineTextBlock.FontSize = Math.Clamp(29 * scale * headerCompression * densityBoost, 12.5, 42); GregorianHeadlineTextBlock.FontWeight = ToVariableWeight(Lerp(560, 720, Math.Clamp((scale - 0.60) / 1.2, 0, 1))); GregorianHeadlineTextBlock.LineHeight = GregorianHeadlineTextBlock.FontSize * 1.03; @@ -393,9 +360,9 @@ public partial class DateWidget : UserControl, IDesktopComponentWidget, ITimeZon block.LineHeight = _weekdayFontSize * 1.02; } - _calendarDayFontSize = Math.Clamp(15.4 * scale * densityBoost, 8, 20); + _calendarDayFontSize = Math.Clamp(15.4 * scale * densityBoost, 8, 22); _calendarDayFontWeight = ToVariableWeight(Lerp(540, 680, Math.Clamp((scale - 0.60) / 1.2, 0, 1))); - _calendarTodayDotSize = Math.Clamp(_calendarDayFontSize * 1.42, 15, 30); + _calendarTodayDotSize = Math.Clamp(_calendarDayFontSize * 1.30, 13.5, 31); var rightDensity = scale <= 0.72 ? 0.90 : scale <= 0.90 ? 0.95 : scale >= 1.38 ? 1.03 : 1.0; LunarDateTextBlock.FontSize = Math.Clamp(30 * scale * rightDensity, 14, 44); diff --git a/LanMountainDesktop/Views/Components/ExchangeRateCalculatorWidget.axaml.cs b/LanMountainDesktop/Views/Components/ExchangeRateCalculatorWidget.axaml.cs index 1ebad06..b91e8a0 100644 --- a/LanMountainDesktop/Views/Components/ExchangeRateCalculatorWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/ExchangeRateCalculatorWidget.axaml.cs @@ -81,7 +81,7 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone _currentCellSize = Math.Max(1, cellSize); var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 14, 48); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(12 * scale, 12 * scale, null, 0.55d); + RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(12 * scale, 6, 18)); } public void SetRecommendationInfoService(IRecommendationInfoService recommendationInfoService) diff --git a/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml b/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml index 8b39cf0..bdfd338 100644 --- a/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml +++ b/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml @@ -13,7 +13,7 @@ ClipToBounds="True" Padding="14"> + MaxLines="1" + Margin="8,0,8,0" /> + MaxLines="1" + Margin="8,0,8,0" /> diff --git a/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml.cs b/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml.cs index 86c09a3..b31e5ce 100644 --- a/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/HolidayCalendarWidget.axaml.cs @@ -6,7 +6,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; -using LanMountainDesktop.DesktopComponents.Runtime; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; @@ -212,81 +211,46 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge var scale = ResolveScale(width, height); var isCompact = width < 170 || height < 170; var isUltraCompact = width < 130 || height < 130; - var titleUnits = ComponentTypographyLayoutService.CountTextDisplayUnits(TitleTextBlock.Text); - var dateUnits = ComponentTypographyLayoutService.CountTextDisplayUnits(DateTextBlock.Text); + 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 = ComponentChromeCornerRadiusHelper.Scale(shortSide * 0.13, 10, 46); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - shortSide * 0.055, - shortSide * 0.05, - null, - 0.55d); - LayoutRoot.RowSpacing = Math.Clamp(shortSide * 0.034, 3.2, 14); + var padding = ComponentChromeCornerRadiusHelper.SafeValue(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); - var rootPadding = RootBorder.Padding; - var innerWidth = Math.Max(1, width - rootPadding.Left - rootPadding.Right); - var innerHeight = Math.Max(1, height - rootPadding.Top - rootPadding.Bottom); + 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(9 * scale, 5, 16); + var horizontalMargin = Math.Clamp(8 * scale, 4, 14); var titleMaxWidth = Math.Max(24, innerWidth - horizontalMargin * 2); var dateMaxWidth = titleMaxWidth; - var titleContentBox = ComponentTypographyLayoutService.ResolveGlyphBox( - titleMaxWidth, - row0Height, - preferredSizeScale: 0.84d, - minSize: 24, - maxSize: 170, - insetScale: 0.10d); - var countContentBox = ComponentTypographyLayoutService.ResolveGlyphBox( - titleMaxWidth, - row1Height, - preferredSizeScale: 0.80d, - minSize: 28, - maxSize: 170, - insetScale: 0.08d); - var unitContentBox = ComponentTypographyLayoutService.ResolveBadgeBox( - titleMaxWidth, - row3Height, - preferredSizeScale: 0.42d, - minSize: 10, - maxSize: 72, - insetScale: 0.12d); - var dateContentBox = ComponentTypographyLayoutService.ResolveGlyphBox( - dateMaxWidth, - row4Height, - preferredSizeScale: 0.78d, - minSize: 22, - maxSize: 92, - insetScale: 0.10d); - var titlePreferred = Math.Clamp(24 * scale, 9.2, 34); + 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); - var titleLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + TitleTextBlock.FontSize = FitTextSize( TitleTextBlock.Text, - Math.Max(24, titleContentBox.Width), - titleHeightCap, - 1, - titleLineCount, - 8.6, + TitleTextBlock.FontWeight, Math.Min(titlePreferred, Math.Max(8.8, row0Height * 0.62)), - [TitleTextBlock.FontWeight], - 1.10); - TitleTextBlock.FontSize = titleLayout.FontSize; - TitleTextBlock.FontWeight = titleLayout.Weight; - TitleTextBlock.MaxLines = titleLayout.MaxLines; - TitleTextBlock.TextWrapping = titleLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; - TitleTextBlock.LineHeight = titleLayout.LineHeight; + 8.6, + titleMaxWidth, + titleHeightCap, + titleLineCount, + lineHeightFactor: 1.10); + TitleTextBlock.LineHeight = TitleTextBlock.FontSize * 1.10; - var digitCount = Math.Max(1, ComponentTypographyLayoutService.CountTextDisplayUnits(CountTextBlock.Text)); + var digitCount = Math.Max(1, CountTextBlock.Text?.Trim().Length ?? 1); var digitCompression = digitCount switch { >= 5 => 0.68, @@ -297,60 +261,39 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge 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); - var countLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + CountTextBlock.FontSize = FitTextSize( CountTextBlock.Text, - Math.Max(24, countContentBox.Width), - countHeightCap, - 1, - 1, - 24, + CountTextBlock.FontWeight, Math.Min(countPreferred, Math.Max(28, row1Height * 0.9)), - [CountTextBlock.FontWeight], - 1.08); - CountTextBlock.FontSize = countLayout.FontSize; - CountTextBlock.FontWeight = countLayout.Weight; - CountTextBlock.MaxLines = countLayout.MaxLines; - CountTextBlock.TextWrapping = TextWrapping.NoWrap; - CountTextBlock.LineHeight = countLayout.LineHeight; + 24, + titleMaxWidth, + countHeightCap, + maxLines: 1, + lineHeightFactor: 1.08); + CountTextBlock.LineHeight = CountTextBlock.FontSize * 1.08; var unitCompactFactor = isUltraCompact ? 0.8 : isCompact ? 0.9 : 1.0; - var unitPreferred = Math.Min(Math.Clamp(52 * scale * unitCompactFactor, 10, 72), Math.Max(10, row3Height * 0.64)); - var unitLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( - DayUnitTextBlock.Text, - Math.Max(18, unitContentBox.Width), - Math.Max(10, row3Height * 0.64), - 1, - 1, - 10, - unitPreferred, - [DayUnitTextBlock.FontWeight], - 1.02); - DayUnitTextBlock.FontSize = unitLayout.FontSize; - DayUnitTextBlock.FontWeight = unitLayout.Weight; - DayUnitTextBlock.MaxLines = 1; - DayUnitTextBlock.TextWrapping = TextWrapping.NoWrap; - DayUnitTextBlock.LineHeight = unitLayout.LineHeight; + 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); - var dateLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( + DateTextBlock.FontSize = FitTextSize( DateTextBlock.Text, - Math.Max(24, dateContentBox.Width), - dateHeightCap, - 1, - dateLineCount, - 8.5, + DateTextBlock.FontWeight, Math.Min(datePreferred, Math.Max(9, row4Height * 0.58)), - [DateTextBlock.FontWeight], - 1.12); - DateTextBlock.FontSize = dateLayout.FontSize; - DateTextBlock.FontWeight = dateLayout.Weight; - DateTextBlock.MaxLines = dateLayout.MaxLines; - DateTextBlock.TextWrapping = dateLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; - DateTextBlock.LineHeight = dateLayout.LineHeight; + 8.5, + dateMaxWidth, + dateHeightCap, + dateLineCount, + lineHeightFactor: 1.12); + DateTextBlock.LineHeight = DateTextBlock.FontSize * 1.12; } private double[] ApplyAdaptiveRowHeights( @@ -400,6 +343,66 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge 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); @@ -407,5 +410,4 @@ public partial class HolidayCalendarWidget : UserControl, IDesktopComponentWidge 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); } - } diff --git a/LanMountainDesktop/Views/Components/IfengNewsWidget.axaml.cs b/LanMountainDesktop/Views/Components/IfengNewsWidget.axaml.cs index 6836490..46194a5 100644 --- a/LanMountainDesktop/Views/Components/IfengNewsWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/IfengNewsWidget.axaml.cs @@ -401,26 +401,18 @@ public partial class IfengNewsWidget : UserControl, IDesktopComponentWidget, IRe var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * softScale, 16, 46); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 12 * softScale, - 10 * softScale, - null, - 0.45d); CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * softScale, 16, 46); var horizontalPadding = Math.Clamp(14 * softScale, 8, 20); var verticalPadding = Math.Clamp(14 * softScale, 8, 20); - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(horizontalPadding, verticalPadding, null, 0.55d); - - var rootPadding = RootBorder.Padding; - var cardPadding = CardBorder.Padding; + CardBorder.Padding = new Thickness(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); var rowSpacing = Math.Clamp(8 * softScale, 4, 12); ContentGrid.RowSpacing = rowSpacing; HeaderGrid.ColumnSpacing = Math.Clamp(10 * softScale, 6, 16); - var innerWidth = Math.Max(150, totalWidth - rootPadding.Left - rootPadding.Right - cardPadding.Left - cardPadding.Right); - var innerHeight = Math.Max(160, totalHeight - rootPadding.Top - rootPadding.Bottom - cardPadding.Top - cardPadding.Bottom); + var innerWidth = Math.Max(150, totalWidth - horizontalPadding * 2d); + var innerHeight = Math.Max(160, totalHeight - verticalPadding * 2d); var availableRowsHeight = Math.Max(120, innerHeight - rowSpacing * 4d); var headerHeight = Math.Clamp(availableRowsHeight * 0.16, 24, 54); var itemHeight = Math.Max(32, (availableRowsHeight - headerHeight) / 4d); diff --git a/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml b/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml index 5d6b836..d53e670 100644 --- a/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml +++ b/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml @@ -11,13 +11,13 @@ Background="#EFE6D9" CornerRadius="30" ClipToBounds="True" - Padding="18"> + Padding="16"> + RowSpacing="10"> + HorizontalAlignment="Center" /> + RowSpacing="12"> + ColumnSpacing="8"> + MaxLines="1" /> + ColumnSpacing="8"> + MaxLines="1" /> diff --git a/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml.cs b/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml.cs index ee7aa17..b43b45e 100644 --- a/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/LunarCalendarWidget.axaml.cs @@ -1,11 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; -using LanMountainDesktop.DesktopComponents.Runtime; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; @@ -182,109 +181,36 @@ public partial class LunarCalendarWidget : UserControl, IDesktopComponentWidget, private void ApplyAdaptiveTypography() { var scale = ResolveScale(); - var lunarUnits = ComponentTypographyLayoutService.CountTextDisplayUnits(LunarDateTextBlock.Text); - var itemUnits = Math.Max( - ComponentTypographyLayoutService.CountTextDisplayUnits(YiItemsTextBlock.Text), - ComponentTypographyLayoutService.CountTextDisplayUnits(JiItemsTextBlock.Text)); - var lunarNeedsTwoLines = lunarUnits >= (scale <= 0.82 ? 18 : 24); - var itemsNeedTwoLines = itemUnits >= (scale <= 0.82 ? 18 : 24); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * scale, 16, 44); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(18 * scale, 18 * scale, null, 0.58d); - LayoutRoot.RowSpacing = Math.Clamp(12 * scale, 6, 20); + RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(16 * scale, 8, 24)); + LayoutRoot.RowSpacing = Math.Clamp(10 * scale, 5, 18); DividerBorder.Margin = new Thickness( - Math.Clamp(10 * scale, 4, 16), Math.Clamp(8 * scale, 3, 14), - Math.Clamp(10 * scale, 4, 16), - Math.Clamp(3 * scale, 1, 7)); - AuspiciousGrid.RowSpacing = Math.Clamp(14 * scale, 7, 22); + Math.Clamp(8 * scale, 3, 14), + Math.Clamp(8 * scale, 3, 14), + Math.Clamp(2 * scale, 1, 6)); + AuspiciousGrid.RowSpacing = Math.Clamp(12 * scale, 6, 20); var densityBoost = scale <= 0.72 ? 0.90 : scale <= 0.88 ? 0.95 : scale >= 1.42 ? 1.04 : 1.0; - var lunarTitleBox = ComponentTypographyLayoutService.ResolveGlyphBox( - Math.Max(1, Bounds.Width > 1 ? Bounds.Width : 300), - Math.Max(1, Bounds.Height > 1 ? Bounds.Height : 300), - preferredSizeScale: 0.50d, - minSize: 28, - maxSize: 134, - insetScale: 0.12d); - var lunarItemBox = ComponentTypographyLayoutService.ResolveBadgeBox( - Math.Max(1, Bounds.Width > 1 ? Bounds.Width : 300), - Math.Max(1, Bounds.Height > 1 ? Bounds.Height : 300), - preferredSizeScale: 0.32d, - minSize: 16, - maxSize: 84, - insetScale: 0.12d); + GregorianLineTextBlock.FontSize = Math.Clamp(24 * scale * densityBoost, 10, 38); + LunarDateTextBlock.FontSize = Math.Clamp(88 * scale * densityBoost, 28, 134); + YiLabelTextBlock.FontSize = Math.Clamp(30 * scale * densityBoost, 12, 46); + JiLabelTextBlock.FontSize = YiLabelTextBlock.FontSize; + YiItemsTextBlock.FontSize = Math.Clamp(24 * scale * densityBoost, 10, 36); + JiItemsTextBlock.FontSize = YiItemsTextBlock.FontSize; - var gregorianLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( - GregorianLineTextBlock.Text, - Math.Max(120, lunarTitleBox.Width * 0.9), - Math.Max(16, 44 * scale * densityBoost), - 1, - 1, - 10, - Math.Clamp(24 * scale * densityBoost, 10, 38), - [FontWeight.SemiBold, FontWeight.Medium], - 1.06); - GregorianLineTextBlock.MaxLines = gregorianLayout.MaxLines; - GregorianLineTextBlock.TextWrapping = TextWrapping.NoWrap; - GregorianLineTextBlock.FontSize = gregorianLayout.FontSize; - GregorianLineTextBlock.FontWeight = gregorianLayout.Weight; - GregorianLineTextBlock.Margin = new Thickness(4 * scale, 0, 4 * scale, 0); - _gregorianLineWeight = gregorianLayout.Weight; + _gregorianLineWeight = ToVariableWeight(Lerp(500, 640, Math.Clamp((scale - 0.58) / 1.2, 0, 1))); + _lunarDateWeight = ToVariableWeight(Lerp(650, 780, Math.Clamp((scale - 0.58) / 1.2, 0, 1))); + _labelWeight = ToVariableWeight(Lerp(620, 760, Math.Clamp((scale - 0.58) / 1.2, 0, 1))); + _itemsWeight = ToVariableWeight(Lerp(520, 670, Math.Clamp((scale - 0.58) / 1.2, 0, 1))); - var lunarLineCount = lunarNeedsTwoLines ? 2 : 1; - LunarDateTextBlock.Margin = new Thickness(2 * scale, 0, 2 * scale, 0); - var lunarLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( - LunarDateTextBlock.Text, - Math.Max(120, lunarTitleBox.Width - (LunarDateTextBlock.Margin.Left + LunarDateTextBlock.Margin.Right)), - lunarLineCount > 1 - ? Math.Clamp(120 * scale * densityBoost, 44, 160) - : Math.Clamp(88 * scale * densityBoost, 28, 126), - 1, - lunarLineCount, - 24, - Math.Clamp(88 * scale * densityBoost, 28, 134), - [FontWeight.Bold, FontWeight.SemiBold], - 1.02); - LunarDateTextBlock.MaxLines = lunarLayout.MaxLines; - LunarDateTextBlock.TextWrapping = lunarLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; - LunarDateTextBlock.FontSize = lunarLayout.FontSize; - LunarDateTextBlock.FontWeight = lunarLayout.Weight; - - var labelSize = Math.Clamp(30 * scale * densityBoost, 12, 46); - YiLabelTextBlock.FontSize = labelSize; - JiLabelTextBlock.FontSize = labelSize; - - var itemMaxLines = itemsNeedTwoLines ? 2 : 1; - YiItemsTextBlock.Margin = new Thickness(0, 1 * scale, 0, 0); - var yiLayout = ComponentTypographyLayoutService.FitAdaptiveTextLayout( - YiItemsTextBlock.Text, - Math.Max(92, lunarItemBox.Width), - Math.Clamp(42 * scale * densityBoost, 16, 84), - 1, - itemMaxLines, - 9, - Math.Clamp(24 * scale * densityBoost, 10, 36), - [FontWeight.SemiBold, FontWeight.Medium], - 1.10); - YiItemsTextBlock.MaxLines = yiLayout.MaxLines; - YiItemsTextBlock.TextWrapping = yiLayout.MaxLines > 1 ? TextWrapping.Wrap : TextWrapping.NoWrap; - YiItemsTextBlock.FontSize = yiLayout.FontSize; - YiItemsTextBlock.FontWeight = yiLayout.Weight; - YiItemsTextBlock.LineHeight = yiLayout.LineHeight; - - JiItemsTextBlock.MaxLines = itemMaxLines; - JiItemsTextBlock.TextWrapping = YiItemsTextBlock.TextWrapping; - JiItemsTextBlock.Margin = new Thickness(0, 1 * scale, 0, 0); - JiItemsTextBlock.FontSize = yiLayout.FontSize; - JiItemsTextBlock.FontWeight = yiLayout.Weight; - JiItemsTextBlock.LineHeight = yiLayout.LineHeight; - - LunarDateTextBlock.FontWeight = lunarLayout.Weight; - YiLabelTextBlock.FontWeight = ToVariableWeight(Lerp(620, 740, Math.Clamp((scale - 0.60) / 1.2, 0, 1))); - JiLabelTextBlock.FontWeight = YiLabelTextBlock.FontWeight; - YiItemsTextBlock.FontWeight = yiLayout.Weight; - JiItemsTextBlock.FontWeight = yiLayout.Weight; + GregorianLineTextBlock.FontWeight = _gregorianLineWeight; + LunarDateTextBlock.FontWeight = _lunarDateWeight; + YiLabelTextBlock.FontWeight = _labelWeight; + JiLabelTextBlock.FontWeight = _labelWeight; + YiItemsTextBlock.FontWeight = _itemsWeight; + JiItemsTextBlock.FontWeight = _itemsWeight; _auspiciousItemCount = scale switch { diff --git a/LanMountainDesktop/Views/Components/MonthCalendarWidget.axaml.cs b/LanMountainDesktop/Views/Components/MonthCalendarWidget.axaml.cs index 792c575..db854ea 100644 --- a/LanMountainDesktop/Views/Components/MonthCalendarWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/MonthCalendarWidget.axaml.cs @@ -5,7 +5,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; -using LanMountainDesktop.DesktopComponents.Runtime; using LanMountainDesktop.Services; namespace LanMountainDesktop.Views.Components; @@ -27,7 +26,6 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget, private double _calendarDayFontSize = 22; private FontWeight _calendarDayFontWeight = FontWeight.SemiBold; private double _calendarTodayDotSize = 44; - private int _calendarVisibleRows = 6; public MonthCalendarWidget() { @@ -150,36 +148,6 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget, var firstDayOfMonth = new DateTime(year, month, 1); var daysInMonth = DateTime.DaysInMonth(year, month); var startDayOfWeek = (int)firstDayOfMonth.DayOfWeek; - var rowDensity = _calendarVisibleRows >= 6 ? 0.84 : 1.0; - var headerReserve = HeaderTextBlock.FontSize * 1.15; - var weekdayReserve = _weekdayFontSize * 1.12; - var gridReserve = LayoutRoot.RowSpacing * 2; - var availableCalendarHeight = Math.Max( - 1, - LayoutRoot.Height - - RootBorder.Padding.Top - - RootBorder.Padding.Bottom - - headerReserve - - weekdayReserve - - gridReserve); - var calendarCellHeight = availableCalendarHeight / Math.Max(1, _calendarVisibleRows); - var todayBadge = ComponentTypographyLayoutService.ResolveBadgeBox( - calendarCellHeight, - calendarCellHeight, - preferredSizeScale: 0.94d, - minSize: 15, - maxSize: 32, - insetScale: 0.14d); - var todayDotSize = Math.Min(todayBadge.Width, todayBadge.Height); - var todayGlyphBox = ComponentTypographyLayoutService.ResolveGlyphBox( - todayDotSize, - todayDotSize, - preferredSizeScale: 0.74d, - minSize: 8, - maxSize: 20, - insetScale: 0.12d); - var todayGlyphSize = Math.Min(todayGlyphBox.Width, todayGlyphBox.Height); - var dayFontSize = Math.Clamp(Math.Min(_calendarDayFontSize * rowDensity, calendarCellHeight * 0.46), 8, 24); for (var day = 1; day <= daysInMonth; day++) { @@ -195,8 +163,7 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget, Text = day.ToString(CultureInfo.CurrentCulture), HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, - FontSize = dayFontSize, - LineHeight = dayFontSize * 1.04, + FontSize = _calendarDayFontSize, FontWeight = _calendarDayFontWeight, Tag = "day" }; @@ -211,15 +178,11 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget, : Brushes.White; dayText.Foreground = onAccentBrush; - dayText.Width = todayGlyphBox.Width; - dayText.Height = todayGlyphBox.Height; - dayText.TextAlignment = TextAlignment.Center; - dayText.LineHeight = todayGlyphSize * 1.03; var dot = new Border { - Width = todayDotSize, - Height = todayDotSize, - CornerRadius = new CornerRadius(todayDotSize * 0.5), + Width = _calendarTodayDotSize, + Height = _calendarTodayDotSize, + CornerRadius = new CornerRadius(_calendarTodayDotSize * 0.5), Background = accentBrush, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, @@ -255,21 +218,21 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget, var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(28 * scale, 14, 40); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness(15 * scale, 15 * scale, null, 0.55d); - LayoutRoot.RowSpacing = Math.Clamp(11 * scale, 5.5, 17); + RootBorder.Padding = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 8, 22)); + LayoutRoot.RowSpacing = Math.Clamp(10 * scale, 5, 16); LayoutRoot.Width = Math.Clamp(280 * scale, 220, 420); LayoutRoot.Height = Math.Clamp(280 * scale, 220, 420); var isZh = CultureInfo.CurrentCulture.TwoLetterISOLanguageName.Equals("zh", StringComparison.OrdinalIgnoreCase); - var headerTextLength = Math.Max(1, ComponentTypographyLayoutService.CountTextDisplayUnits(HeaderTextBlock.Text)); + var headerTextLength = Math.Max(1, HeaderTextBlock.Text?.Length ?? (isZh ? 5 : 6)); var headerCompression = headerTextLength >= 8 ? 0.90 : headerTextLength >= 6 ? 0.95 : 1.0; var densityBoost = scale <= 0.74 ? 0.90 : scale <= 0.90 ? 0.95 : scale >= 1.45 ? 1.05 : 1.0; - HeaderTextBlock.FontSize = Math.Clamp(42 * scale * headerCompression * densityBoost, 13, 58); + HeaderTextBlock.FontSize = Math.Clamp(42 * scale * headerCompression * densityBoost, 13, 62); HeaderTextBlock.FontWeight = ToVariableWeight(Lerp(560, 720, Math.Clamp((scale - 0.62) / 1.2, 0, 1))); HeaderTextBlock.LineHeight = HeaderTextBlock.FontSize * 1.05; - _weekdayFontSize = Math.Clamp(20 * scale * densityBoost, 7.5, 24); + _weekdayFontSize = Math.Clamp(20 * scale * densityBoost, 7.5, 27); _weekdayFontWeight = ToVariableWeight(Lerp(500, 640, Math.Clamp((scale - 0.60) / 1.3, 0, 1))); foreach (var block in GetWeekdayHeaderBlocks()) { @@ -278,9 +241,9 @@ public partial class MonthCalendarWidget : UserControl, IDesktopComponentWidget, block.LineHeight = _weekdayFontSize * 1.06; } - _calendarDayFontSize = Math.Clamp(22 * scale * densityBoost, 8, 28); + _calendarDayFontSize = Math.Clamp(22 * scale * densityBoost, 8, 32); _calendarDayFontWeight = ToVariableWeight(Lerp(540, 680, Math.Clamp((scale - 0.60) / 1.3, 0, 1))); - _calendarTodayDotSize = Math.Clamp(_calendarDayFontSize * 1.88, 16, 58); + _calendarTodayDotSize = Math.Clamp(_calendarDayFontSize * 1.95, 16, 62); } private double ResolveScale() diff --git a/LanMountainDesktop/Views/Components/MusicControlWidget.axaml b/LanMountainDesktop/Views/Components/MusicControlWidget.axaml index e48b2a2..35cf009 100644 --- a/LanMountainDesktop/Views/Components/MusicControlWidget.axaml +++ b/LanMountainDesktop/Views/Components/MusicControlWidget.axaml @@ -1,4 +1,4 @@ - + Height="31"> + Height="31"> - { - if (!_isAttached || !_isOnActivePage) - { - return; - } - - _currentState = state; - ApplyState(state); - }); - } - - private void OnQueueChanged(object? sender, MusicQueueState queue) - { - Dispatcher.UIThread.Post(() => - { - if (!_isAttached || !_isOnActivePage) - { - return; - } - - _currentQueue = queue; - UpdateQueueButtonState(); - }); - } - - private void UpdateListeningState() - { - var shouldListen = _isAttached && _isOnActivePage; - - if (shouldListen && !_isListening) - { - _musicControlService.StartListening(); - _isListening = true; - } - else if (!shouldListen && _isListening) - { - _musicControlService.StopListening(); - _isListening = false; - } - } - - private async Task RefreshStateAsync() - { - if (!_isAttached || !_isOnActivePage) - { - return; - } - - UpdateLanguageCode(); - - try - { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - var state = await _musicControlService.GetCurrentStateAsync(cts.Token); - - if (cts.IsCancellationRequested || !_isAttached) - { - return; - } - - _currentState = state; - ApplyState(state); - - // Also refresh queue - var queue = await _musicControlService.GetPlaybackQueueAsync(20, cts.Token); - _currentQueue = queue; - UpdateQueueButtonState(); - } - catch (OperationCanceledException) - { - // Ignore cancellation. - } - catch - { - var fallbackState = MusicPlaybackState.NoSession(isSupported: OperatingSystem.IsWindows()); - _currentState = fallbackState; - ApplyState(fallbackState); - } + await RefreshStateAsync(); } private async void OnPlayPauseButtonClick(object? sender, RoutedEventArgs e) @@ -258,25 +183,6 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, await ExecuteCommandAsync(token => _musicControlService.SkipNextAsync(token)); } - private async void OnFavoriteButtonClick(object? sender, RoutedEventArgs e) - { - await ExecuteCommandAsync(token => _musicControlService.ToggleFavoriteAsync(token)); - } - - private async void OnQueueButtonClick(object? sender, RoutedEventArgs e) - { - // Show queue flyout or panel - // For now, just refresh the queue - try - { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - var queue = await _musicControlService.GetPlaybackQueueAsync(20, cts.Token); - _currentQueue = queue; - UpdateQueueButtonState(); - } - catch { } - } - private async void OnSourceAppButtonClick(object? sender, RoutedEventArgs e) { await ExecuteCommandAsync( @@ -302,39 +208,85 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, try { - CancelCommandRequest(); - _commandCts = new CancellationTokenSource(TimeSpan.FromSeconds(4)); - _ = await command(_commandCts.Token); + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4)); + _ = await command(cts.Token); } catch { - // Ignore command transport errors and recover on next event. + // Ignore command transport errors and recover on next poll. } finally { _isExecutingCommand = false; - CancelCommandRequest(); } if (refreshAfterCommand) { - // The event-driven system will update the UI automatically, - // but we also do a manual refresh to ensure consistency - await Task.Delay(100); await RefreshStateAsync(); } } - private void CancelCommandRequest() + private async Task RefreshStateAsync() { - var cts = Interlocked.Exchange(ref _commandCts, null); - if (cts is null) + if (!_isAttached || !_isOnActivePage || _isRefreshing) { return; } - cts.Cancel(); - cts.Dispose(); + _isRefreshing = true; + UpdateLanguageCode(); + + var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + var previous = Interlocked.Exchange(ref _refreshCts, cts); + previous?.Cancel(); + previous?.Dispose(); + + try + { + var state = await _musicControlService.GetCurrentStateAsync(cts.Token); + if (cts.IsCancellationRequested || !_isAttached) + { + return; + } + + _currentState = state; + ApplyState(state); + } + catch (OperationCanceledException) + { + // Ignore cancellation. + } + catch + { + var fallbackState = MusicPlaybackState.NoSession(isSupported: OperatingSystem.IsWindows()); + _currentState = fallbackState; + ApplyState(fallbackState); + } + finally + { + if (ReferenceEquals(_refreshCts, cts)) + { + _refreshCts = null; + } + + cts.Dispose(); + _isRefreshing = false; + } + } + + private void UpdateRefreshTimerState() + { + if (_isAttached && _isOnActivePage) + { + if (!_refreshTimer.IsEnabled) + { + _refreshTimer.Start(); + } + + return; + } + + _refreshTimer.Stop(); } private void ApplyState(MusicPlaybackState state) @@ -412,10 +364,6 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, ? PauseSymbol : PlaySymbol; - // Update favorite button - FavoriteIcon.Symbol = state.IsFavorite ? HeartFilledSymbol : HeartSymbol; - FavoriteIcon.IconVariant = state.IsFavorite ? IconVariant.Filled : IconVariant.Regular; - SetCoverImage(state.ThumbnailBytes); ApplyActionButtonState(state); UpdateSourceAppButtonTooltip(); @@ -437,16 +385,7 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, : showNoSessionStyle; SourceAppButton.IsEnabled = !_isExecutingCommand && state.IsSupported; QueueButton.IsEnabled = canOperate || showNoSessionStyle; - FavoriteButton.IsEnabled = canOperate - ? state.CanToggleFavorite - : showNoSessionStyle; - } - - private void UpdateQueueButtonState() - { - // Update queue button visual state based on queue availability - var hasQueue = _currentQueue.IsSupported && _currentQueue.HasMoreItems; - QueueIcon.Opacity = hasQueue ? 1.0 : 0.5; + FavoriteButton.IsEnabled = canOperate || showNoSessionStyle; } private void ApplyNoMediaVisualTheme() @@ -502,10 +441,6 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, SourceAppGlyphBadge.BorderBrush = new SolidColorBrush(Color.Parse("#00FFFFFF")); SourceAppIcon.IconVariant = IconVariant.Filled; SourceAppIcon.Foreground = new SolidColorBrush(Color.Parse("#FBFFFFFF")); - - // Reset favorite icon - FavoriteIcon.Symbol = HeartSymbol; - FavoriteIcon.IconVariant = IconVariant.Regular; } private void ApplyActiveVisualTheme() @@ -539,6 +474,18 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, } } + private void CancelRefreshRequest() + { + var cts = Interlocked.Exchange(ref _refreshCts, null); + if (cts is null) + { + return; + } + + cts.Cancel(); + cts.Dispose(); + } + private string ResolveStatusText(MusicPlaybackStatus status) { return status switch @@ -749,18 +696,4 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget, var safeIndex = Math.Clamp(index, 0, colors.Count - 1); return colors[safeIndex]; } - - public void Dispose() - { - _musicControlService.PlaybackStateChanged -= OnPlaybackStateChanged; - _musicControlService.QueueChanged -= OnQueueChanged; - _musicControlService.StopListening(); - if (_musicControlService is IDisposable disposableService) - { - disposableService.Dispose(); - } - - CancelCommandRequest(); - DisposeCoverBitmap(); - } } diff --git a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml index e6632eb..59d50c7 100644 --- a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml +++ b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml @@ -9,62 +9,37 @@ d:DesignHeight="320" x:Class="LanMountainDesktop.Views.Components.OfficeRecentDocumentsWidget"> - - 34 - 12,10,12,10 - 16,14,16,14 - 0,4,0,0 - 10 - 12 - 14 - 70 - 18 - 14 - 14 - 8 - 28 - 140 - 130 - 90 - 12 - 10 - 8 - - + Padding="0"> - + @@ -80,29 +55,28 @@ + Margin="0,4,0,0"> - + @@ -126,7 +100,7 @@ IsVisible="False" Text="暂无最近文档" Foreground="#9AFFFFFF" - FontSize="{DynamicResource OfficeRecentDocumentsStatusFontSize}" + FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" /> diff --git a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs index ba1782a..a1f0489 100644 --- a/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/OfficeRecentDocumentsWidget.axaml.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Avalonia; using Avalonia.Controls; using Avalonia.Input; using LanMountainDesktop.ComponentSystem; @@ -37,54 +36,8 @@ public partial class OfficeRecentDocumentsWidget : UserControl, IDesktopComponen return; } - var normalizedCellSize = Math.Max(1, cellSize); - var scale = Math.Clamp(normalizedCellSize / 48d, 0.72d, 1.65d); - var rootCornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 16, 52); - - RootBorder.CornerRadius = rootCornerRadius; - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 10 * scale, - 8 * scale, - null, - 0.45d); - - Resources["OfficeRecentDocumentsRootCornerRadius"] = rootCornerRadius; - Resources["OfficeRecentDocumentsRootPadding"] = RootBorder.Padding; - - var contentMargin = ComponentChromeCornerRadiusHelper.SafeThickness( - 16 * scale, - 14 * scale, - null, - 0.55d); - Resources["OfficeRecentDocumentsContentMargin"] = contentMargin; - Resources["OfficeRecentDocumentsScrollMargin"] = new Thickness( - 0, - ComponentChromeCornerRadiusHelper.SafeValue(4 * scale, 2, 8, null, 0.40d), - 0, - 0); - Resources["OfficeRecentDocumentsContentRowSpacing"] = ComponentChromeCornerRadiusHelper.SafeValue(8 * scale, 4, 12, null, 0.40d); - - var refreshButtonSize = Math.Clamp(28 * scale, 20, 40); - Resources["OfficeRecentDocumentsRefreshButtonSize"] = refreshButtonSize; - Resources["OfficeRecentDocumentsRefreshCornerRadius"] = new CornerRadius(refreshButtonSize / 2d); - Resources["OfficeRecentDocumentsRefreshIconFontSize"] = Math.Clamp(14 * scale, 10, 20); - - var accentSize = Math.Clamp(140 * scale, 88, 188); - 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); - var cardHeight = Math.Clamp(90 * scale, 68, 124); - Resources["OfficeRecentDocumentsDocumentCardWidth"] = cardWidth; - Resources["OfficeRecentDocumentsDocumentCardHeight"] = cardHeight; - Resources["OfficeRecentDocumentsCardCornerRadius"] = ComponentChromeCornerRadiusHelper.Scale(16 * scale, 10, 24); - Resources["OfficeRecentDocumentsCardPadding"] = new Thickness(ComponentChromeCornerRadiusHelper.SafeValue(10 * scale, 6, 16, null, 0.50d)); - Resources["OfficeRecentDocumentsDocumentTitleFontSize"] = Math.Clamp(12 * scale, 10, 18); - Resources["OfficeRecentDocumentsDocumentTimeFontSize"] = Math.Clamp(10 * scale, 8, 14); + var scale = cellSize / 100.0; + RootBorder.CornerRadius = new Avalonia.CornerRadius(Math.Max(8, 34 * scale)); } public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) diff --git a/LanMountainDesktop/Views/Components/RecordingWidget.axaml.cs b/LanMountainDesktop/Views/Components/RecordingWidget.axaml.cs index 879bc6c..26ebe58 100644 --- a/LanMountainDesktop/Views/Components/RecordingWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/RecordingWidget.axaml.cs @@ -65,11 +65,7 @@ public partial class RecordingWidget : UserControl, IDesktopComponentWidget, IDe var rootRadius = ComponentChromeCornerRadiusHelper.Scale(34 * chromeScale, 16, 56); RootBorder.CornerRadius = rootRadius; - RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(14 * contentScale, 10, 22), - ComponentChromeCornerRadiusHelper.SafeValue(12 * contentScale, 8, 18), - ComponentChromeCornerRadiusHelper.SafeValue(14 * contentScale, 10, 22), - ComponentChromeCornerRadiusHelper.SafeValue(12 * contentScale, 8, 18)); + RootBorder.Padding = new Thickness(0); RecorderCardBorder.CornerRadius = rootRadius; RecorderContentGrid.Margin = new Thickness( ComponentChromeCornerRadiusHelper.SafeValue(24 * contentScale, 14, 26), diff --git a/LanMountainDesktop/Views/Components/Stcn24ForumWidget.axaml.cs b/LanMountainDesktop/Views/Components/Stcn24ForumWidget.axaml.cs index 585f3ef..2c5868b 100644 --- a/LanMountainDesktop/Views/Components/Stcn24ForumWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/Stcn24ForumWidget.axaml.cs @@ -603,20 +603,12 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I var totalHeight = Bounds.Height > 1 ? Bounds.Height : _currentCellSize * BaseHeightCells; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * softScale, 14, 44); - RootBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( - 10 * softScale, - 8 * softScale, - null, - 0.45d); CardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(30 * softScale, 14, 44); - CardBorder.Padding = ComponentChromeCornerRadiusHelper.SafeThickness( + CardBorder.Padding = new Thickness( Math.Clamp(12 * softScale, 8, 18), Math.Clamp(12 * softScale, 8, 18), - null, - 0.55d); - - var rootPadding = RootBorder.Padding; - var cardPadding = CardBorder.Padding; + Math.Clamp(12 * softScale, 8, 18), + Math.Clamp(12 * softScale, 8, 18)); var rowSpacing = Math.Clamp(6 * softScale, 3, 10); ContentGrid.RowSpacing = rowSpacing; @@ -633,7 +625,7 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I RefreshButton.CornerRadius = new CornerRadius(refreshSize / 2d); RefreshGlyphIcon.FontSize = Math.Clamp(16 * softScale, 10, 20); - var innerWidth = Math.Max(100, totalWidth - rootPadding.Left - rootPadding.Right - cardPadding.Left - cardPadding.Right); + var innerWidth = Math.Max(100, totalWidth - CardBorder.Padding.Left - CardBorder.Padding.Right); var rowPaddingHorizontal = Math.Clamp(8 * softScale, 5, 14); var rowPaddingVertical = Math.Clamp(6 * softScale, 3, 10); var avatarSize = Math.Clamp(30 * softScale, 20, 40); @@ -648,10 +640,8 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I var availablePostsHeight = Math.Max( 0d, totalHeight - - rootPadding.Top - - rootPadding.Bottom - - cardPadding.Top - - cardPadding.Bottom - + CardBorder.Padding.Top - + CardBorder.Padding.Bottom - estimatedHeaderHeight - rowSpacing); var rowFootprint = Math.Max(1d, estimatedRowHeight + rowSpacing); diff --git a/LanMountainDesktop/Views/Components/StudyDeductionReasonsWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyDeductionReasonsWidget.axaml.cs index d034f24..bd68a19 100644 --- a/LanMountainDesktop/Views/Components/StudyDeductionReasonsWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyDeductionReasonsWidget.axaml.cs @@ -231,8 +231,8 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.46, 12, 34); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(12 * scale * compactMultiplier, 6, 18), - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale * compactMultiplier, 5, 16)); + Math.Clamp(12 * scale * compactMultiplier, 6, 18), + Math.Clamp(10 * scale * compactMultiplier, 5, 16)); ContentRootGrid.RowSpacing = _isUltraCompactMode ? Math.Clamp(4 * scale, 2, 6) @@ -271,8 +271,8 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(8 * scale, 4, 12)); var rowPadding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale * compactMultiplier, 5, 14), - ComponentChromeCornerRadiusHelper.SafeValue(7 * scale * compactMultiplier, 3, 10)); + Math.Clamp(10 * scale * compactMultiplier, 5, 14), + Math.Clamp(7 * scale * compactMultiplier, 3, 10)); SustainedRowBorder.Padding = rowPadding; TimeRowBorder.Padding = rowPadding; SegmentRowBorder.Padding = rowPadding; diff --git a/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs index 26dee40..173cb7f 100644 --- a/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyEnvironmentWidget.axaml.cs @@ -54,8 +54,8 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 10, 28); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 8, 20), - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale, 6, 16)); + Math.Clamp(14 * scale, 8, 20), + Math.Clamp(10 * scale, 6, 16)); StatusTitleTextBlock.FontSize = Math.Clamp(11 * scale, 9, 18); StatusValueTextBlock.FontSize = Math.Clamp(20 * scale, 12, 34); diff --git a/LanMountainDesktop/Views/Components/StudyInterruptDensityWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyInterruptDensityWidget.axaml.cs index d2f885c..568061f 100644 --- a/LanMountainDesktop/Views/Components/StudyInterruptDensityWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyInterruptDensityWidget.axaml.cs @@ -257,8 +257,8 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.46, 12, 34); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(12 * scale * compactMultiplier, 6, 18), - ComponentChromeCornerRadiusHelper.SafeValue(9 * scale * compactMultiplier, 5, 16)); + Math.Clamp(12 * scale * compactMultiplier, 6, 18), + Math.Clamp(9 * scale * compactMultiplier, 5, 16)); ContentRootGrid.RowSpacing = _isUltraCompactMode ? Math.Clamp(3 * scale, 2, 5) @@ -297,8 +297,8 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(8 * scale, 4, 12)); var cardPadding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale * compactMultiplier, 5, 14), - ComponentChromeCornerRadiusHelper.SafeValue(6 * scale * compactMultiplier, 3, 9)); + Math.Clamp(10 * scale * compactMultiplier, 5, 14), + Math.Clamp(6 * scale * compactMultiplier, 3, 9)); CountCardBorder.Padding = cardPadding; DurationCardBorder.Padding = cardPadding; diff --git a/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs index c6cb04f..c85c4ef 100644 --- a/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyNoiseCurveWidget.axaml.cs @@ -107,8 +107,8 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 14, 42); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 8, 22), - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale, 6, 16)); + Math.Clamp(14 * scale, 8, 22), + Math.Clamp(10 * scale, 6, 16)); StatusTextBlock.FontSize = Math.Clamp(16 * scale, 12, 30); RealtimeValueTextBlock.FontSize = Math.Clamp(18 * scale, 12, 34); diff --git a/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs index fad3dd9..717f0f5 100644 --- a/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyNoiseDistributionWidget.axaml.cs @@ -325,8 +325,8 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone var compactMultiplier = _isUltraCompactMode ? 0.76 : _isCompactMode ? 0.88 : 1.0; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 12, 34); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(12 * scale * compactMultiplier, 6, 18), - ComponentChromeCornerRadiusHelper.SafeValue(9 * scale * compactMultiplier, 5, 16)); + Math.Clamp(12 * scale * compactMultiplier, 6, 18), + Math.Clamp(9 * scale * compactMultiplier, 5, 16)); ContentRootGrid.RowSpacing = _isUltraCompactMode ? Math.Clamp(4 * scale, 2, 5) diff --git a/LanMountainDesktop/Views/Components/StudyScoreOverviewWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudyScoreOverviewWidget.axaml.cs index 8c894ee..74c3bcb 100644 --- a/LanMountainDesktop/Views/Components/StudyScoreOverviewWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudyScoreOverviewWidget.axaml.cs @@ -260,8 +260,8 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi var expandedMultiplier = _isExpandedMode ? 1.12 : 1.0; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.50, 14, 42); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(16 * scale * compactMultiplier * expandedMultiplier, 8, 30), - ComponentChromeCornerRadiusHelper.SafeValue(14 * scale * compactMultiplier * expandedMultiplier, 6, 26)); + Math.Clamp(16 * scale * compactMultiplier * expandedMultiplier, 8, 30), + Math.Clamp(14 * scale * compactMultiplier * expandedMultiplier, 6, 26)); ContentRootGrid.RowSpacing = _isUltraCompactMode ? Math.Clamp(4 * scale, 2, 5) @@ -303,8 +303,8 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi ModeBadgeBorder.CornerRadius = new CornerRadius(Math.Clamp(8 * scale, 5, 14)); var cardPadding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale * compactMultiplier * expandedMultiplier, 6, 20), - ComponentChromeCornerRadiusHelper.SafeValue(8 * scale * compactMultiplier * expandedMultiplier, 4, 16)); + Math.Clamp(10 * scale * compactMultiplier * expandedMultiplier, 6, 20), + Math.Clamp(8 * scale * compactMultiplier * expandedMultiplier, 4, 16)); var cardCornerRadius = ComponentChromeCornerRadiusHelper.Scale(10 * scale, 6, 18); AverageCardBorder.Padding = cardPadding; MinimumCardBorder.Padding = cardPadding; diff --git a/LanMountainDesktop/Views/Components/StudySessionControlWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudySessionControlWidget.axaml.cs index af204dd..422af01 100644 --- a/LanMountainDesktop/Views/Components/StudySessionControlWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudySessionControlWidget.axaml.cs @@ -270,8 +270,8 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW var compactMultiplier = _isUltraCompactMode ? 0.78 : _isCompactMode ? 0.90 : 1.0; RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.34, 10, 28); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(14 * scale * compactMultiplier, 7, 22), - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale * compactMultiplier, 5, 16)); + Math.Clamp(14 * scale * compactMultiplier, 7, 22), + Math.Clamp(10 * scale * compactMultiplier, 5, 16)); LayoutGrid.ColumnSpacing = _isUltraCompactMode ? Math.Clamp(6 * scale, 3, 8) diff --git a/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs b/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs index b94dc72..db56fcf 100644 --- a/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/StudySessionHistoryWidget.axaml.cs @@ -241,9 +241,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW Background = new SolidColorBrush(rowBackground), BorderBrush = new SolidColorBrush(rowBorderColor), BorderThickness = new Thickness(1), - Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(8, 6, 12), - ComponentChromeCornerRadiusHelper.SafeValue(6, 4, 10)) + Padding = new Thickness(Math.Clamp(8, 6, 12), Math.Clamp(6, 4, 10)) }; var panelComposite = ToOpaqueAgainst(panelColor, DarkSubstrate); @@ -357,7 +355,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW Width = _isUltraCompactMode ? 26 : 34, Height = Math.Clamp(26 * (_isCompactMode ? 0.90 : 1.0), 24, 30), Padding = new Thickness(0), - CornerRadius = ComponentChromeCornerRadiusHelper.Scale(10, 8, 12), + CornerRadius = new CornerRadius(10), Background = new SolidColorBrush(buttonBackground), BorderBrush = Brushes.Transparent, BorderThickness = new Thickness(0), @@ -592,8 +590,8 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(_currentCellSize * 0.44, 12, 36); RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(12 * scale, 7, 22), - ComponentChromeCornerRadiusHelper.SafeValue(9 * scale, 5, 16)); + Math.Clamp(12 * scale, 7, 22), + Math.Clamp(9 * scale, 5, 16)); ContentRootGrid.RowSpacing = _isUltraCompactMode ? Math.Clamp(4 * scale, 2, 6) @@ -606,12 +604,12 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW : Math.Clamp(6 * scale, 3, 8); DialogOverlayBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(12 * scale, 8, 20), - ComponentChromeCornerRadiusHelper.SafeValue(10 * scale, 8, 18)); + Math.Clamp(12 * scale, 8, 20), + Math.Clamp(10 * scale, 8, 18)); DialogCardBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(12 * scale, 10, 18); DialogCardBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(12 * scale, 9, 20), - ComponentChromeCornerRadiusHelper.SafeValue(11 * scale, 8, 18)); + Math.Clamp(12 * scale, 9, 20), + Math.Clamp(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); diff --git a/LanMountainDesktop/Views/Components/TimerWidget.axaml.cs b/LanMountainDesktop/Views/Components/TimerWidget.axaml.cs index 73823f1..76b3220 100644 --- a/LanMountainDesktop/Views/Components/TimerWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/TimerWidget.axaml.cs @@ -198,8 +198,7 @@ public partial class TimerWidget : UserControl, IDesktopComponentWidget var scale = ResolveScale(); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(34 * scale, 12, 48); - RootBorder.Padding = new Thickness( - ComponentChromeCornerRadiusHelper.SafeValue(14 * scale, 7, 22)); + RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 7, 22)); TimerPanelBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(32 * scale, 12, 42); PlayButtonBorder.Width = Math.Clamp(42 * scale, 28, 58); diff --git a/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs b/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs index f4d8c33..5621dac 100644 --- a/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs +++ b/LanMountainDesktop/Views/Components/WorldClockWidget.axaml.cs @@ -169,7 +169,7 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT 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.Padding = new Thickness(horizontalPadding, verticalPadding); RootBorder.CornerRadius = ComponentChromeCornerRadiusHelper.Scale(24 * scale, 10, 46); var usableWidth = Math.Max(48, totalWidth - horizontalPadding * 2);