mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
Compare commits
4 Commits
1c3cc76f21
...
5d2449fa8f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2449fa8f | ||
|
|
00339f0ed0 | ||
|
|
021c7ff245 | ||
|
|
675096b6c4 |
@@ -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",
|
||||
@@ -1052,7 +1075,9 @@
|
||||
"zhijiaohub.settings.source": "Image Source",
|
||||
"zhijiaohub.settings.classisland": "ClassIsland Gallery",
|
||||
"zhijiaohub.settings.sectl": "SECTL Gallery",
|
||||
"zhijiaohub.settings.source_desc": "Select the image source. ClassIsland Gallery contains fun moments from the ClassIsland community, SECTL Gallery contains content from the SECTL community.",
|
||||
"zhijiaohub.settings.rinlit": "Rin's Gallery",
|
||||
"zhijiaohub.settings.jiangtokoto": "Jiangtokoto Memes",
|
||||
"zhijiaohub.settings.source_desc": "Select the image source. ClassIsland Gallery contains fun moments from the ClassIsland community, SECTL Gallery contains content from the SECTL community, Rin's Gallery contains content from Rin's community, Jiangtokoto Memes contains rich meme resources.",
|
||||
"zhijiaohub.settings.mirror_source": "Mirror Acceleration",
|
||||
"zhijiaohub.settings.mirror_direct": "Direct (GitHub)",
|
||||
"zhijiaohub.settings.mirror_ghproxy": "Mirror Acceleration (Recommended)",
|
||||
|
||||
@@ -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": "グリッド設定",
|
||||
|
||||
@@ -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": "그리드 설정",
|
||||
|
||||
@@ -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": "网格设置",
|
||||
@@ -1046,7 +1069,9 @@
|
||||
"zhijiaohub.settings.source": "图片源",
|
||||
"zhijiaohub.settings.classisland": "ClassIsland 图库",
|
||||
"zhijiaohub.settings.sectl": "SECTL 图库",
|
||||
"zhijiaohub.settings.source_desc": "选择图片来源。ClassIsland 图库包含 ClassIsland 社区的趣味瞬间,SECTL 图库包含 SECTL 社区的内容。",
|
||||
"zhijiaohub.settings.rinlit": "Rin's 图库",
|
||||
"zhijiaohub.settings.jiangtokoto": "Jiangtokoto 表情包",
|
||||
"zhijiaohub.settings.source_desc": "选择图片来源。ClassIsland 图库包含 ClassIsland 社区的趣味瞬间,SECTL 图库包含 SECTL 社区的内容,Rin's 图库包含 Rin's 社区的内容,Jiangtokoto 表情包包含丰富的表情包资源。",
|
||||
"zhijiaohub.settings.mirror_source": "镜像加速",
|
||||
"zhijiaohub.settings.mirror_direct": "直连(GitHub)",
|
||||
"zhijiaohub.settings.mirror_ghproxy": "镜像加速(推荐)",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -125,6 +125,7 @@ public static class ZhiJiaoHubSources
|
||||
public const string ClassIsland = "classisland";
|
||||
public const string Sectl = "sectl";
|
||||
public const string RinLit = "rinlit";
|
||||
public const string Jiangtokoto = "jiangtokoto";
|
||||
|
||||
public static string Normalize(string? value)
|
||||
{
|
||||
@@ -132,9 +133,74 @@ public static class ZhiJiaoHubSources
|
||||
{
|
||||
"sectl" => Sectl,
|
||||
"rinlit" => RinLit,
|
||||
"jiangtokoto" => Jiangtokoto,
|
||||
_ => ClassIsland
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetDisplayName(string source)
|
||||
{
|
||||
return source?.ToLowerInvariant() switch
|
||||
{
|
||||
Sectl => "SECTL 图库",
|
||||
RinLit => "Rin's 图库",
|
||||
Jiangtokoto => "Jiangtokoto 表情包",
|
||||
_ => "ClassIsland 图库"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 智教Hub数据源配置
|
||||
public sealed class ZhiJiaoHubSourceConfig
|
||||
{
|
||||
public string Owner { get; init; } = string.Empty;
|
||||
public string Repo { get; init; } = string.Empty;
|
||||
public string Path { get; init; } = string.Empty;
|
||||
public string DisplayName { get; init; } = string.Empty;
|
||||
public bool UseJsonIndex { get; init; } = false;
|
||||
public string? JsonIndexPath { get; init; } = null;
|
||||
public string ApiUrl => $"https://api.github.com/repos/{Owner}/{Repo}/contents/{Path}";
|
||||
public string RawUrlTemplate => $"https://raw.githubusercontent.com/{Owner}/{Repo}/main/{Path}/{{0}}";
|
||||
public string? JsonIndexUrl => JsonIndexPath != null
|
||||
? $"https://raw.githubusercontent.com/{Owner}/{Repo}/main/{JsonIndexPath}"
|
||||
: null;
|
||||
|
||||
public static ZhiJiaoHubSourceConfig GetConfig(string source)
|
||||
{
|
||||
return source?.ToLowerInvariant() switch
|
||||
{
|
||||
ZhiJiaoHubSources.Sectl => new ZhiJiaoHubSourceConfig
|
||||
{
|
||||
Owner = "SECTL",
|
||||
Repo = "SECTL-hub",
|
||||
Path = "docs/.vuepress/public/images",
|
||||
DisplayName = "SECTL 图库"
|
||||
},
|
||||
ZhiJiaoHubSources.RinLit => new ZhiJiaoHubSourceConfig
|
||||
{
|
||||
Owner = "RinLit-233-shiroko",
|
||||
Repo = "Rin-sHub",
|
||||
Path = "updates/images",
|
||||
DisplayName = "Rin's 图库",
|
||||
UseJsonIndex = true,
|
||||
JsonIndexPath = "updates/images.json"
|
||||
},
|
||||
ZhiJiaoHubSources.Jiangtokoto => new ZhiJiaoHubSourceConfig
|
||||
{
|
||||
Owner = "unDefFtr",
|
||||
Repo = "jiangtokoto-images",
|
||||
Path = "images",
|
||||
DisplayName = "Jiangtokoto 表情包"
|
||||
},
|
||||
_ => new ZhiJiaoHubSourceConfig
|
||||
{
|
||||
Owner = "ClassIsland",
|
||||
Repo = "classisland-hub",
|
||||
Path = "images",
|
||||
DisplayName = "ClassIsland 图库"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 智教Hub镜像加速源常量
|
||||
|
||||
@@ -317,11 +317,13 @@ 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 RinLitHubApiUrl { get; init; } = "https://api.github.com/repos/RinLit-233-shiroko/Rin-sHub/contents/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}";
|
||||
}
|
||||
|
||||
@@ -3244,35 +3244,38 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
|
||||
private async Task<ZhiJiaoHubSnapshot> FetchZhiJiaoHubSnapshotAsync(string source, string mirrorSource, CancellationToken cancellationToken)
|
||||
{
|
||||
var (owner, repo, path) = source switch
|
||||
{
|
||||
ZhiJiaoHubSources.Sectl => ("SECTL", "SECTL-hub", "docs/.vuepress/public/images"),
|
||||
ZhiJiaoHubSources.RinLit => ("RinLit-233-shiroko", "Rin-sHub", "images"),
|
||||
_ => ("ClassIsland", "classisland-hub", "images")
|
||||
};
|
||||
|
||||
var contentsUrl = $"https://api.github.com/repos/{owner}/{repo}/contents/{path}";
|
||||
|
||||
// 如果使用镜像加速,代理 GitHub API 请求
|
||||
if (string.Equals(mirrorSource, ZhiJiaoHubMirrorSources.GhProxy, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
contentsUrl = ZhiJiaoHubMirrorSources.GhProxyBaseUrl.TrimEnd('/') + "/" + contentsUrl;
|
||||
}
|
||||
var config = ZhiJiaoHubSourceConfig.GetConfig(source);
|
||||
|
||||
try
|
||||
{
|
||||
var images = await FetchImagesFromContentsApi(owner, repo, path, contentsUrl, mirrorSource, cancellationToken);
|
||||
List<ZhiJiaoHubImageItem> images;
|
||||
|
||||
// 如果使用JSON索引模式(Rin's Hub)
|
||||
if (config.UseJsonIndex && !string.IsNullOrEmpty(config.JsonIndexUrl))
|
||||
{
|
||||
images = await FetchImagesFromJsonIndex(config, mirrorSource, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 标准模式(ClassIsland/SECTL)
|
||||
var contentsUrl = config.ApiUrl;
|
||||
|
||||
if (string.Equals(mirrorSource, ZhiJiaoHubMirrorSources.GhProxy, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
contentsUrl = ZhiJiaoHubMirrorSources.GhProxyBaseUrl.TrimEnd('/') + "/" + contentsUrl;
|
||||
}
|
||||
|
||||
images = await FetchImagesFromContentsApi(config, contentsUrl, mirrorSource, cancellationToken);
|
||||
}
|
||||
|
||||
if (images.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("未找到图片文件");
|
||||
throw new InvalidOperationException($"在 {config.DisplayName} 中未找到图片文件");
|
||||
}
|
||||
|
||||
// 随机打乱图片顺序
|
||||
var random = new Random();
|
||||
var shuffled = images.OrderBy(_ => random.Next()).ToList();
|
||||
|
||||
// 重新设置索引
|
||||
for (int i = 0; i < shuffled.Count; i++)
|
||||
{
|
||||
var item = shuffled[i];
|
||||
@@ -3287,11 +3290,15 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new HttpRequestException($"获取图片列表失败: {ex.Message}");
|
||||
throw new HttpRequestException($"从 {config.DisplayName} 获取图片列表失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<ZhiJiaoHubImageItem>> FetchImagesFromContentsApi(string owner, string repo, string path, string contentsUrl, string mirrorSource, CancellationToken cancellationToken)
|
||||
private async Task<List<ZhiJiaoHubImageItem>> FetchImagesFromContentsApi(
|
||||
ZhiJiaoHubSourceConfig config,
|
||||
string contentsUrl,
|
||||
string mirrorSource,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var images = new List<ZhiJiaoHubImageItem>();
|
||||
|
||||
@@ -3309,7 +3316,17 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
{
|
||||
throw new HttpRequestException("GitHub API 速率限制,请稍后重试");
|
||||
}
|
||||
throw new HttpRequestException($"API 返回错误: {(int)response.StatusCode} - {Truncate(errorText, 200)}");
|
||||
|
||||
if ((int)response.StatusCode == 404)
|
||||
{
|
||||
throw new HttpRequestException(
|
||||
$"在 {config.DisplayName} 中找不到图片目录。请检查仓库结构和路径配置。\n" +
|
||||
$"仓库: {config.Owner}/{config.Repo}\n" +
|
||||
$"路径: {config.Path}");
|
||||
}
|
||||
|
||||
throw new HttpRequestException(
|
||||
$"从 {config.DisplayName} 获取数据失败: {(int)response.StatusCode} - {Truncate(errorText, 200)}");
|
||||
}
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
@@ -3321,9 +3338,9 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
if (root.ValueKind == JsonValueKind.Object && root.TryGetProperty("message", out var messageNode))
|
||||
{
|
||||
var errorMessage = messageNode.GetString();
|
||||
throw new InvalidOperationException($"GitHub API 错误: {errorMessage}");
|
||||
throw new InvalidOperationException($"GitHub API 错误 ({config.DisplayName}): {errorMessage}");
|
||||
}
|
||||
throw new InvalidOperationException("Invalid response format from GitHub API.");
|
||||
throw new InvalidOperationException($"从 {config.DisplayName} 返回的数据格式无效");
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
@@ -3343,18 +3360,15 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
continue;
|
||||
}
|
||||
|
||||
// 只处理图片文件
|
||||
var extension = Path.GetExtension(name).ToLowerInvariant();
|
||||
if (extension != ".png" && extension != ".jpg" && extension != ".jpeg" && extension != ".gif" && extension != ".webp")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 解码文件名
|
||||
var decodedName = Uri.UnescapeDataString(name);
|
||||
decodedName = Path.GetFileNameWithoutExtension(decodedName);
|
||||
|
||||
// 构造图片 URL
|
||||
string imageUrl;
|
||||
if (!string.IsNullOrWhiteSpace(downloadUrl))
|
||||
{
|
||||
@@ -3362,10 +3376,12 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
}
|
||||
else
|
||||
{
|
||||
imageUrl = $"https://raw.githubusercontent.com/{owner}/{repo}/main/{path}/{Uri.EscapeDataString(name)}";
|
||||
imageUrl = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
config.RawUrlTemplate,
|
||||
Uri.EscapeDataString(name));
|
||||
}
|
||||
|
||||
// 应用镜像加速到图片 URL
|
||||
imageUrl = ZhiJiaoHubMirrorSources.ApplyMirror(imageUrl, mirrorSource);
|
||||
|
||||
images.Add(new ZhiJiaoHubImageItem(decodedName, imageUrl, index));
|
||||
@@ -3375,6 +3391,85 @@ public sealed class RecommendationDataService : IRecommendationInfoService, IDis
|
||||
return images;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JSON索引文件获取图片列表(Rin's Hub专用)
|
||||
/// </summary>
|
||||
private async Task<List<ZhiJiaoHubImageItem>> FetchImagesFromJsonIndex(
|
||||
ZhiJiaoHubSourceConfig config,
|
||||
string mirrorSource,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var images = new List<ZhiJiaoHubImageItem>();
|
||||
|
||||
// 下载JSON索引文件
|
||||
var jsonUrl = config.JsonIndexUrl!;
|
||||
if (string.Equals(mirrorSource, ZhiJiaoHubMirrorSources.GhProxy, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
jsonUrl = ZhiJiaoHubMirrorSources.GhProxyBaseUrl.TrimEnd('/') + "/" + jsonUrl;
|
||||
}
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, jsonUrl);
|
||||
request.Headers.TryAddWithoutValidation("User-Agent", "LanMountainDesktop/1.0");
|
||||
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var jsonText = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
using var document = JsonDocument.Parse(jsonText);
|
||||
var root = document.RootElement;
|
||||
|
||||
// 解析 hub_items 数组
|
||||
if (!root.TryGetProperty("hub_items", out var hubItems) || hubItems.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
throw new InvalidOperationException($"JSON索引文件格式无效:缺少 hub_items 数组");
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
foreach (var item in hubItems.EnumerateArray())
|
||||
{
|
||||
// 获取图片路径
|
||||
if (!item.TryGetProperty("image", out var imageProp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var imagePath = imageProp.GetString();
|
||||
if (string.IsNullOrWhiteSpace(imagePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取标题(用于显示名称)
|
||||
string title = string.Empty;
|
||||
if (item.TryGetProperty("title", out var titleProp))
|
||||
{
|
||||
title = titleProp.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
// 如果没有标题,使用文件名
|
||||
if (string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = Path.GetFileNameWithoutExtension(imagePath);
|
||||
}
|
||||
|
||||
// 构建完整的图片URL
|
||||
// imagePath 格式如: "Discord/姐姐好香.png"
|
||||
// 需要拼接为: https://raw.githubusercontent.com/.../updates/images/Discord/姐姐好香.png
|
||||
// 并对路径中的每个部分进行URL编码
|
||||
var pathParts = imagePath.Split('/');
|
||||
var encodedPath = string.Join("/", pathParts.Select(part => Uri.EscapeDataString(part)));
|
||||
var imageUrl = $"https://raw.githubusercontent.com/{config.Owner}/{config.Repo}/main/{config.Path}/{encodedPath}";
|
||||
|
||||
// 应用镜像加速
|
||||
imageUrl = ZhiJiaoHubMirrorSources.ApplyMirror(imageUrl, mirrorSource);
|
||||
|
||||
images.Add(new ZhiJiaoHubImageItem(title, imageUrl, index));
|
||||
index++;
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
private bool TryGetZhiJiaoHubFromCache(string cacheKey, out ZhiJiaoHubSnapshot snapshot)
|
||||
{
|
||||
lock (_cacheGate)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SelectionOption> TextCapsulePositions { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption> NetworkSpeedPositions { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption> NetworkSpeedDisplayModes { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption> SpacingModes { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption> ClockFontSizes { get; }
|
||||
|
||||
public IReadOnlyList<SelectionOption> 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<SelectionOption> CreateClockFormats()
|
||||
@@ -325,6 +554,26 @@ public sealed partial class StatusBarSettingsPageViewModel : ViewModelBase
|
||||
];
|
||||
}
|
||||
|
||||
private IReadOnlyList<SelectionOption> 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<SelectionOption> 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<SelectionOption> 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<SelectionOption> 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);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<ComboBoxItem x:Name="RinLitItem"
|
||||
Classes="component-editor-select-item"
|
||||
Tag="rinlit" />
|
||||
<ComboBoxItem x:Name="JiangtokotoItem"
|
||||
Classes="component-editor-select-item"
|
||||
Tag="jiangtokoto" />
|
||||
</ComboBox>
|
||||
<TextBlock x:Name="SourceDescriptionTextBlock"
|
||||
Classes="component-editor-secondary-text"
|
||||
|
||||
@@ -30,10 +30,11 @@ public partial class ZhiJiaoHubComponentEditor : ComponentEditorViewBase
|
||||
ClassIslandItem.Content = L("zhijiaohub.settings.classisland", "ClassIsland 图库");
|
||||
SectlItem.Content = L("zhijiaohub.settings.sectl", "SECTL 图库");
|
||||
RinLitItem.Content = L("zhijiaohub.settings.rinlit", "Rin's 图库");
|
||||
JiangtokotoItem.Content = L("zhijiaohub.settings.jiangtokoto", "Jiangtokoto 表情包");
|
||||
|
||||
// 数据源描述
|
||||
SourceDescriptionTextBlock.Text = L("zhijiaohub.settings.source_desc",
|
||||
"选择图片来源。ClassIsland 图库包含 ClassIsland 社区的趣味瞬间,SECTL 图库包含 SECTL 社区的内容,Rin's 图库包含 Rin's 社区的内容。");
|
||||
"选择图片来源。ClassIsland 图库包含 ClassIsland 社区的趣味瞬间,SECTL 图库包含 SECTL 社区的内容,Rin's 图库包含 Rin's 社区的内容,Jiangtokoto 表情包包含丰富的表情包资源。");
|
||||
|
||||
// 镜像加速源
|
||||
MirrorSourceLabelTextBlock.Text = L("zhijiaohub.settings.mirror_source", "镜像加速");
|
||||
@@ -67,6 +68,7 @@ public partial class ZhiJiaoHubComponentEditor : ComponentEditorViewBase
|
||||
{
|
||||
ZhiJiaoHubSources.Sectl => SectlItem,
|
||||
ZhiJiaoHubSources.RinLit => RinLitItem,
|
||||
ZhiJiaoHubSources.Jiangtokoto => JiangtokotoItem,
|
||||
_ => ClassIslandItem
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
@@ -25,6 +25,7 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
|
||||
private ClockDisplayFormat _displayFormat = ClockDisplayFormat.HourMinuteSecond;
|
||||
private bool _transparentBackground;
|
||||
private double _lastAppliedCellSize = 100;
|
||||
private string _fontSize = "Medium"; // Small, Medium, Large
|
||||
|
||||
public ClockWidget()
|
||||
{
|
||||
@@ -72,6 +73,21 @@ public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZo
|
||||
TransparentBackground = transparentBackground;
|
||||
}
|
||||
|
||||
public string WidgetFontSize
|
||||
{
|
||||
get => _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;
|
||||
|
||||
|
||||
72
LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml
Normal file
72
LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml
Normal file
@@ -0,0 +1,72 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:fi="using:FluentIcons.Avalonia"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="160"
|
||||
d:DesignHeight="48"
|
||||
x:Class="LanMountainDesktop.Views.Components.NetworkSpeedWidget">
|
||||
|
||||
<Border x:Name="RootBorder"
|
||||
Classes="surface-translucent-panel"
|
||||
Padding="0"
|
||||
CornerRadius="{DynamicResource DesignCornerRadiusComponent}">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0">
|
||||
<!-- 上传速度 -->
|
||||
<StackPanel x:Name="UploadPanel"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="↑"
|
||||
FontSize="12"
|
||||
Opacity="0.7"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"/>
|
||||
<TextBlock x:Name="UploadSpeedTextBlock"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Margin="2,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 分隔符 -->
|
||||
<Rectangle x:Name="Separator"
|
||||
Width="1"
|
||||
Height="16"
|
||||
Margin="8,0"
|
||||
Opacity="0.3"
|
||||
Fill="{DynamicResource AdaptiveTextSecondaryBrush}"/>
|
||||
|
||||
<!-- 下载速度 -->
|
||||
<StackPanel x:Name="DownloadPanel"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="↓"
|
||||
FontSize="12"
|
||||
Opacity="0.7"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"/>
|
||||
<TextBlock x:Name="DownloadSpeedTextBlock"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Margin="2,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 网络类型图标 -->
|
||||
<fi:SymbolIcon x:Name="NetworkTypeIcon"
|
||||
Symbol="Globe"
|
||||
FontSize="14"
|
||||
Margin="8,0,0,0"
|
||||
Opacity="0.8"
|
||||
IsVisible="False"
|
||||
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
451
LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml.cs
Normal file
451
LanMountainDesktop/Views/Components/NetworkSpeedWidget.axaml.cs
Normal file
@@ -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")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格式化数字,始终保持3个有效数字的显示宽度
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测状态栏组件是否会发生碰撞
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -226,13 +226,34 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 状态栏阴影层 - macOS 风格的完整阴影带 -->
|
||||
<Border x:Name="StatusBarOverlay"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="1"
|
||||
IsVisible="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Height="24"
|
||||
ZIndex="0"
|
||||
Margin="0,0,0,-24">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
|
||||
<GradientStop Color="#CC000000" Offset="0.0" />
|
||||
<GradientStop Color="#66000000" Offset="0.3" />
|
||||
<GradientStop Color="#00000000" Offset="1.0" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="TopStatusBarHost"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="1"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="4">
|
||||
Padding="4"
|
||||
ZIndex="2">
|
||||
<Grid ColumnDefinitions="*,Auto,*">
|
||||
<!-- 左侧状态栏组件 -->
|
||||
<StackPanel x:Name="TopStatusLeftPanel"
|
||||
@@ -246,6 +267,9 @@
|
||||
<comp:TextCapsuleWidget x:Name="TextCapsuleWidgetLeft"
|
||||
IsVisible="False"
|
||||
Margin="0" />
|
||||
<comp:NetworkSpeedWidget x:Name="NetworkSpeedWidgetLeft"
|
||||
IsVisible="False"
|
||||
Margin="0" />
|
||||
</StackPanel>
|
||||
<!-- 中间状态栏组件 -->
|
||||
<StackPanel x:Name="TopStatusCenterPanel"
|
||||
@@ -259,6 +283,9 @@
|
||||
<comp:TextCapsuleWidget x:Name="TextCapsuleWidgetCenter"
|
||||
IsVisible="False"
|
||||
Margin="0" />
|
||||
<comp:NetworkSpeedWidget x:Name="NetworkSpeedWidgetCenter"
|
||||
IsVisible="False"
|
||||
Margin="0" />
|
||||
</StackPanel>
|
||||
<!-- 右侧状态栏组件 -->
|
||||
<StackPanel x:Name="TopStatusRightPanel"
|
||||
@@ -272,6 +299,9 @@
|
||||
<comp:TextCapsuleWidget x:Name="TextCapsuleWidgetRight"
|
||||
IsVisible="False"
|
||||
Margin="0" />
|
||||
<comp:NetworkSpeedWidget x:Name="NetworkSpeedWidgetRight"
|
||||
IsVisible="False"
|
||||
Margin="0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,24 @@
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding ClockFontSizeLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="1"
|
||||
Width="220"
|
||||
IsEnabled="{Binding ShowClock}"
|
||||
ItemsSource="{Binding ClockFontSizes}"
|
||||
SelectedItem="{Binding SelectedClockFontSize}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SelectionOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<ui:SettingsExpander Header="{Binding TextCapsuleHeader}"
|
||||
@@ -126,6 +144,92 @@
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<ui:SettingsExpander Header="{Binding NetworkSpeedHeader}"
|
||||
Description="{Binding NetworkSpeedDescription}">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<fi:SymbolIconSource Symbol="ArrowBidirectionalUpDown" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ToggleSwitch IsChecked="{Binding ShowNetworkSpeed}" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding NetworkSpeedPositionLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="1"
|
||||
Width="220"
|
||||
IsEnabled="{Binding ShowNetworkSpeed}"
|
||||
ItemsSource="{Binding NetworkSpeedPositions}"
|
||||
SelectedItem="{Binding SelectedNetworkSpeedPosition}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SelectionOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding NetworkSpeedDisplayModeLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="1"
|
||||
Width="220"
|
||||
IsEnabled="{Binding ShowNetworkSpeed}"
|
||||
ItemsSource="{Binding NetworkSpeedDisplayModes}"
|
||||
SelectedItem="{Binding SelectedNetworkSpeedDisplayMode}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SelectionOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="*,Auto"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding NetworkSpeedTransparentBackgroundLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<ToggleSwitch Grid.Column="1"
|
||||
IsChecked="{Binding NetworkSpeedTransparentBackground}"
|
||||
IsEnabled="{Binding ShowNetworkSpeed}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="*,Auto"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding ShowNetworkTypeIconLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<ToggleSwitch Grid.Column="1"
|
||||
IsChecked="{Binding ShowNetworkTypeIcon}"
|
||||
IsEnabled="{Binding ShowNetworkSpeed}"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding NetworkSpeedFontSizeLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Column="1"
|
||||
Width="220"
|
||||
IsEnabled="{Binding ShowNetworkSpeed}"
|
||||
ItemsSource="{Binding NetworkSpeedFontSizes}"
|
||||
SelectedItem="{Binding SelectedNetworkSpeedFontSize}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SelectionOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<Separator Classes="settings-separator" />
|
||||
|
||||
<controls:IconText Icon="Apps"
|
||||
@@ -164,6 +268,55 @@
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
|
||||
<Separator Classes="settings-separator" />
|
||||
|
||||
<controls:IconText Icon="Square"
|
||||
Text="{Binding StatusBarShadowHeader}"
|
||||
Margin="0,0,0,4" />
|
||||
|
||||
<ui:SettingsExpander Header="{Binding StatusBarShadowHeader}"
|
||||
Description="{Binding StatusBarShadowDescription}">
|
||||
<ui:SettingsExpander.IconSource>
|
||||
<fi:SymbolIconSource Symbol="Square" />
|
||||
</ui:SettingsExpander.IconSource>
|
||||
<ui:SettingsExpander.Footer>
|
||||
<ToggleSwitch IsChecked="{Binding StatusBarShadowEnabled}" />
|
||||
</ui:SettingsExpander.Footer>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding StatusBarShadowColorLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
IsEnabled="{Binding StatusBarShadowEnabled}">
|
||||
<Border Width="32"
|
||||
Height="32"
|
||||
CornerRadius="4"
|
||||
Background="{Binding StatusBarShadowColorBrush}" />
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedRight">
|
||||
<ColorPicker Color="{Binding StatusBarShadowColor}" />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
<ui:SettingsExpanderItem>
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="16">
|
||||
<TextBlock Text="{Binding StatusBarShadowOpacityLabel}"
|
||||
VerticalAlignment="Center" />
|
||||
<Slider Grid.Column="1"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
TickFrequency="10"
|
||||
IsEnabled="{Binding StatusBarShadowEnabled}"
|
||||
Value="{Binding StatusBarShadowOpacity}" />
|
||||
</Grid>
|
||||
</ui:SettingsExpanderItem>
|
||||
</ui:SettingsExpander>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user