From 675096b6c4acf3b4b3f19d57aca773146b070f1e Mon Sep 17 00:00:00 2001 From: lincube Date: Fri, 3 Apr 2026 21:25:15 +0800 Subject: [PATCH] =?UTF-8?q?fead.=E5=81=9A=E4=BA=86=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F=E5=8A=A0=E4=BA=86=E6=9B=B4=E5=A4=9A=E7=9A=84=E8=83=B6?= =?UTF-8?q?=E5=9B=8A=E7=BB=84=E4=BB=B6=E3=80=82=E7=84=B6=E5=90=8E=E6=88=91?= =?UTF-8?q?=E7=A8=8D=E5=BE=AE=E4=BF=AE=E4=BA=86=E4=B8=80=E4=B8=8B=E6=99=BA?= =?UTF-8?q?=E6=95=99Hub=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LanMountainDesktop/Localization/en-US.json | 23 + LanMountainDesktop/Localization/ja-JP.json | 23 + LanMountainDesktop/Localization/ko-KR.json | 23 + LanMountainDesktop/Localization/zh-CN.json | 23 + .../Models/AppSettingsSnapshot.cs | 22 + .../Services/IRecommendationDataService.cs | 4 +- .../Services/RecommendationDataService.cs | 16 +- .../Services/Settings/SettingsContracts.cs | 13 +- .../Settings/SettingsDomainServices.cs | 37 +- .../StatusBarSettingsPageViewModel.cs | 304 +++++++++++- .../Views/Components/ClockWidget.axaml | 2 +- .../Views/Components/ClockWidget.axaml.cs | 27 +- .../Views/Components/NetworkSpeedWidget.axaml | 72 +++ .../Components/NetworkSpeedWidget.axaml.cs | 451 ++++++++++++++++++ .../Views/MainWindow.ComponentSystem.cs | 243 ++++++++++ .../Views/MainWindow.SettingsHardCut.Stubs.cs | 16 + LanMountainDesktop/Views/MainWindow.axaml | 32 +- LanMountainDesktop/Views/MainWindow.axaml.cs | 21 + .../SettingsPages/StatusBarSettingsPage.axaml | 153 ++++++ 19 files changed, 1488 insertions(+), 17 deletions(-) create mode 100644 LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml create mode 100644 LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml.cs diff --git a/LanMountainDesktop/Localization/en-US.json b/LanMountainDesktop/Localization/en-US.json index 213185f..d911311 100644 --- a/LanMountainDesktop/Localization/en-US.json +++ b/LanMountainDesktop/Localization/en-US.json @@ -400,6 +400,29 @@ "settings.status_bar.text_capsule_position.right": "Right", "settings.status_bar.text_capsule_content_label": "Text content (Markdown supported)", "settings.status_bar.text_capsule_transparent_background_label": "Transparent background", + "settings.status_bar.network_speed_header": "Network Speed", + "settings.status_bar.network_speed_description": "Display real-time network upload and download speed on the status bar.", + "settings.status_bar.network_speed_position_label": "Network speed position", + "settings.status_bar.network_speed_position.left": "Left", + "settings.status_bar.network_speed_position.center": "Center", + "settings.status_bar.network_speed_position.right": "Right", + "settings.status_bar.network_speed_mode_label": "Display mode", + "settings.status_bar.network_speed_mode.both": "Upload + Download", + "settings.status_bar.network_speed_mode.upload": "Upload only", + "settings.status_bar.network_speed_mode.download": "Download only", + "settings.status_bar.network_speed_transparent_background_label": "Transparent background", + "settings.status_bar.show_network_type_icon_label": "Show network type icon", + "settings.status_bar.shadow_header": "Status Bar Shadow", + "settings.status_bar.shadow_desc": "Add shadow effect to the status bar for better visibility of transparent components.", + "settings.status_bar.shadow_enabled_label": "Enable shadow", + "settings.status_bar.shadow_color_label": "Shadow color", + "settings.status_bar.shadow_opacity_label": "Shadow opacity", + "settings.status_bar.theme_header": "Status Bar Theme", + "settings.status_bar.theme_desc": "Set the theme mode for the status bar independently.", + "settings.status_bar.theme_mode_label": "Theme mode", + "settings.status_bar.theme_mode.follow_global": "Follow Global", + "settings.status_bar.theme_mode.dark": "Dark", + "settings.status_bar.theme_mode.light": "Light", "settings.components.title": "Components", "settings.components.description": "Adjust component layout and corner design.", "settings.components.grid_header": "Grid Settings", diff --git a/LanMountainDesktop/Localization/ja-JP.json b/LanMountainDesktop/Localization/ja-JP.json index 2d541fc..ee9a982 100644 --- a/LanMountainDesktop/Localization/ja-JP.json +++ b/LanMountainDesktop/Localization/ja-JP.json @@ -343,6 +343,29 @@ "settings.status_bar.text_capsule_position.right": "右", "settings.status_bar.text_capsule_content_label": "テキスト内容(Markdown対応)", "settings.status_bar.text_capsule_transparent_background_label": "透明な背景", + "settings.status_bar.network_speed_header": "ネットワーク速度", + "settings.status_bar.network_speed_description": "ステータスバーにリアルタイムのネットワーク速度を表示します。", + "settings.status_bar.network_speed_position_label": "ネットワーク速度の位置", + "settings.status_bar.network_speed_position.left": "左", + "settings.status_bar.network_speed_position.center": "中央", + "settings.status_bar.network_speed_position.right": "右", + "settings.status_bar.network_speed_mode_label": "表示モード", + "settings.status_bar.network_speed_mode.both": "アップロード + ダウンロード", + "settings.status_bar.network_speed_mode.upload": "アップロードのみ", + "settings.status_bar.network_speed_mode.download": "ダウンロードのみ", + "settings.status_bar.network_speed_transparent_background_label": "透明な背景", + "settings.status_bar.show_network_type_icon_label": "ネットワークタイプアイコンを表示", + "settings.status_bar.shadow_header": "ステータスバーの影", + "settings.status_bar.shadow_desc": "透明なコンポーネントの視認性を高めるために、ステータスバーに影効果を追加します。", + "settings.status_bar.shadow_enabled_label": "影を有効にする", + "settings.status_bar.shadow_color_label": "影の色", + "settings.status_bar.shadow_opacity_label": "影の不透明度", + "settings.status_bar.theme_header": "ステータスバーのテーマ", + "settings.status_bar.theme_desc": "ステータスバーのテーマモードを独立して設定します。", + "settings.status_bar.theme_mode_label": "テーマモード", + "settings.status_bar.theme_mode.follow_global": "グローバルに従う", + "settings.status_bar.theme_mode.dark": "ダーク", + "settings.status_bar.theme_mode.light": "ライト", "settings.components.title": "コンポーネント", "settings.components.description": "コンポーネントのレイアウトとコーナーデザインを調整します。", "settings.components.grid_header": "グリッド設定", diff --git a/LanMountainDesktop/Localization/ko-KR.json b/LanMountainDesktop/Localization/ko-KR.json index 2358cdb..6f86a3e 100644 --- a/LanMountainDesktop/Localization/ko-KR.json +++ b/LanMountainDesktop/Localization/ko-KR.json @@ -389,6 +389,29 @@ "settings.status_bar.text_capsule_position.right": "오른쪽", "settings.status_bar.text_capsule_content_label": "텍스트 내용 (Markdown 지원)", "settings.status_bar.text_capsule_transparent_background_label": "투명 배경", + "settings.status_bar.network_speed_header": "네트워크 속도", + "settings.status_bar.network_speed_description": "상태 표시줄에 실시간 네트워크 속도를 표시합니다.", + "settings.status_bar.network_speed_position_label": "네트워크 속도 위치", + "settings.status_bar.network_speed_position.left": "왼쪽", + "settings.status_bar.network_speed_position.center": "가욍데", + "settings.status_bar.network_speed_position.right": "오른쪽", + "settings.status_bar.network_speed_mode_label": "표시 모드", + "settings.status_bar.network_speed_mode.both": "업로드 + 다운로드", + "settings.status_bar.network_speed_mode.upload": "업로드만", + "settings.status_bar.network_speed_mode.download": "다운로드만", + "settings.status_bar.network_speed_transparent_background_label": "투명 배경", + "settings.status_bar.show_network_type_icon_label": "네트워크 유형 아이콘 표시", + "settings.status_bar.shadow_header": "상태 표시줄 그림자", + "settings.status_bar.shadow_desc": "투명한 구성 요소의 가시성을 높이기 위해 상태 표시줄에 그림자 효과를 추가합니다.", + "settings.status_bar.shadow_enabled_label": "그림자 활성화", + "settings.status_bar.shadow_color_label": "그림자 색상", + "settings.status_bar.shadow_opacity_label": "그림자 불투명도", + "settings.status_bar.theme_header": "상태 표시줄 테마", + "settings.status_bar.theme_desc": "상태 표시줄의 테마 모드를 독립적으로 설정합니다.", + "settings.status_bar.theme_mode_label": "테마 모드", + "settings.status_bar.theme_mode.follow_global": "전역 따르기", + "settings.status_bar.theme_mode.dark": "다크", + "settings.status_bar.theme_mode.light": "라이트", "settings.components.title": "컴포넌트", "settings.components.description": "컴포넌트 레이아웃과 모서리 디자인을 조정합니다.", "settings.components.grid_header": "그리드 설정", diff --git a/LanMountainDesktop/Localization/zh-CN.json b/LanMountainDesktop/Localization/zh-CN.json index f5a3097..7b58b80 100644 --- a/LanMountainDesktop/Localization/zh-CN.json +++ b/LanMountainDesktop/Localization/zh-CN.json @@ -395,6 +395,29 @@ "settings.status_bar.text_capsule_position.right": "靠右", "settings.status_bar.text_capsule_content_label": "文字内容(支持 Markdown)", "settings.status_bar.text_capsule_transparent_background_label": "透明背景", + "settings.status_bar.network_speed_header": "网速显示", + "settings.status_bar.network_speed_description": "在状态栏显示实时网络上传和下载速度。", + "settings.status_bar.network_speed_position_label": "网速显示位置", + "settings.status_bar.network_speed_position.left": "靠左", + "settings.status_bar.network_speed_position.center": "居中", + "settings.status_bar.network_speed_position.right": "靠右", + "settings.status_bar.network_speed_mode_label": "显示模式", + "settings.status_bar.network_speed_mode.both": "上传 + 下载", + "settings.status_bar.network_speed_mode.upload": "仅上传", + "settings.status_bar.network_speed_mode.download": "仅下载", + "settings.status_bar.network_speed_transparent_background_label": "透明背景", + "settings.status_bar.show_network_type_icon_label": "显示网络类型图标", + "settings.status_bar.shadow_header": "状态栏阴影", + "settings.status_bar.shadow_desc": "为状态栏添加阴影效果,使透明背景的组件更清晰。", + "settings.status_bar.shadow_enabled_label": "启用阴影", + "settings.status_bar.shadow_color_label": "阴影颜色", + "settings.status_bar.shadow_opacity_label": "阴影透明度", + "settings.status_bar.theme_header": "状态栏主题", + "settings.status_bar.theme_desc": "独立设置状态栏的主题模式。", + "settings.status_bar.theme_mode_label": "主题模式", + "settings.status_bar.theme_mode.follow_global": "跟随全局", + "settings.status_bar.theme_mode.dark": "暗色", + "settings.status_bar.theme_mode.light": "浅色", "settings.components.title": "组件", "settings.components.description": "调整组件布局与圆角设计。", "settings.components.grid_header": "网格设置", diff --git a/LanMountainDesktop/Models/AppSettingsSnapshot.cs b/LanMountainDesktop/Models/AppSettingsSnapshot.cs index 828d5a7..f01fbd4 100644 --- a/LanMountainDesktop/Models/AppSettingsSnapshot.cs +++ b/LanMountainDesktop/Models/AppSettingsSnapshot.cs @@ -114,6 +114,8 @@ public sealed class AppSettingsSnapshot public string ClockPosition { get; set; } = "Left"; // Left, Center, Right + public string ClockFontSize { get; set; } = "Medium"; // Small, Medium, Large + public bool ShowTextCapsule { get; set; } = false; public string TextCapsuleContent { get; set; } = "**Hello** World!"; @@ -122,8 +124,28 @@ public sealed class AppSettingsSnapshot public bool TextCapsuleTransparentBackground { get; set; } = false; + public string TextCapsuleFontSize { get; set; } = "Medium"; // Small, Medium, Large + + public bool ShowNetworkSpeed { get; set; } = false; + + public string NetworkSpeedPosition { get; set; } = "Right"; // Left, Center, Right + + public string NetworkSpeedDisplayMode { get; set; } = "Both"; // Upload, Download, Both + + public bool NetworkSpeedTransparentBackground { get; set; } = false; + + public bool ShowNetworkTypeIcon { get; set; } = false; + + public string NetworkSpeedFontSize { get; set; } = "Medium"; // Small, Medium, Large + public string StatusBarSpacingMode { get; set; } = "Relaxed"; + public bool StatusBarShadowEnabled { get; set; } = false; + + public string StatusBarShadowColor { get; set; } = "#000000"; + + public double StatusBarShadowOpacity { get; set; } = 0.3; + public int StatusBarCustomSpacingPercent { get; set; } = 12; public bool EnableThreeFingerSwipe { get; set; } = false; diff --git a/LanMountainDesktop/Services/IRecommendationDataService.cs b/LanMountainDesktop/Services/IRecommendationDataService.cs index e0822fa..89a0710 100644 --- a/LanMountainDesktop/Services/IRecommendationDataService.cs +++ b/LanMountainDesktop/Services/IRecommendationDataService.cs @@ -317,11 +317,11 @@ public sealed record RecommendationApiOptions public string ClassIslandHubApiUrl { get; init; } = "https://api.github.com/repos/ClassIsland/classisland-hub/contents/images"; - public string SectlHubApiUrl { get; init; } = "https://api.github.com/repos/SECTL/SECTL-hub/contents/images"; + public string SectlHubApiUrl { get; init; } = "https://api.github.com/repos/SECTL/SECTL-hub/contents/docs/.vuepress/public/images"; public string ClassIslandHubRawUrlTemplate { get; init; } = "https://raw.githubusercontent.com/ClassIsland/classisland-hub/main/images/{0}"; - public string SectlHubRawUrlTemplate { get; init; } = "https://raw.githubusercontent.com/SECTL/SECTL-hub/main/images/{0}"; + public string SectlHubRawUrlTemplate { get; init; } = "https://raw.githubusercontent.com/SECTL/SECTL-hub/main/docs/.vuepress/public/images/{0}"; public string RinLitHubRawUrlTemplate { get; init; } = "https://raw.githubusercontent.com/RinLit-233-shiroko/Rin-sHub/main/images/{0}"; } diff --git a/LanMountainDesktop/Services/RecommendationDataService.cs b/LanMountainDesktop/Services/RecommendationDataService.cs index 5e3252e..c04fb3c 100644 --- a/LanMountainDesktop/Services/RecommendationDataService.cs +++ b/LanMountainDesktop/Services/RecommendationDataService.cs @@ -3244,11 +3244,11 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis private async Task FetchZhiJiaoHubSnapshotAsync(string source, string mirrorSource, CancellationToken cancellationToken) { - var (owner, repo, path) = source switch + var (owner, repo, path, rawUrlTemplate) = source switch { - ZhiJiaoHubSources.Sectl => ("SECTL", "SECTL-hub", "docs/.vuepress/public/images"), - ZhiJiaoHubSources.RinLit => ("RinLit-233-shiroko", "Rin-sHub", "images"), - _ => ("ClassIsland", "classisland-hub", "images") + ZhiJiaoHubSources.Sectl => ("SECTL", "SECTL-hub", "docs/.vuepress/public/images", _options.SectlHubRawUrlTemplate), + ZhiJiaoHubSources.RinLit => ("RinLit-233-shiroko", "Rin-sHub", "images", _options.RinLitHubRawUrlTemplate), + _ => ("ClassIsland", "classisland-hub", "images", _options.ClassIslandHubRawUrlTemplate) }; var contentsUrl = $"https://api.github.com/repos/{owner}/{repo}/contents/{path}"; @@ -3261,7 +3261,7 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis try { - var images = await FetchImagesFromContentsApi(owner, repo, path, contentsUrl, mirrorSource, cancellationToken); + var images = await FetchImagesFromContentsApi(owner, repo, path, rawUrlTemplate, contentsUrl, mirrorSource, cancellationToken); if (images.Count == 0) { @@ -3291,7 +3291,7 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis } } - private async Task> FetchImagesFromContentsApi(string owner, string repo, string path, string contentsUrl, string mirrorSource, CancellationToken cancellationToken) + private async Task> FetchImagesFromContentsApi(string owner, string repo, string path, string rawUrlTemplate, string contentsUrl, string mirrorSource, CancellationToken cancellationToken) { var images = new List(); @@ -3362,7 +3362,9 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis } else { - imageUrl = $"https://raw.githubusercontent.com/{owner}/{repo}/main/{path}/{Uri.EscapeDataString(name)}"; + // 使用为每个数据源专门配置的 raw URL 模板 + // 注意:模板已经包含了正确的路径,只需要传入文件名 + imageUrl = string.Format(rawUrlTemplate, Uri.EscapeDataString(name)); } // 应用镜像加速到图片 URL diff --git a/LanMountainDesktop/Services/Settings/SettingsContracts.cs b/LanMountainDesktop/Services/Settings/SettingsContracts.cs index 32b2bf5..57090ca 100644 --- a/LanMountainDesktop/Services/Settings/SettingsContracts.cs +++ b/LanMountainDesktop/Services/Settings/SettingsContracts.cs @@ -42,12 +42,23 @@ public sealed record StatusBarSettingsState( string ClockDisplayFormat, bool ClockTransparentBackground, string ClockPosition, + string ClockFontSize, bool ShowTextCapsule, string TextCapsuleContent, string TextCapsulePosition, bool TextCapsuleTransparentBackground, + string TextCapsuleFontSize, + bool ShowNetworkSpeed, + string NetworkSpeedPosition, + string NetworkSpeedDisplayMode, + bool NetworkSpeedTransparentBackground, + bool ShowNetworkTypeIcon, + string NetworkSpeedFontSize, string SpacingMode, - int CustomSpacingPercent); + int CustomSpacingPercent, + bool ShadowEnabled, + string ShadowColor, + double ShadowOpacity); public sealed record TextCapsuleSettingsState( bool ShowTextCapsule, diff --git a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs index e895f50..06fd6d5 100644 --- a/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs +++ b/LanMountainDesktop/Services/Settings/SettingsDomainServices.cs @@ -387,12 +387,23 @@ internal sealed class StatusBarSettingsService : IStatusBarSettingsService snapshot.ClockDisplayFormat, snapshot.StatusBarClockTransparentBackground, snapshot.ClockPosition, + snapshot.ClockFontSize, snapshot.ShowTextCapsule, snapshot.TextCapsuleContent, snapshot.TextCapsulePosition, snapshot.TextCapsuleTransparentBackground, + snapshot.TextCapsuleFontSize, + snapshot.ShowNetworkSpeed, + snapshot.NetworkSpeedPosition, + snapshot.NetworkSpeedDisplayMode, + snapshot.NetworkSpeedTransparentBackground, + snapshot.ShowNetworkTypeIcon, + snapshot.NetworkSpeedFontSize, snapshot.StatusBarSpacingMode, - snapshot.StatusBarCustomSpacingPercent); + snapshot.StatusBarCustomSpacingPercent, + snapshot.StatusBarShadowEnabled, + snapshot.StatusBarShadowColor, + snapshot.StatusBarShadowOpacity); } public void Save(StatusBarSettingsState state) @@ -405,12 +416,23 @@ internal sealed class StatusBarSettingsService : IStatusBarSettingsService snapshot.ClockDisplayFormat = state.ClockDisplayFormat; snapshot.StatusBarClockTransparentBackground = state.ClockTransparentBackground; snapshot.ClockPosition = state.ClockPosition; + snapshot.ClockFontSize = state.ClockFontSize; snapshot.ShowTextCapsule = state.ShowTextCapsule; snapshot.TextCapsuleContent = state.TextCapsuleContent; snapshot.TextCapsulePosition = state.TextCapsulePosition; snapshot.TextCapsuleTransparentBackground = state.TextCapsuleTransparentBackground; + snapshot.TextCapsuleFontSize = state.TextCapsuleFontSize; + snapshot.ShowNetworkSpeed = state.ShowNetworkSpeed; + snapshot.NetworkSpeedPosition = state.NetworkSpeedPosition; + snapshot.NetworkSpeedDisplayMode = state.NetworkSpeedDisplayMode; + snapshot.NetworkSpeedTransparentBackground = state.NetworkSpeedTransparentBackground; + snapshot.ShowNetworkTypeIcon = state.ShowNetworkTypeIcon; + snapshot.NetworkSpeedFontSize = state.NetworkSpeedFontSize; snapshot.StatusBarSpacingMode = state.SpacingMode; snapshot.StatusBarCustomSpacingPercent = state.CustomSpacingPercent; + snapshot.StatusBarShadowEnabled = state.ShadowEnabled; + snapshot.StatusBarShadowColor = state.ShadowColor; + snapshot.StatusBarShadowOpacity = state.ShadowOpacity; _settingsService.SaveSnapshot( SettingsScope.App, snapshot, @@ -423,12 +445,23 @@ internal sealed class StatusBarSettingsService : IStatusBarSettingsService nameof(AppSettingsSnapshot.ClockDisplayFormat), nameof(AppSettingsSnapshot.StatusBarClockTransparentBackground), nameof(AppSettingsSnapshot.ClockPosition), + nameof(AppSettingsSnapshot.ClockFontSize), nameof(AppSettingsSnapshot.ShowTextCapsule), nameof(AppSettingsSnapshot.TextCapsuleContent), nameof(AppSettingsSnapshot.TextCapsulePosition), nameof(AppSettingsSnapshot.TextCapsuleTransparentBackground), + nameof(AppSettingsSnapshot.TextCapsuleFontSize), + nameof(AppSettingsSnapshot.ShowNetworkSpeed), + nameof(AppSettingsSnapshot.NetworkSpeedPosition), + nameof(AppSettingsSnapshot.NetworkSpeedDisplayMode), + nameof(AppSettingsSnapshot.NetworkSpeedTransparentBackground), + nameof(AppSettingsSnapshot.ShowNetworkTypeIcon), + nameof(AppSettingsSnapshot.NetworkSpeedFontSize), nameof(AppSettingsSnapshot.StatusBarSpacingMode), - nameof(AppSettingsSnapshot.StatusBarCustomSpacingPercent) + nameof(AppSettingsSnapshot.StatusBarCustomSpacingPercent), + nameof(AppSettingsSnapshot.StatusBarShadowEnabled), + nameof(AppSettingsSnapshot.StatusBarShadowColor), + nameof(AppSettingsSnapshot.StatusBarShadowOpacity) ]); } } diff --git a/LanMountainDesktop/ViewModels/StatusBarSettingsPageViewModel.cs b/LanMountainDesktop/ViewModels/StatusBarSettingsPageViewModel.cs index 161fd18..73955d9 100644 --- a/LanMountainDesktop/ViewModels/StatusBarSettingsPageViewModel.cs +++ b/LanMountainDesktop/ViewModels/StatusBarSettingsPageViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Media; using CommunityToolkit.Mvvm.ComponentModel; using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.Services; @@ -22,7 +23,11 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase ClockFormats = CreateClockFormats(); ClockPositions = CreateClockPositions(); + ClockFontSizes = CreateFontSizes(); TextCapsulePositions = CreateTextCapsulePositions(); + NetworkSpeedPositions = CreateNetworkSpeedPositions(); + NetworkSpeedDisplayModes = CreateNetworkSpeedDisplayModes(); + NetworkSpeedFontSizes = CreateFontSizes(); SpacingModes = CreateSpacingModes(); RefreshLocalizedText(); @@ -37,8 +42,16 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase public IReadOnlyList TextCapsulePositions { get; } + public IReadOnlyList NetworkSpeedPositions { get; } + + public IReadOnlyList NetworkSpeedDisplayModes { get; } + public IReadOnlyList SpacingModes { get; } + public IReadOnlyList ClockFontSizes { get; } + + public IReadOnlyList NetworkSpeedFontSizes { get; } + [ObservableProperty] private bool _showClock = true; @@ -87,6 +100,12 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase [ObservableProperty] private string _clockPositionLabel = string.Empty; + [ObservableProperty] + private SelectionOption _selectedClockFontSize = new("Medium", "Medium"); + + [ObservableProperty] + private string _clockFontSizeLabel = string.Empty; + [ObservableProperty] private string _textCapsuleHeader = string.Empty; @@ -114,6 +133,45 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase [ObservableProperty] private string _textCapsuleTransparentBackgroundLabel = string.Empty; + [ObservableProperty] + private string _networkSpeedHeader = string.Empty; + + [ObservableProperty] + private string _networkSpeedDescription = string.Empty; + + [ObservableProperty] + private bool _showNetworkSpeed; + + [ObservableProperty] + private SelectionOption _selectedNetworkSpeedPosition = new("Right", "Right"); + + [ObservableProperty] + private SelectionOption _selectedNetworkSpeedDisplayMode = new("Both", "Both"); + + [ObservableProperty] + private bool _networkSpeedTransparentBackground; + + [ObservableProperty] + private string _networkSpeedPositionLabel = string.Empty; + + [ObservableProperty] + private string _networkSpeedDisplayModeLabel = string.Empty; + + [ObservableProperty] + private string _networkSpeedTransparentBackgroundLabel = string.Empty; + + [ObservableProperty] + private bool _showNetworkTypeIcon; + + [ObservableProperty] + private string _showNetworkTypeIconLabel = string.Empty; + + [ObservableProperty] + private SelectionOption _selectedNetworkSpeedFontSize = new("Medium", "Medium"); + + [ObservableProperty] + private string _networkSpeedFontSizeLabel = string.Empty; + [ObservableProperty] private string _spacingHeader = string.Empty; @@ -123,6 +181,32 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase [ObservableProperty] private string _customSpacingLabel = string.Empty; + [ObservableProperty] + private bool _statusBarShadowEnabled; + + [ObservableProperty] + private Color _statusBarShadowColor = Colors.Black; + + [ObservableProperty] + private double _statusBarShadowOpacity = 30; + + public IBrush StatusBarShadowColorBrush => new SolidColorBrush(StatusBarShadowColor); + + [ObservableProperty] + private string _statusBarShadowHeader = string.Empty; + + [ObservableProperty] + private string _statusBarShadowDescription = string.Empty; + + [ObservableProperty] + private string _statusBarShadowEnabledLabel = string.Empty; + + [ObservableProperty] + private string _statusBarShadowColorLabel = string.Empty; + + [ObservableProperty] + private string _statusBarShadowOpacityLabel = string.Empty; + public void Load() { var state = _settingsFacade.StatusBar.Get(); @@ -143,6 +227,12 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase string.Equals(option.Value, clockPosition, StringComparison.OrdinalIgnoreCase)) ?? ClockPositions[0]; + // 时钟字体大小设置 + var clockFontSize = NormalizeFontSize(state.ClockFontSize); + SelectedClockFontSize = ClockFontSizes.FirstOrDefault(option => + string.Equals(option.Value, clockFontSize, StringComparison.OrdinalIgnoreCase)) + ?? ClockFontSizes[1]; // 默认中等 + // 文字胶囊设置 ShowTextCapsule = state.ShowTextCapsule; TextCapsuleContent = state.TextCapsuleContent ?? "**Hello** World!"; @@ -152,12 +242,39 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase ?? TextCapsulePositions[2]; // 默认靠右 TextCapsuleTransparentBackground = state.TextCapsuleTransparentBackground; + // 网速设置 + ShowNetworkSpeed = state.ShowNetworkSpeed; + var networkSpeedPosition = NormalizeNetworkSpeedPosition(state.NetworkSpeedPosition); + SelectedNetworkSpeedPosition = NetworkSpeedPositions.FirstOrDefault(option => + string.Equals(option.Value, networkSpeedPosition, StringComparison.OrdinalIgnoreCase)) + ?? NetworkSpeedPositions[2]; // 默认靠右 + var networkSpeedDisplayMode = NormalizeNetworkSpeedDisplayMode(state.NetworkSpeedDisplayMode); + SelectedNetworkSpeedDisplayMode = NetworkSpeedDisplayModes.FirstOrDefault(option => + string.Equals(option.Value, networkSpeedDisplayMode, StringComparison.OrdinalIgnoreCase)) + ?? NetworkSpeedDisplayModes[0]; // 默认双向 + NetworkSpeedTransparentBackground = state.NetworkSpeedTransparentBackground; + ShowNetworkTypeIcon = state.ShowNetworkTypeIcon; + + // 网速字体大小设置 + var networkSpeedFontSize = NormalizeFontSize(state.NetworkSpeedFontSize); + SelectedNetworkSpeedFontSize = NetworkSpeedFontSizes.FirstOrDefault(option => + string.Equals(option.Value, networkSpeedFontSize, StringComparison.OrdinalIgnoreCase)) + ?? NetworkSpeedFontSizes[1]; // 默认中等 + var spacingMode = NormalizeSpacingMode(state.SpacingMode); SelectedSpacingMode = SpacingModes.FirstOrDefault(option => string.Equals(option.Value, spacingMode, StringComparison.OrdinalIgnoreCase)) ?? SpacingModes[1]; CustomSpacingPercent = Math.Clamp(state.CustomSpacingPercent, 0, 30); IsCustomSpacingVisible = string.Equals(SelectedSpacingMode.Value, "Custom", StringComparison.OrdinalIgnoreCase); + + // 状态栏阴影设置 + StatusBarShadowEnabled = state.ShadowEnabled; + if (Color.TryParse(state.ShadowColor, out var shadowColor)) + { + StatusBarShadowColor = shadowColor; + } + StatusBarShadowOpacity = Math.Clamp(state.ShadowOpacity * 100, 0, 100); } partial void OnShowClockChanged(bool value) @@ -200,6 +317,16 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase Save(); } + partial void OnSelectedClockFontSizeChanged(SelectionOption value) + { + if (_isInitializing || value is null) + { + return; + } + + Save(); + } + partial void OnShowTextCapsuleChanged(bool value) { if (_isInitializing) @@ -240,6 +367,66 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase Save(); } + partial void OnShowNetworkSpeedChanged(bool value) + { + if (_isInitializing) + { + return; + } + + Save(); + } + + partial void OnSelectedNetworkSpeedPositionChanged(SelectionOption value) + { + if (_isInitializing || value is null) + { + return; + } + + Save(); + } + + partial void OnSelectedNetworkSpeedDisplayModeChanged(SelectionOption value) + { + if (_isInitializing || value is null) + { + return; + } + + Save(); + } + + partial void OnNetworkSpeedTransparentBackgroundChanged(bool value) + { + if (_isInitializing) + { + return; + } + + Save(); + } + + partial void OnShowNetworkTypeIconChanged(bool value) + { + if (_isInitializing) + { + return; + } + + Save(); + } + + partial void OnSelectedNetworkSpeedFontSizeChanged(SelectionOption value) + { + if (_isInitializing || value is null) + { + return; + } + + Save(); + } + partial void OnSelectedSpacingModeChanged(SelectionOption value) { IsCustomSpacingVisible = string.Equals(value?.Value, "Custom", StringComparison.OrdinalIgnoreCase); @@ -268,6 +455,37 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase Save(); } + partial void OnStatusBarShadowEnabledChanged(bool value) + { + if (_isInitializing) + { + return; + } + + Save(); + } + + partial void OnStatusBarShadowColorChanged(Color value) + { + OnPropertyChanged(nameof(StatusBarShadowColorBrush)); + if (_isInitializing) + { + return; + } + + Save(); + } + + partial void OnStatusBarShadowOpacityChanged(double value) + { + if (_isInitializing) + { + return; + } + + Save(); + } + private void Save() { var state = _settingsFacade.StatusBar.Get(); @@ -288,12 +506,23 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase SelectedClockFormat.Value, ClockTransparentBackground, SelectedClockPosition.Value, + SelectedClockFontSize?.Value ?? "Medium", ShowTextCapsule, TextCapsuleContent ?? "**Hello** World!", SelectedTextCapsulePosition?.Value ?? "Right", TextCapsuleTransparentBackground, + "Medium", // TextCapsuleFontSize - 暂时使用默认值 + ShowNetworkSpeed, + SelectedNetworkSpeedPosition?.Value ?? "Right", + SelectedNetworkSpeedDisplayMode?.Value ?? "Both", + NetworkSpeedTransparentBackground, + ShowNetworkTypeIcon, + SelectedNetworkSpeedFontSize?.Value ?? "Medium", NormalizeSpacingMode(SelectedSpacingMode.Value), - Math.Clamp(CustomSpacingPercent, 0, 30))); + Math.Clamp(CustomSpacingPercent, 0, 30), + StatusBarShadowEnabled, + StatusBarShadowColor.ToString(), + StatusBarShadowOpacity / 100.0)); } private IReadOnlyList CreateClockFormats() @@ -325,6 +554,26 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase ]; } + private IReadOnlyList CreateNetworkSpeedPositions() + { + return + [ + new SelectionOption("Left", L("settings.status_bar.network_speed_position.left", "Left")), + new SelectionOption("Center", L("settings.status_bar.network_speed_position.center", "Center")), + new SelectionOption("Right", L("settings.status_bar.network_speed_position.right", "Right")) + ]; + } + + private IReadOnlyList CreateNetworkSpeedDisplayModes() + { + return + [ + new SelectionOption("Both", L("settings.status_bar.network_speed_mode.both", "Upload + Download")), + new SelectionOption("Upload", L("settings.status_bar.network_speed_mode.upload", "Upload only")), + new SelectionOption("Download", L("settings.status_bar.network_speed_mode.download", "Download only")) + ]; + } + private IReadOnlyList CreateSpacingModes() { return @@ -346,14 +595,27 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase ClockTransparentBackgroundLabel = L("settings.status_bar.clock_transparent_background_label", "Transparent background"); ClockTransparentBackgroundDescription = L("settings.status_bar.clock_transparent_background_desc", "Remove the capsule background and keep only the clock text."); ClockPositionLabel = L("settings.status_bar.clock_position_label", "Clock position"); + ClockFontSizeLabel = L("settings.status_bar.clock_font_size_label", "Font size"); TextCapsuleHeader = L("settings.status_bar.text_capsule_header", "Text Capsule"); TextCapsuleDescription = L("settings.status_bar.text_capsule_description", "Display custom text with Markdown support on the status bar."); TextCapsulePositionLabel = L("settings.status_bar.text_capsule_position_label", "Text capsule position"); TextCapsuleContentLabel = L("settings.status_bar.text_capsule_content_label", "Text content (Markdown supported)"); TextCapsuleTransparentBackgroundLabel = L("settings.status_bar.text_capsule_transparent_background_label", "Transparent background"); + NetworkSpeedHeader = L("settings.status_bar.network_speed_header", "Network Speed"); + NetworkSpeedDescription = L("settings.status_bar.network_speed_description", "Display real-time network upload and download speed."); + NetworkSpeedPositionLabel = L("settings.status_bar.network_speed_position_label", "Network speed position"); + NetworkSpeedDisplayModeLabel = L("settings.status_bar.network_speed_mode_label", "Display mode"); + NetworkSpeedTransparentBackgroundLabel = L("settings.status_bar.network_speed_transparent_background_label", "Transparent background"); + ShowNetworkTypeIconLabel = L("settings.status_bar.show_network_type_icon_label", "Show network type icon"); + NetworkSpeedFontSizeLabel = L("settings.status_bar.network_speed_font_size_label", "Font size"); SpacingHeader = L("settings.status_bar.spacing_header", "Component Spacing"); SpacingDescription = L("settings.status_bar.spacing_desc", "Adjust spacing between status bar components."); CustomSpacingLabel = L("settings.status_bar.spacing_custom_label", "Custom spacing (%)"); + StatusBarShadowHeader = L("settings.status_bar.shadow_header", "Status Bar Shadow"); + StatusBarShadowDescription = L("settings.status_bar.shadow_desc", "Add shadow effect to the status bar for better visibility."); + StatusBarShadowEnabledLabel = L("settings.status_bar.shadow_enabled_label", "Enable shadow"); + StatusBarShadowColorLabel = L("settings.status_bar.shadow_color_label", "Shadow color"); + StatusBarShadowOpacityLabel = L("settings.status_bar.shadow_opacity_label", "Shadow opacity"); } private string NormalizeSpacingMode(string? value) @@ -386,6 +648,46 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase }; } + private static string NormalizeNetworkSpeedPosition(string? value) + { + return value switch + { + _ when string.Equals(value, "Left", StringComparison.OrdinalIgnoreCase) => "Left", + _ when string.Equals(value, "Center", StringComparison.OrdinalIgnoreCase) => "Center", + _ => "Right" + }; + } + + private static string NormalizeNetworkSpeedDisplayMode(string? value) + { + return value switch + { + _ when string.Equals(value, "Upload", StringComparison.OrdinalIgnoreCase) => "Upload", + _ when string.Equals(value, "Download", StringComparison.OrdinalIgnoreCase) => "Download", + _ => "Both" + }; + } + + private static string NormalizeFontSize(string? value) + { + return value switch + { + _ when string.Equals(value, "Small", StringComparison.OrdinalIgnoreCase) => "Small", + _ when string.Equals(value, "Large", StringComparison.OrdinalIgnoreCase) => "Large", + _ => "Medium" + }; + } + + private IReadOnlyList CreateFontSizes() + { + return + [ + new SelectionOption("Small", L("settings.status_bar.font_size.small", "Small")), + new SelectionOption("Medium", L("settings.status_bar.font_size.medium", "Medium")), + new SelectionOption("Large", L("settings.status_bar.font_size.large", "Large")) + ]; + } + private string L(string key, string fallback) => _localizationService.GetString(_languageCode, key, fallback); } diff --git a/LanMountainDesktop/Views/Components/ClockWidget.axaml b/LanMountainDesktop/Views/Components/ClockWidget.axaml index c7f067a..bcad9c6 100644 --- a/LanMountainDesktop/Views/Components/ClockWidget.axaml +++ b/LanMountainDesktop/Views/Components/ClockWidget.axaml @@ -1,4 +1,4 @@ - _fontSize; + set + { + _fontSize = value; + ApplyCellSize(_lastAppliedCellSize); + } + } + + public void SetFontSize(string fontSize) + { + WidgetFontSize = fontSize; + } + public void SetTimeZoneService(TimeZoneService timeZoneService) { ClearTimeZoneService(); @@ -138,7 +154,14 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo // 3. 核心:满盈字阶 (Filled Typography) // 使主时间文字占据容器高度的 ~68%,产生饱满的视觉张力 - var mainFontSize = targetHeight * 0.68; + // 根据字体大小设置调整基础大小 + var fontSizeMultiplier = _fontSize switch + { + "Small" => 0.55, + "Large" => 0.85, + _ => 0.68 // Medium (default) + }; + var mainFontSize = targetHeight * fontSizeMultiplier; MainTimeTextBlock.FontSize = mainFontSize; MainTimeTextBlock.FontWeight = FontWeight.SemiBold; diff --git a/LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml b/LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml new file mode 100644 index 0000000..6d6f7ac --- /dev/null +++ b/LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml.cs b/LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml.cs new file mode 100644 index 0000000..b3b16c8 --- /dev/null +++ b/LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml.cs @@ -0,0 +1,451 @@ +using System; +using System.Linq; +using System.Net.NetworkInformation; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Threading; +using FluentIcons.Avalonia; +using FluentIcons.Common; +using LanMountainDesktop.Services; +using Symbol = FluentIcons.Common.Symbol; + +namespace LanMountainDesktop.Views.Components; + +public partial class NetworkSpeedWidget : UserControl, IDesktopComponentWidget +{ + private readonly DispatcherTimer _timer = new(); + private readonly DispatcherTimer _networkTypeTimer = new(); + private NetworkInterface? _selectedInterface; + private long _lastBytesReceived; + private long _lastBytesSent; + private bool _isFirstUpdate = true; + private double _lastAppliedCellSize = 100; + private bool _transparentBackground; + private string _displayMode = "Both"; // "Upload", "Download", "Both" + private bool _showNetworkTypeIcon; + private string _fontSize = "Medium"; // Small, Medium, Large + + public NetworkSpeedWidget() + { + InitializeComponent(); + SetupTimer(); + SelectBestInterface(); + UpdateDisplayMode(); + UpdateNetworkTypeIcon(); + } + + public string DisplayMode + { + get => _displayMode; + set + { + if (_displayMode == value) return; + _displayMode = value; + UpdateDisplayMode(); + } + } + + public bool TransparentBackground + { + get => _transparentBackground; + set + { + if (_transparentBackground == value) return; + _transparentBackground = value; + ApplyChrome(); + ApplyCellSize(_lastAppliedCellSize); + } + } + + public bool ShowNetworkTypeIcon + { + get => _showNetworkTypeIcon; + set + { + if (_showNetworkTypeIcon == value) return; + _showNetworkTypeIcon = value; + UpdateNetworkTypeIcon(); + } + } + + public void SetDisplayMode(string mode) + { + DisplayMode = mode; + } + + public void SetTransparentBackground(bool transparent) + { + TransparentBackground = transparent; + } + + public void SetShowNetworkTypeIcon(bool show) + { + ShowNetworkTypeIcon = show; + } + + public string WidgetFontSize + { + get => _fontSize; + set + { + _fontSize = value; + ApplyCellSize(_lastAppliedCellSize); + } + } + + public void SetFontSize(string fontSize) + { + WidgetFontSize = fontSize; + } + + private void SetupTimer() + { + // 网速更新定时器(每秒) + _timer.Interval = TimeSpan.FromSeconds(1); + _timer.Tick += (_, _) => UpdateSpeed(); + _timer.Start(); + + // 网络类型检测定时器(每500ms,满足响应延迟要求) + _networkTypeTimer.Interval = TimeSpan.FromMilliseconds(500); + _networkTypeTimer.Tick += (_, _) => UpdateNetworkTypeIcon(); + _networkTypeTimer.Start(); + } + + private void SelectBestInterface() + { + try + { + var interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(ni => ni.OperationalStatus == OperationalStatus.Up) + .Where(ni => ni.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Where(ni => ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel) + .Where(ni => !ni.Description.Contains("Virtual", StringComparison.OrdinalIgnoreCase)) + .Where(ni => !ni.Description.Contains("VPN", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + // 优先选择有流量的物理网卡 + _selectedInterface = interfaces + .OrderByDescending(ni => ni.GetIPv4Statistics().BytesReceived + ni.GetIPv4Statistics().BytesSent) + .FirstOrDefault(); + + // 如果没有找到,选择第一个活动的非虚拟网卡 + _selectedInterface ??= interfaces.FirstOrDefault(); + + if (_selectedInterface != null) + { + var stats = _selectedInterface.GetIPv4Statistics(); + _lastBytesReceived = stats.BytesReceived; + _lastBytesSent = stats.BytesSent; + } + } + catch + { + // 忽略错误,下次重试 + } + } + + private void UpdateSpeed() + { + try + { + // 如果当前网卡不可用,尝试重新选择 + if (_selectedInterface == null || + _selectedInterface.OperationalStatus != OperationalStatus.Up) + { + SelectBestInterface(); + } + + if (_selectedInterface == null) + { + UploadSpeedTextBlock.Text = "--"; + DownloadSpeedTextBlock.Text = "--"; + return; + } + + var stats = _selectedInterface.GetIPv4Statistics(); + var currentBytesReceived = stats.BytesReceived; + var currentBytesSent = stats.BytesSent; + + if (_isFirstUpdate) + { + _lastBytesReceived = currentBytesReceived; + _lastBytesSent = currentBytesSent; + _isFirstUpdate = false; + return; + } + + // 计算速度(每秒字节数) + var downloadBytes = currentBytesReceived - _lastBytesReceived; + var uploadBytes = currentBytesSent - _lastBytesSent; + + // 处理计数器重置的情况 + if (downloadBytes < 0) downloadBytes = 0; + if (uploadBytes < 0) uploadBytes = 0; + + UploadSpeedTextBlock.Text = FormatSpeed(uploadBytes); + DownloadSpeedTextBlock.Text = FormatSpeed(downloadBytes); + + _lastBytesReceived = currentBytesReceived; + _lastBytesSent = currentBytesSent; + } + catch + { + // 错误时显示 -- + UploadSpeedTextBlock.Text = "--"; + DownloadSpeedTextBlock.Text = "--"; + } + } + + private void UpdateNetworkTypeIcon() + { + try + { + if (!_showNetworkTypeIcon || NetworkTypeIcon == null) + { + if (NetworkTypeIcon != null) + NetworkTypeIcon.IsVisible = false; + return; + } + + // 获取当前活动的网络接口 + var activeInterface = GetActiveNetworkInterface(); + + if (activeInterface == null) + { + // 无网络连接 + NetworkTypeIcon.Symbol = Symbol.DismissCircle; + NetworkTypeIcon.IsVisible = true; + return; + } + + // 根据网络类型设置图标 + switch (activeInterface.NetworkInterfaceType) + { + case NetworkInterfaceType.Wireless80211: + // WiFi + NetworkTypeIcon.Symbol = Symbol.WiFi; + break; + + case NetworkInterfaceType.Ethernet: + // 有线网络 - 检查是否是移动网络热点 + if (IsLikelyMobileHotspot(activeInterface)) + { + NetworkTypeIcon.Symbol = Symbol.Phone; + } + else + { + NetworkTypeIcon.Symbol = Symbol.PlugConnected; + } + break; + + default: + // 其他类型,尝试根据描述判断 + var symbol = GetSymbolFromDescription(activeInterface.Description); + NetworkTypeIcon.Symbol = symbol; + break; + } + + NetworkTypeIcon.IsVisible = true; + } + catch + { + // 错误时隐藏图标 + if (NetworkTypeIcon != null) + NetworkTypeIcon.IsVisible = false; + } + } + + private NetworkInterface? GetActiveNetworkInterface() + { + try + { + // 优先使用当前选中的网卡 + if (_selectedInterface != null && + _selectedInterface.OperationalStatus == OperationalStatus.Up) + { + return _selectedInterface; + } + + // 否则查找最佳网卡 + var interfaces = NetworkInterface.GetAllNetworkInterfaces() + .Where(ni => ni.OperationalStatus == OperationalStatus.Up) + .Where(ni => ni.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .Where(ni => ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel) + .ToList(); + + // 优先返回有流量的网卡 + return interfaces + .OrderByDescending(ni => ni.GetIPv4Statistics().BytesReceived + ni.GetIPv4Statistics().BytesSent) + .FirstOrDefault(); + } + catch + { + return null; + } + } + + private static bool IsLikelyMobileHotspot(NetworkInterface ni) + { + // 通过描述判断是否是移动热点 + var desc = ni.Description.ToLowerInvariant(); + return desc.Contains("mobile") || + desc.Contains("cellular") || + desc.Contains("phone") || + desc.Contains("tether"); + } + + private static Symbol GetSymbolFromDescription(string description) + { + var desc = description.ToLowerInvariant(); + + if (desc.Contains("wifi") || desc.Contains("wi-fi") || desc.Contains("wireless")) + return Symbol.WiFi; + + if (desc.Contains("ethernet") || desc.Contains("lan") || desc.Contains("wired")) + return Symbol.PlugConnected; + + if (desc.Contains("cellular") || desc.Contains("mobile") || desc.Contains("lte") || desc.Contains("5g") || desc.Contains("4g")) + return Symbol.Phone; + + if (desc.Contains("bluetooth")) + return Symbol.Bluetooth; + + // 默认使用 Globe 图标 + return Symbol.Globe; + } + + private static string FormatSpeed(long bytesPerSecond) + { + // 根据数值大小决定显示格式,始终保持3个字符宽度 + // 例如: 1.23, 12.3, 123 + return bytesPerSecond switch + { + >= 1024 * 1024 * 1024 => FormatWithThreeDigits(bytesPerSecond / (1024.0 * 1024.0 * 1024.0), "G"), + >= 1024 * 1024 => FormatWithThreeDigits(bytesPerSecond / (1024.0 * 1024.0), "M"), + >= 1024 => FormatWithThreeDigits(bytesPerSecond / 1024.0, "K"), + _ => FormatWithThreeDigits(bytesPerSecond, "B") + }; + } + + /// + /// 格式化数字,始终保持3个有效数字的显示宽度 + /// + private static string FormatWithThreeDigits(double value, string unit) + { + // 根据数值大小决定小数位数,确保总宽度一致 + // < 10: 显示两位小数 (如 1.23) + // 10-99: 显示一位小数 (如 12.3) + // >= 100: 显示整数 (如 123) + string formatted = value switch + { + < 10 => $"{value:F2}", + < 100 => $"{value:F1}", + _ => $"{value:F0}" + }; + + return formatted + unit; + } + + private void UpdateDisplayMode() + { + switch (_displayMode) + { + case "Upload": + UploadPanel.IsVisible = true; + DownloadPanel.IsVisible = false; + Separator.IsVisible = false; + break; + case "Download": + UploadPanel.IsVisible = false; + DownloadPanel.IsVisible = true; + Separator.IsVisible = false; + break; + case "Both": + default: + UploadPanel.IsVisible = true; + DownloadPanel.IsVisible = true; + Separator.IsVisible = true; + break; + } + } + + public void ApplyCellSize(double cellSize) + { + _lastAppliedCellSize = cellSize; + + // 计算组件高度:保持与任务栏核心比例一致 (0.74x) + var targetHeight = Math.Clamp(cellSize * 0.74, 34, 74); + RootBorder.Height = targetHeight; + + // 主矩形统一到主题主档圆角 + RootBorder.CornerRadius = ResolveUnifiedMainRectangle(); + RootBorder.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center; + + // 根据单元格大小和字体大小设置调整字体大小 + var fontSizeMultiplier = _fontSize switch + { + "Small" => 0.32, + "Large" => 0.48, + _ => 0.4 // Medium (default) + }; + var fontSize = Math.Clamp(targetHeight * fontSizeMultiplier, 11, 22); + UploadSpeedTextBlock.FontSize = fontSize; + DownloadSpeedTextBlock.FontSize = fontSize; + + // 调整图标大小 + if (NetworkTypeIcon != null) + { + NetworkTypeIcon.FontSize = Math.Clamp(targetHeight * 0.35, 10, 18); + } + + // 设置最小和最大宽度 + RootBorder.MinWidth = cellSize * 1.5; + RootBorder.MaxWidth = cellSize * 5; + + if (_transparentBackground) + { + RootBorder.MinWidth = 0; + RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.06, 4, 10), 0); + return; + } + + // 确保清除可能存在的固定 Padding,由代码控制"紧密感" + RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.15, 12, 24), 0); + } + + private void ApplyChrome() + { + if (_transparentBackground) + { + RootBorder.Classes.Remove("glass-panel"); + RootBorder.Background = Brushes.Transparent; + RootBorder.BorderBrush = Brushes.Transparent; + RootBorder.BorderThickness = new Thickness(0); + RootBorder.BoxShadow = default; + return; + } + + if (!RootBorder.Classes.Contains("glass-panel")) + { + RootBorder.Classes.Add("glass-panel"); + } + + RootBorder.ClearValue(Border.BackgroundProperty); + RootBorder.ClearValue(Border.BorderBrushProperty); + RootBorder.ClearValue(Border.BorderThicknessProperty); + RootBorder.ClearValue(Border.BoxShadowProperty); + } + + private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue()); + + private static double ResolveUnifiedMainRadiusValue() => + HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft; + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + _timer?.Stop(); + _networkTypeTimer?.Stop(); + } +} diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs index e647f9b..06250d2 100644 --- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs +++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs @@ -365,14 +365,29 @@ public partial class MainWindow : ClockDisplayFormat.HourMinuteSecond; _statusBarClockTransparentBackground = snapshot.StatusBarClockTransparentBackground; _clockPosition = NormalizeClockPosition(snapshot.ClockPosition); + _clockFontSize = NormalizeFontSize(snapshot.ClockFontSize); _showTextCapsule = snapshot.ShowTextCapsule; _textCapsuleContent = snapshot.TextCapsuleContent ?? "**Hello** World!"; _textCapsulePosition = NormalizeTextCapsulePosition(snapshot.TextCapsulePosition); _textCapsuleTransparentBackground = snapshot.TextCapsuleTransparentBackground; + _textCapsuleFontSize = NormalizeFontSize(snapshot.TextCapsuleFontSize); + + _showNetworkSpeed = snapshot.ShowNetworkSpeed; + _networkSpeedPosition = NormalizeNetworkSpeedPosition(snapshot.NetworkSpeedPosition); + _networkSpeedDisplayMode = NormalizeNetworkSpeedDisplayMode(snapshot.NetworkSpeedDisplayMode); + _networkSpeedTransparentBackground = snapshot.NetworkSpeedTransparentBackground; + _showNetworkTypeIcon = snapshot.ShowNetworkTypeIcon; + _networkSpeedFontSize = NormalizeFontSize(snapshot.NetworkSpeedFontSize); + + _statusBarShadowEnabled = snapshot.StatusBarShadowEnabled; + _statusBarShadowColor = snapshot.StatusBarShadowColor ?? "#000000"; + _statusBarShadowOpacity = snapshot.StatusBarShadowOpacity; ApplyClockSettingsToAllWidgets(); ApplyTextCapsuleSettingsToAllWidgets(); + ApplyNetworkSpeedSettingsToAllWidgets(); + ApplyStatusBarShadow(); } private void ApplyClockSettingsToAllWidgets() @@ -381,16 +396,19 @@ public partial class MainWindow { ClockWidgetLeft.SetDisplayFormat(_clockDisplayFormat); ClockWidgetLeft.SetTransparentBackground(_statusBarClockTransparentBackground); + ClockWidgetLeft.SetFontSize(_clockFontSize); } if (ClockWidgetCenter is not null) { ClockWidgetCenter.SetDisplayFormat(_clockDisplayFormat); ClockWidgetCenter.SetTransparentBackground(_statusBarClockTransparentBackground); + ClockWidgetCenter.SetFontSize(_clockFontSize); } if (ClockWidgetRight is not null) { ClockWidgetRight.SetDisplayFormat(_clockDisplayFormat); ClockWidgetRight.SetTransparentBackground(_statusBarClockTransparentBackground); + ClockWidgetRight.SetFontSize(_clockFontSize); } } @@ -404,6 +422,16 @@ public partial class MainWindow }; } + private static string NormalizeFontSize(string? value) + { + return value switch + { + _ when string.Equals(value, "Small", StringComparison.OrdinalIgnoreCase) => "Small", + _ when string.Equals(value, "Large", StringComparison.OrdinalIgnoreCase) => "Large", + _ => "Medium" + }; + } + private void ApplyTextCapsuleSettingsToAllWidgets() { if (TextCapsuleWidgetLeft is not null) @@ -433,6 +461,90 @@ public partial class MainWindow }; } + private void ApplyNetworkSpeedSettingsToAllWidgets() + { + if (NetworkSpeedWidgetLeft is not null) + { + NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode); + NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetLeft.SetShowNetworkTypeIcon(_showNetworkTypeIcon); + NetworkSpeedWidgetLeft.SetFontSize(_networkSpeedFontSize); + } + if (NetworkSpeedWidgetCenter is not null) + { + NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode); + NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetCenter.SetShowNetworkTypeIcon(_showNetworkTypeIcon); + NetworkSpeedWidgetCenter.SetFontSize(_networkSpeedFontSize); + } + if (NetworkSpeedWidgetRight is not null) + { + NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode); + NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetRight.SetShowNetworkTypeIcon(_showNetworkTypeIcon); + NetworkSpeedWidgetRight.SetFontSize(_networkSpeedFontSize); + } + } + + private static string NormalizeNetworkSpeedPosition(string? value) + { + return value switch + { + _ when string.Equals(value, "Left", StringComparison.OrdinalIgnoreCase) => "Left", + _ when string.Equals(value, "Center", StringComparison.OrdinalIgnoreCase) => "Center", + _ => "Right" + }; + } + + private static string NormalizeNetworkSpeedDisplayMode(string? value) + { + return value switch + { + _ when string.Equals(value, "Upload", StringComparison.OrdinalIgnoreCase) => "Upload", + _ when string.Equals(value, "Download", StringComparison.OrdinalIgnoreCase) => "Download", + _ => "Both" + }; + } + + private void ApplyStatusBarShadow() + { + if (StatusBarOverlay is null) + { + return; + } + + if (_statusBarShadowEnabled) + { + if (Color.TryParse(_statusBarShadowColor, out var shadowColor)) + { + var opacity = Math.Clamp(_statusBarShadowOpacity, 0, 1); + + StatusBarOverlay.IsVisible = true; + + var gradientBrush = new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), + EndPoint = new RelativePoint(0, 1, RelativeUnit.Relative) + }; + + var alpha1 = (byte)(shadowColor.A * opacity * 0.8); + var alpha2 = (byte)(shadowColor.A * opacity * 0.4); + var color1 = Color.FromArgb(alpha1, shadowColor.R, shadowColor.G, shadowColor.B); + var color2 = Color.FromArgb(alpha2, shadowColor.R, shadowColor.G, shadowColor.B); + + gradientBrush.GradientStops.Add(new GradientStop(color1, 0.0)); + gradientBrush.GradientStops.Add(new GradientStop(color2, 0.3)); + gradientBrush.GradientStops.Add(new GradientStop(Colors.Transparent, 1.0)); + + StatusBarOverlay.Background = gradientBrush; + } + } + else + { + StatusBarOverlay.IsVisible = false; + } + } + /// /// 检测状态栏组件是否会发生碰撞 /// @@ -676,6 +788,14 @@ public partial class MainWindow if (TextCapsuleWidgetRight is not null) TextCapsuleWidgetRight.IsVisible = false; + // 先隐藏所有网速控件 + if (NetworkSpeedWidgetLeft is not null) + NetworkSpeedWidgetLeft.IsVisible = false; + if (NetworkSpeedWidgetCenter is not null) + NetworkSpeedWidgetCenter.IsVisible = false; + if (NetworkSpeedWidgetRight is not null) + NetworkSpeedWidgetRight.IsVisible = false; + // 根据位置设置显示对应的时钟控件(带碰撞检测) if (showClock) { @@ -770,6 +890,53 @@ public partial class MainWindow } } + // 根据位置设置显示对应的网速控件(带碰撞检测) + if (_showNetworkSpeed) + { + var targetPosition = _networkSpeedPosition; + var canAdd = CanAddComponentAtPosition(targetPosition); + + if (canAdd) + { + var targetNetworkSpeed = targetPosition switch + { + "Left" => NetworkSpeedWidgetLeft, + "Center" => NetworkSpeedWidgetCenter, + _ => NetworkSpeedWidgetRight + }; + + if (targetNetworkSpeed is not null) + { + targetNetworkSpeed.IsVisible = true; + targetNetworkSpeed.SetTransparentBackground(_networkSpeedTransparentBackground); + targetNetworkSpeed.SetDisplayMode(_networkSpeedDisplayMode); + hasVisibleTopStatusComponent = true; + } + } + else + { + // 如果目标位置无法添加,尝试其他位置 + var alternativePosition = FindAlternativePosition(targetPosition); + if (alternativePosition is not null) + { + var targetNetworkSpeed = alternativePosition switch + { + "Left" => NetworkSpeedWidgetLeft, + "Center" => NetworkSpeedWidgetCenter, + _ => NetworkSpeedWidgetRight + }; + + if (targetNetworkSpeed is not null) + { + targetNetworkSpeed.IsVisible = true; + targetNetworkSpeed.SetTransparentBackground(_networkSpeedTransparentBackground); + targetNetworkSpeed.SetDisplayMode(_networkSpeedDisplayMode); + hasVisibleTopStatusComponent = true; + } + } + } + } + if (TopStatusBarHost is not null) { TopStatusBarHost.IsVisible = hasVisibleTopStatusComponent; @@ -871,6 +1038,82 @@ public partial class MainWindow TextCapsuleWidgetCenter.IsVisible = false; } } + + // 调整网速组件位置(优先级:时钟 > 文字胶囊 > 网速) + if (NetworkSpeedWidgetLeft?.IsVisible == true && WouldComponentsCollide()) + { + // 尝试将左侧网速移到中间 + if (CanAddComponentAtPosition("Center")) + { + NetworkSpeedWidgetLeft.IsVisible = false; + NetworkSpeedWidgetCenter!.IsVisible = true; + NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode); + } + // 或者移到右侧 + else if (CanAddComponentAtPosition("Right")) + { + NetworkSpeedWidgetLeft.IsVisible = false; + NetworkSpeedWidgetRight!.IsVisible = true; + NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode); + } + // 如果都无法添加,则隐藏网速 + else + { + NetworkSpeedWidgetLeft.IsVisible = false; + } + } + + if (NetworkSpeedWidgetRight?.IsVisible == true && WouldComponentsCollide()) + { + // 尝试将右侧网速移到中间 + if (CanAddComponentAtPosition("Center")) + { + NetworkSpeedWidgetRight.IsVisible = false; + NetworkSpeedWidgetCenter!.IsVisible = true; + NetworkSpeedWidgetCenter.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetCenter.SetDisplayMode(_networkSpeedDisplayMode); + } + // 或者移到左侧 + else if (CanAddComponentAtPosition("Left")) + { + NetworkSpeedWidgetRight.IsVisible = false; + NetworkSpeedWidgetLeft!.IsVisible = true; + NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode); + } + // 如果都无法添加,则隐藏网速 + else + { + NetworkSpeedWidgetRight.IsVisible = false; + } + } + + if (NetworkSpeedWidgetCenter?.IsVisible == true && WouldComponentsCollide()) + { + // 尝试将中间网速移到左侧 + if (CanAddComponentAtPosition("Left")) + { + NetworkSpeedWidgetCenter.IsVisible = false; + NetworkSpeedWidgetLeft!.IsVisible = true; + NetworkSpeedWidgetLeft.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetLeft.SetDisplayMode(_networkSpeedDisplayMode); + } + // 或者移到右侧 + else if (CanAddComponentAtPosition("Right")) + { + NetworkSpeedWidgetCenter.IsVisible = false; + NetworkSpeedWidgetRight!.IsVisible = true; + NetworkSpeedWidgetRight.SetTransparentBackground(_networkSpeedTransparentBackground); + NetworkSpeedWidgetRight.SetDisplayMode(_networkSpeedDisplayMode); + } + // 如果都无法添加,则隐藏网速 + else + { + NetworkSpeedWidgetCenter.IsVisible = false; + } + } } /// diff --git a/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs b/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs index 58bd638..3276772 100644 --- a/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs +++ b/LanMountainDesktop/Views/MainWindow.SettingsHardCut.Stubs.cs @@ -650,8 +650,24 @@ public partial class MainWindow TaskbarLayoutMode = _taskbarLayoutMode, ClockDisplayFormat = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? "HourMinute" : "HourMinuteSecond", StatusBarClockTransparentBackground = _statusBarClockTransparentBackground, + ClockPosition = _clockPosition, + ClockFontSize = _clockFontSize, + ShowTextCapsule = _showTextCapsule, + TextCapsuleContent = _textCapsuleContent, + TextCapsulePosition = _textCapsulePosition, + TextCapsuleTransparentBackground = _textCapsuleTransparentBackground, + TextCapsuleFontSize = _textCapsuleFontSize, + ShowNetworkSpeed = _showNetworkSpeed, + NetworkSpeedPosition = _networkSpeedPosition, + NetworkSpeedDisplayMode = _networkSpeedDisplayMode, + NetworkSpeedTransparentBackground = _networkSpeedTransparentBackground, + ShowNetworkTypeIcon = _showNetworkTypeIcon, + NetworkSpeedFontSize = _networkSpeedFontSize, StatusBarSpacingMode = _statusBarSpacingMode, StatusBarCustomSpacingPercent = _statusBarCustomSpacingPercent, + StatusBarShadowEnabled = _statusBarShadowEnabled, + StatusBarShadowColor = _statusBarShadowColor, + StatusBarShadowOpacity = _statusBarShadowOpacity, DisabledPluginIds = existingSnapshot.DisabledPluginIds, StudyFrameMs = existingSnapshot.StudyFrameMs, StudyScoreThresholdDbfs = existingSnapshot.StudyScoreThresholdDbfs, diff --git a/LanMountainDesktop/Views/MainWindow.axaml b/LanMountainDesktop/Views/MainWindow.axaml index a7b3d2b..7790b32 100644 --- a/LanMountainDesktop/Views/MainWindow.axaml +++ b/LanMountainDesktop/Views/MainWindow.axaml @@ -226,13 +226,34 @@ + + + + + + + + + + + + Padding="4" + ZIndex="2"> + + + diff --git a/LanMountainDesktop/Views/MainWindow.axaml.cs b/LanMountainDesktop/Views/MainWindow.axaml.cs index 28f8ca3..75e15c5 100644 --- a/LanMountainDesktop/Views/MainWindow.axaml.cs +++ b/LanMountainDesktop/Views/MainWindow.axaml.cs @@ -136,10 +136,21 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider private int _statusBarCustomSpacingPercent = 12; private bool _statusBarClockTransparentBackground; private string _clockPosition = "Left"; // Left, Center, Right + private string _clockFontSize = "Medium"; // Small, Medium, Large private bool _showTextCapsule; private string _textCapsuleContent = "**Hello** World!"; private string _textCapsulePosition = "Right"; // Left, Center, Right private bool _textCapsuleTransparentBackground; + private string _textCapsuleFontSize = "Medium"; // Small, Medium, Large + private bool _showNetworkSpeed; + private string _networkSpeedPosition = "Right"; // Left, Center, Right + private string _networkSpeedDisplayMode = "Both"; // Upload, Download, Both + private bool _networkSpeedTransparentBackground; + private bool _showNetworkTypeIcon; + private string _networkSpeedFontSize = "Medium"; // Small, Medium, Large + private bool _statusBarShadowEnabled; + private string _statusBarShadowColor = "#000000"; + private double _statusBarShadowOpacity = 0.3; private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent; private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle; private string _languageCode = "zh-CN"; @@ -720,6 +731,13 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider TextCapsuleWidgetRight.Margin = new Thickness(0); TextCapsuleWidgetRight.ApplyCellSize(cellSize); + NetworkSpeedWidgetLeft.Margin = new Thickness(0); + NetworkSpeedWidgetLeft.ApplyCellSize(cellSize); + NetworkSpeedWidgetCenter.Margin = new Thickness(0); + NetworkSpeedWidgetCenter.ApplyCellSize(cellSize); + NetworkSpeedWidgetRight.Margin = new Thickness(0); + NetworkSpeedWidgetRight.ApplyCellSize(cellSize); + var buttonMinWidth = Math.Clamp(taskbarCellHeight * 2.35, 100, 340); BackToWindowsButton.Margin = new Thickness(0); @@ -763,6 +781,9 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider TextCapsuleWidgetLeft.ApplyCellSize(_currentDesktopCellSize); TextCapsuleWidgetCenter.ApplyCellSize(_currentDesktopCellSize); TextCapsuleWidgetRight.ApplyCellSize(_currentDesktopCellSize); + NetworkSpeedWidgetLeft.ApplyCellSize(_currentDesktopCellSize); + NetworkSpeedWidgetCenter.ApplyCellSize(_currentDesktopCellSize); + NetworkSpeedWidgetRight.ApplyCellSize(_currentDesktopCellSize); } } diff --git a/LanMountainDesktop/Views/SettingsPages/StatusBarSettingsPage.axaml b/LanMountainDesktop/Views/SettingsPages/StatusBarSettingsPage.axaml index 7eab8a0..cf21741 100644 --- a/LanMountainDesktop/Views/SettingsPages/StatusBarSettingsPage.axaml +++ b/LanMountainDesktop/Views/SettingsPages/StatusBarSettingsPage.axaml @@ -70,6 +70,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +