Compare commits

...
541 Commits
Author SHA1 Message Date
LTCat 6b9a50df91 2.99.0.0 正式版 2026-10-20 14:51:42 +08:00
LTCat e4b0489a22 2.99.0.0 2026-10-20 14:33:23 +08:00
LTCat 5ef81e490a 2.14.0.0 正式版 2026-10-19 23:50:04 +08:00
LTCat ade6e04c6e 2.14.0.0 2026-10-17 03:39:13 +08:00
HerobrineXia 9dcb799fb0 Merge pull request #9452 from camileko/close-9450
fix: 修复没有 单个自定义事件 的情况下无法触发自定义事件列表
2026-10-14 21:51:02 +08:00
HerobrineXia 073546ba19 Merge pull request #9447 from camileko/close-9442
fix: 联网主页地址含有替换标记时主页无法显示
2026-10-14 19:51:45 +08:00
HerobrineXia f8584954b8 Merge pull request #9469 from RyogiMutsuki/fix/naudio/license
fix(license): 修复 NAudio 许可证问题
2026-10-13 02:25:52 +08:00
HerobrineXia a91d5e0079 Merge pull request #9445 from camileko/close-9254
fix: 修复窗口大小为最大化时启动高版本 Minecraft 会出现 Hidden Utility Window
2026-10-13 02:18:35 +08:00
HerobrineXia 5dcbd570a8 Merge pull request #9414 from camileko/close-9413
feat(cape): 添加 Aurora Cape 披风翻译
2026-10-13 00:34:31 +08:00
Ryogi Mutsuki 87a89e877d merge
fix(license): use hash ref to fixed license
2026-10-11 01:57:46 +08:00
Ryogi Mutsuki bc4ed0797f fix(license): use hash ref to fixed license 2026-10-11 01:56:11 +08:00
camileko b4a8193bae 修复没有单个自定义事件的情况下无法触发自定义事件列表 2026-10-06 00:09:53 +08:00
camileko 0a3258974a 联网主页地址含有替换标记时主页无法显示 2026-10-05 04:33:43 +08:00
unknown d69ef35547 把 Hidden Utility Window 加进窗口名黑名单 2026-10-05 00:18:13 +08:00
unknown 1ca8b78388 quick fix 2026-09-29 21:20:27 +08:00
龙腾猫跃 0e0d12fdce Merge pull request #9383 from camileko/close-9380
fix: 修复剪贴板被占用时无提示
2026-09-28 19:04:13 +08:00
HerobrineXia 565b49321f Merge pull request #9356 from camileko/close-8679
fix: 修复使用中文搜索 Mod、同时指定版本和加载器时,Modrinth 可能出现分别匹配但实际上没有对应版本的结果
2026-09-25 00:35:49 +08:00
HerobrineXia 8f4e6bbe05 Merge pull request #9378 from camileko/close-9362
fix: 修复失败和取消以外多余的1s等待
2026-09-24 23:49:03 +08:00
camileko d1e969d742 修复剪贴板被占用时无提示 2026-09-22 23:25:55 +08:00
camileko 6d5c73fe98 修复失败和取消以外多余的1s等待 2026-09-21 01:06:18 +08:00
camileko 8122a65e86 修复使用中文搜索 Mod、同时指定版本和加载器时,Modrinth 可能出现分别匹配但实际上没有对应版本的结果 2026-09-13 23:50:59 +08:00
龙腾猫跃 8f76864574 Merge pull request #9198 from Howard302/feature/esc-cancel-launch
feat: 在启动页面按 Esc 取消启动游戏
2026-09-03 21:39:12 +08:00
HerobrineXia 06125e2b89 Merge pull request #9220 from Howard302/feature/zip-drag-drop-hint
feat: 压缩包拖入时先显示检测提示
2026-08-28 15:19:07 +08:00
HerobrineXia 39c49c8b9d Merge pull request #9236 from Howard302/feat/add-open-custom.xaml-button
feat: 页面添加打开自定义XAML所在文件夹按钮
2026-08-27 16:49:14 +08:00
HerobrineXia 8230882954 Merge pull request #9133 from Howard302/fix/move-ssl-cert-option
feat:将"在正版登录时验证 SSL 证书"选项从"下载"卡片移动到"启动选项"
2026-08-27 15:20:06 +08:00
HerobrineXia 87cd3bc69d Merge pull request #9240 from ailiujiarui/codex/fix-issue-9239-java25-fabric
fix: 避免旧版 Fabric 自动选择 Java 25
2026-08-26 19:55:24 +08:00
HerobrineXia 4759945743 Merge pull request #9149 from Neclyon/chore/update-mgh-addr
chore: 更新杂志主页地址
2026-08-26 15:08:58 +08:00
Zhengyuan Huang 092b2a2edd fix: 对不起啦,忘记改ID会影响仓库地址了 2026-08-25 02:04:56 +08:00
pih 140348e10d chore: remove issue design note 2026-08-19 19:56:42 +08:00
pih 05c8a47a06 fix: avoid Java 25 for legacy Fabric 2026-08-19 15:27:37 +08:00
Howard302 b8651f109f feat: 页面添加打开自定义XAML所在文件夹按钮 2026-08-19 11:05:00 +08:00
PCL Contributor e5d4db3a6d feat: 压缩包拖入时先显示检测提示 2026-08-16 09:26:41 +00:00
Howard302 7c75fe4d4b feat: 在启动页面按 Esc 取消启动游戏
- 在 ModLaunch.vb 中添加 McLaunchCancel() 公共方法,封装取消启动逻辑
- 更新 PageLaunchLeft.vb 的 BtnCancel_Click 调用 McLaunchCancel()
- 更新 FormMain.xaml.vb 的 Esc 键处理:启动中按 Esc 取消启动,否则返回上一级

Closes #9193
2026-08-13 10:45:09 +00:00
CreeperIsASpy 9afbfe3f64 chore: 更新杂志主页地址 2026-08-08 18:11:46 +08:00
龙腾猫跃 639de1b48a Update PCL download link in README 2026-08-08 12:44:28 +08:00
LTCat 904de2af71 2.13.1.1 正式版 2026-08-07 19:54:54 +08:00
Howard302 2fe1ceb40f 将"在正版登录时验证 SSL 证书"选项从"下载"卡片移动到"启动选项"
Fixes #9119

将"在正版登录时验证 SSL 证书"选项从 设置-其他 的"下载"卡片移动到
设置-启动 的"启动选项"卡片中,因为该选项与下载无关,而与启动游戏
时的正版登录相关,在语义上更符合。
2026-08-07 05:52:41 +00:00
LTCat 0ce35fe7e5 2.13.1.1 2026-08-06 22:30:13 +08:00
LTCat 7fb6e5d68e 2.13.1.0 2026-08-02 01:00:16 +08:00
龙腾猫跃 c7ccdd65dc 将分发优先许可外链到石墨 2026-08-01 19:52:00 +08:00
龙腾猫跃 bbb4fc31a8 Merge pull request #8994 from qinghuayuci/devin/1784297153-fix-validatefolderpath-8dot3
fix: NTFS 8.3 文件名检查应校验各路径段而非整个路径
2026-07-18 00:56:36 +08:00
qinghuayuci d6c8266c58 fix: NTFS 8.3 文件名检查应校验各路径段而非整个路径 2026-07-17 14:07:36 +00:00
龙腾猫跃 217d34a237 Merge pull request #5767 from Open-KFC/main
fix #5744: 模组下载界面显示Tooltip时滚动鼠标致使该Tooltip被永远隐藏
2026-07-17 12:34:13 +08:00
龙腾猫跃 671cf8bdf5 Merge branch 'main' into main 2026-07-17 12:34:06 +08:00
龙腾猫跃 fd7b722346 放宽条件:若 PCL 不是收费主体,也允许收费分发 2026-07-11 10:22:00 +08:00
龙腾猫跃 cf7f8cbe19 2.13.0.1 正式版 2026-07-06 00:54:41 +08:00
龙腾猫跃 5b05f87067 2.13.0.1 2026-07-03 15:07:59 +08:00
龙腾猫跃 c6d0457946 2.13.0.0 2026-07-02 23:24:29 +08:00
龙腾猫跃 124e3be6bf Merge pull request #8866 from RyogiMutsuki/msbuild
feat: upgrade setup-msbuild to v3
2026-06-30 18:41:43 +08:00
Ryogi Mutsuki 070c4f48b0 feat: upgrade setup-msbuild to v3 2026-06-30 12:32:56 +08:00
龙腾猫跃 8bdf027647 Update licensing terms and guidelines in LICENCE file 2026-06-18 18:32:50 +08:00
龙腾猫跃 6fe24e2522 Update LICENCE 2026-06-18 18:25:22 +08:00
龙腾猫跃 bebbc3536a Clarify licensing terms in LICENCE file 2026-06-18 18:23:41 +08:00
龙腾猫跃 4d27f97a7a Update LICENCE 2026-06-18 18:20:56 +08:00
龙腾猫跃 7b30b6d02f 添加 PCL 分发有限许可 2026-06-18 18:18:51 +08:00
龙腾猫跃 f1009d7d67 Merge pull request #8815 from LuoYun-Team/feat/actions
feat: 升级 checkout 版本并加快执行速度
2026-06-17 15:36:30 +08:00
林轻语 3f9bab9686 feat: upgrade to v7 2026-06-17 13:44:43 +08:00
林轻语 b868e193e5 feat: 升级 checkout 版本并加快执行速度 2026-06-17 13:40:08 +08:00
龙腾猫跃 00089c3b91 Merge pull request #8782 from Zmjjeff7/fix/setup-system-removeditems-oob
fix(setup): 避免系统设置下拉框回退时空集合数组越界
2026-06-14 15:40:05 +08:00
龙腾猫跃 ae6bbccfda Merge pull request #8697 from PCL-Community/#8673
fix(resource): 展开上个页面已展开的卡片功能不生效
2026-06-14 15:00:40 +08:00
龙腾猫跃 e25a06205f Merge pull request #8794 from qinghuayuci/fix/myloading-error-text-formatting
fix: MyLoading 网络异常时错误文本格式化有误
2026-06-14 13:05:46 +08:00
jaquelynsannuttiskiocandDevin AI 31909201fa fix: MyLoading 网络异常时错误文本格式化有误 (#8712)
修复两个问题:
1. IsBadNetwork() 原本对 RootException() 调用,根异常已无 InnerException,
   导致无法正确检测异常链中的网络异常类型,现改为对原始异常调用。
2. 错误文本原本直接使用 RootException().Message,底层异常的 Message
   可能是不友好的技术信息,现改用 GetDisplay(False) 获取用户友好的
   单行错误描述。

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-06-14 01:28:45 +00:00
龙腾猫跃 1ae7e8e371 Merge pull request #8727 from CiiLu/patch-1
feat: 添加建造者披风译名
2026-06-14 03:24:41 +08:00
龙腾猫跃 5de7050dff Merge pull request #8756 from PCL-Community/#8738
fix(cape): 更换披风后没有更新缓存
2026-06-14 03:21:38 +08:00
Zmjjeff7 4319c991b3 fix(setup): 避免系统设置下拉框回退时空集合数组越界
ComboSystemActivity / ComboSystemUpdate 的 SelectionChanged 在用户于警告框
点击“取消”时,会执行 SelectedItem = e.RemovedItems(0) 进行选择回退。当此前
没有选中项(e.RemovedItems 为空)时会抛出 IndexOutOfRangeException。

与 #7085(版本独立设置)同类问题,仅在 e.RemovedItems.Count > 0 时才回退。
2026-06-10 02:15:50 +08:00
CiiLu a02602776a Update MySkin.xaml.vb 2026-06-09 21:54:37 +08:00
wyc-26 7370b1adcb fix(cape): 更换披风后没有更新缓存 2026-06-06 15:13:07 +08:00
龙腾猫跃 9beabf6933 Update bug1.yml 2026-06-03 05:16:53 +08:00
龙腾猫跃 f109f75d96 Merge pull request #8741 from Pearl-Luo/issue_template
chore: 在“Minecraft 崩溃”的 issue 模板中添加 PCL 汉医堂的链接
2026-06-03 05:14:08 +08:00
B-0712天依號 5a1b560087 Update bug1.yml 2026-06-03 01:10:05 +08:00
B-0712天依號 61638c75b1 添加 PCL 汉医堂链接 2026-06-03 01:07:02 +08:00
CiiLu 348cc81252 feat: 添加建造者披风译名 2026-05-31 10:57:47 +08:00
wyc-26 cad37e9fc2 fix(resource): 展开上个页面已展开的卡片功能不生效 2026-05-24 20:47:17 +08:00
龙腾猫跃 105e798744 2.12.8.2 正式版 2026-05-24 19:28:58 +08:00
龙腾猫跃 9be217fe12 2.12.8.2 2026-05-24 19:16:19 +08:00
龙腾猫跃 4d51dda61e 2.12.8.1 正式版 2026-05-22 22:56:57 +08:00
龙腾猫跃 c86fdc3af1 2.12.8.1 2026-05-21 01:15:20 +08:00
龙腾猫跃 8c3896da36 Merge pull request #8651 from LuoYun-Team/feat/new-lock
feat: 新的单例锁实现
2026-05-20 23:23:54 +08:00
龙腾猫跃 d95364c2d5 Merge pull request #8648 from LuoYun-Team/fix/cape
fix(cape): 复用登录信息导致无法修改披风
2026-05-20 22:23:01 +08:00
林轻语 3d4b768af9 fix: 重复调用
Removed duplicate call to DropToTopByLock.
2026-05-18 20:29:39 +08:00
林轻语 ac18b979ce fix: use local namespace 2026-05-17 04:48:05 +08:00
林轻语 c2580809e0 fix: incorrect code 2026-05-17 04:34:24 +08:00
林轻语 bbb2182175 chore: add handle valid check 2026-05-17 04:33:49 +08:00
林轻语 e24311c7cf fix: 未调用 Exit 导致程序继续执行 2026-05-17 04:30:27 +08:00
林轻语 f5370a1743 fix: undefined var 2026-05-17 04:25:36 +08:00
林轻语 c10eae6596 feat: 新的单例锁实现 2026-05-17 04:20:31 +08:00
林轻语 b286183151 fix(cape): 复用登录信息导致无法修改披风 2026-05-16 23:02:10 +08:00
龙腾猫跃 409e1ccdc1 * 2026-05-15 23:42:51 +08:00
龙腾猫跃 1bc4f3feef * 2026-05-15 22:07:05 +08:00
龙腾猫跃 53fe8204cf 2.12.8.0 2026-05-15 22:06:22 +08:00
龙腾猫跃 277c020186 Merge pull request #8484 from 0x1A5/main
issue#8018: 重新登录频率放缓
2026-05-13 16:19:56 +08:00
龙腾猫跃 4f46b6b4a8 Merge pull request #8464 from Big-Cake-jpg/main
refactor(launch): 根据 LWJGL 版本判断是否使用 LWJGL Unsafe Agent
2026-05-13 00:14:18 +08:00
龙腾猫跃 a5602da9b9 Merge pull request #8475 from PCL-Community/#7048
feat(controls): 悬停至按钮等控件时更改鼠标光标
2026-05-12 21:19:06 +08:00
龙腾猫跃 b42f9c9efd Merge pull request #6807 from PCL-Community/#6703
fix(version): 为含 w 的愚人节版本加入对应表
2026-05-12 11:28:20 +08:00
龙腾猫跃 570df497c8 Merge pull request #8519 from MatchaOre/main
chore: remove protocol set
2026-05-05 09:29:19 +08:00
龙腾猫跃 20846ed523 2.12.7.3 正式版 2026-05-05 08:51:16 +08:00
龙腾猫跃 1b4d1296e7 2.12.7.3 2026-04-29 07:50:25 +08:00
龙腾猫跃 793e07da03 2.12.7.2 2026-04-29 06:49:46 +08:00
龙腾猫跃 115a0cd9e9 Merge pull request #8560 from Meloong-Git/copilot/fix-workflow-submodule-error
fix: add submodules: true to checkout step in build workflow
2026-04-29 06:35:56 +08:00
copilot-swe-agent[bot]andLTCatt e061feb957 fix: add submodules: true to checkout step in build workflow
Agent-Logs-Url: https://github.com/Meloong-Git/PCL/sessions/d8361969-344a-4ddd-8370-df791df78f57

Co-authored-by: LTCatt <[email protected]>
2026-04-28 22:21:26 +00:00
龙腾猫跃 4577a85d1a 2.12.7.1 2026-04-29 06:19:22 +08:00
龙腾猫跃 6d9033f099 Merge pull request #8559 from Meloong-Git/copilot/investigate-workflow-failure
Fix build failure: add NuGet restore for PCLCS before MSBuild
2026-04-29 06:09:27 +08:00
copilot-swe-agent[bot]andLTCatt 0030ebe029 Add dotnet restore step for PCLCS before build
Agent-Logs-Url: https://github.com/Meloong-Git/PCL/sessions/faa92c72-a55a-4172-9464-e5c3be47f6fa

Co-authored-by: LTCatt <[email protected]>
2026-04-28 21:59:15 +00:00
龙腾猫跃 08c81b6951 * 2026-04-29 05:45:33 +08:00
龙腾猫跃 67764b393d 2.12.7.0 2026-04-29 05:37:25 +08:00
龙腾猫跃 6aedd20512 Merge pull request #8511 from PCL-Community/#8508
fix(settings): 读取版本设置时错误地使用了缓存
2026-04-18 15:40:58 +08:00
雨墨大小姐 db5f6b5773 chore: remove protocol set 2026-04-17 13:11:33 +08:00
wyc-26 3862d77e50 fix(settings): 读取版本设置时错误地使用了缓存 2026-04-13 19:52:40 +08:00
YIJANGs 15fda76fd3 修复无限重启加载器的问题
在原595行的代码使得内存中的到期时间改变。使得输入值变化(理论上是这样)
2026-04-09 21:56:51 +08:00
YIJANGs fa02f5e015 重写的解决方案 2026-04-08 22:50:27 +08:00
YIJANGs 6becdcf666 Merge branch 'main' of https://github.com/0x1A5/PCL 2026-04-08 21:49:29 +08:00
wyc-26 dd70b59ea6 feat(controls): 悬停至按钮等控件时更改鼠标光标 2026-04-06 15:16:12 +08:00
Big-Cake-jpg cbf9180bd4 refactor(launch): 根据 LWJGL 版本判断是否使用 LWJGL Unsafe Agent 2026-04-05 08:35:30 +08:00
wyc-26 cc1073ad03 fix(version): 为含 w 的愚人节版本加入对应表 2026-04-04 21:57:55 +08:00
wyc-26 24ff7e6f40 Merge branch 'main' into #6703 2026-04-03 22:16:10 +08:00
龙腾猫跃 f578dbd784 2.12.6.2 正式版 2026-04-03 16:57:35 +08:00
龙腾猫跃 7879112bc9 2.12.6.2 2026-04-03 16:54:26 +08:00
龙腾猫跃 2a01043261 2.12.6.1 正式版 2026-04-02 18:11:36 +08:00
龙腾猫跃 36b5bcbef4 2.12.6.1 2026-04-02 17:48:49 +08:00
龙腾猫跃 c850596cab 2.12.6 正式版 2026-03-30 04:34:54 +08:00
龙腾猫跃 c3ee4c7e1c 2.12.6 2026-03-30 04:34:11 +08:00
龙腾猫跃 4d76936f22 Merge pull request #8374 from PCL-Community/fix#8367
fix(version-rename): 路径错误导致无法重命名版本
2026-03-30 00:07:14 +08:00
龙腾猫跃 be59ab8dfb Merge pull request #8394 from PCL-Community/fix#8392
fix(folder-select): 无法保存路径含程序的启动路径的 Minecraft 文件夹
2026-03-29 23:50:24 +08:00
wyc-26 fffa26cd4c fix(folder-select): 无法保存路径含程序的启动路径的 Minecraft 文件夹 2026-03-29 20:22:41 +08:00
wyc-26 d253b4f187 fix(version-rename): 路径错误导致无法重命名版本 x3 2026-03-27 22:19:49 +08:00
wyc-26 2e41bbc187 fix(version-rename): 路径错误导致无法重命名版本 x2 2026-03-27 22:16:35 +08:00
龙腾猫跃 af8be24506 2.12.5 2026-03-27 07:15:50 +08:00
龙腾猫跃 f14bb03361 Merge pull request #8362 from PCL-Community/fix#8351
fix(folder-select): 无法保存选择的 Minecraft 文件夹
2026-03-27 02:09:00 +08:00
龙腾猫跃 de4ad48acb Merge pull request #8361 from PCL-Community/fix#8357
fix(download): 未适配 Neoforge 新版本命名规则导致找不到 26.1 匹配的 Neoforge
2026-03-26 23:59:32 +08:00
龙腾猫跃 c6c7b818c9 Merge pull request #8363 from PCL-Community/fix#8349
fix(version-rename): 路径错误导致无法重命名版本
2026-03-26 23:15:27 +08:00
wyc-26 46f4b0b2d2 fix(version-rename): 路径错误导致无法重命名版本 2026-03-26 18:57:11 +08:00
wyc-26 ffaf5b9618 fix(folder-select): 无法保存选择的 Minecraft 文件夹 2026-03-26 18:33:09 +08:00
wyc-26 c89ebf3b19 fix(download): 未适配 Neoforge 新版本命名规则导致找不到 26.1 匹配的 Neoforge 2026-03-26 17:46:11 +08:00
龙腾猫跃 6a140ce5b9 2.12.4 2026-03-24 22:53:27 +08:00
龙腾猫跃 43963f8f7c Merge pull request #8286 from MegaWall3/main
fix(ModSecret): 修复使用JAVA7及更早版本时,未设置永久代内存导致游戏频繁内存溢出崩溃的问题
2026-03-21 07:47:54 +08:00
龙腾猫跃 17d8b269ef Merge pull request #8289 from icocoer/main
fix(java): 补充 SDKMAN 安装的 Java 自动搜索
2026-03-21 03:31:02 +08:00
龙腾猫跃 762f1adf2f 替换爱发电域名 2026-03-14 23:47:13 +08:00
龙腾猫跃 3e89d9261a 替换爱发电域名 2026-03-14 23:46:46 +08:00
icocoer 95f775c3f3 fix(java): 补充 SDKMAN 安装的 Java 自动搜索
在 Java 自动搜索流程中加入对用户目录下
.sdkman/candidates/java 及其 current 子目录的扫描,
以支持通过 Git Bash + SDKMAN 在 Windows 上安装的 Java。
2026-03-11 11:33:25 +08:00
IridiumMetal 228e2d1fb9 修复使用JAVA7及更早版本时,未设置永久代内存导致游戏频繁内存溢出崩溃的问题
Java 7 及更早版本使用永久代 (PermGen)​ 存储类元数据,其大小固定(默认约64-128MB),而往后的Java 8 用元空间 (Metaspace)​ 替代了 PermGen,可自动扩展。

在使用JAVA7游玩老版本怀旧时,频繁出现"java.lang.OutOfMemoryError: PermGen space"错误致客户端崩溃。

该PR永久修复了此问题,当检测到用户选择了老版本JAVA时,如果用户未未指定这个参数,自动添加了PermGen参数,扩充预分配的内存额度。
2026-03-10 22:19:23 +08:00
龙腾猫跃 df1bab0ebe 2.12.3 正式版 2026-03-08 21:20:03 +08:00
龙腾猫跃 be67c4ccc2 2.12.3 2026-03-08 20:56:13 +08:00
YIJANGand薄奚梦灵 802ca80314 更新 ModSetup.vb
Co-authored-by: 薄奚梦灵 <[email protected]>
2026-03-06 21:59:26 +08:00
YIJANGs 3951a12d00 2
理论上有效
2026-03-06 20:53:47 +08:00
YIJANGs be9798669d Update ModLaunch.vb 2026-03-05 21:02:17 +08:00
YIJANGs 3db574fcdc Update ModLaunch.vb 2026-03-03 17:39:39 +08:00
YIJANGs dbffe737e9 延长Token刷新时间 2026-03-03 15:51:13 +08:00
龙腾猫跃 2bffafcd7a Merge pull request #8236 from PCL-Community/#8147
fix(download): OptiFine 与 LiteLoader 一并安装时安装路径错误
2026-03-03 04:50:17 +08:00
龙腾猫跃 8b49aea476 Merge pull request #8237 from PCL-Community/#7996
fix: Catch 后未 Continue For
2026-03-03 02:16:32 +08:00
wyc-26 9aa882e2fa fix: Catch 后未 Continue For 2026-03-01 09:37:07 +08:00
wyc-26 c184cc22e5 fix(download): OptiFine 与 LiteLoader 一并安装时安装路径错误 2026-03-01 09:03:22 +08:00
龙腾猫跃 156dadc202 Merge pull request #8003 from PCL-Community/#7979
fix(export): 导入配置时“打包资源文件,以避免在导入时下载”和“仅从Modrinth下载资源文件”允许同时勾选
2026-02-28 21:23:17 +08:00
龙腾猫跃 85aa14aa25 Merge pull request #8062 from LuoYun-Team/audio
chore: use MediaFoundationReader
2026-02-28 21:09:55 +08:00
龙腾猫跃 c75ee952c5 Merge pull request #8043 from 0x1A5/main
fix: Issue #7983
2026-02-28 21:02:06 +08:00
龙腾猫跃 d2d302901f Merge pull request #8190 from Ignis-Studio/main
chore: 更换杂志主页链接
2026-02-28 20:47:26 +08:00
龙腾猫跃 a601802dc5 Merge pull request #8206 from tangge233/fix/hash
fix(hash): 修复哈希处理部分资源处理问题
2026-02-28 20:34:46 +08:00
tangge233 dc7d6b3e91 chore: 漏了一个 2026-02-22 22:47:54 +08:00
tangge233 4c1578f9aa fix(hash): 修复哈希处理部分资源处理问题 2026-02-22 22:41:30 +08:00
Zhengyuan Huang 26ecd049c1 chore: 更换杂志主页链接 2026-02-18 12:30:03 +08:00
薄奚梦灵 15820c893f chore: fallback
Add error handling for audio file loading fallback.
2026-01-28 20:24:24 +08:00
薄奚梦灵 cd765a9097 chore: use MediaFoundationReader 2026-01-28 16:28:36 +08:00
YIJANGs 9f87b694cf 剪切板中复制联机码后跳转界面
取消检测是否有邀请码
直接返回
2026-01-24 12:06:31 +08:00
wyc-26 2749812fe3 fix(export): 导入配置时“打包资源文件,以避免在导入时下载”和“仅从Modrinth下载资源文件”允许同时勾选 2026-01-17 19:55:45 +08:00
龙腾猫跃 73bdc53309 2.12.2 正式版 2026-01-15 03:30:55 +08:00
龙腾猫跃 52b4c87fbc 2.12.2 2026-01-15 03:20:43 +08:00
龙腾猫跃 87f5cc2ea9 Merge pull request #7919 from PCL-Community/#7894
fix(cape): 更换披风后未更新当前选择的披风
2026-01-15 01:18:01 +08:00
龙腾猫跃 8323c1f116 Merge pull request #7948 from PCL-Community/#7932
feat(event): 增加“检查更新”自定义事件
2026-01-14 22:33:39 +08:00
龙腾猫跃 4ad3acb8ff Merge pull request #7906 from MoYuan-CN/fix/7904
fix(ModDownload): 跟进新的 NeoForge 命名方式以正确识别测试版
2026-01-14 21:48:22 +08:00
wyc-26 7ee3ebb51a feat(event): 增加“检查更新”自定义事件 2026-01-10 11:26:40 +08:00
wyc-26 1f1a3348bc Merge branch 'main' into #7894 2026-01-10 11:14:48 +08:00
wyc-26 8babdf990f refactor(Cape): 直接修改 ProfileJson 以防止卡登录 2026-01-10 11:12:09 +08:00
wyc-26 b8eab42c33 fix(cape): 更换披风后未更新当前选择的披风 2026-01-03 20:15:17 +08:00
墨渊and薄奚梦灵 3ec91e1b0b Update Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
Co-authored-by: 薄奚梦灵 <[email protected]>
2026-01-02 10:27:24 +08:00
MoYuan-CN bd4498cec4 fix: unexcepted equal symbol 2026-01-02 04:16:45 +08:00
MoYuan-CN 6dc9dadf1c fix: mismatched neoforge type description 2026-01-02 04:11:13 +08:00
龙腾猫跃 68f4808c1c 2.12.1 正式版 2025-12-31 15:57:53 +08:00
龙腾猫跃 a674630f54 2.12.1 2025-12-27 05:37:27 +08:00
龙腾猫跃 1ecff91c1c * 2025-12-27 03:15:15 +08:00
龙腾猫跃 085af160a1 2.12.0 2025-12-27 02:48:47 +08:00
龙腾猫跃 d6ba445e54 Merge pull request #6944 from pynickle/fix/issue-6645
fix(ModDownload): 修复mc同一版本更新日志可能多次出现
2025-12-22 19:43:42 +08:00
龙腾猫跃 095bd473af Merge pull request #6262 from PCL-Community/fix#6230
fix(ModDownload): 愚人节版本无法下载 NeoForge
2025-12-22 07:04:11 +08:00
龙腾猫跃 730387d424 Merge pull request #7523 from PCL-Community/#7415
feat(cape): 更换披风时显示当前使用的披风
2025-12-16 00:55:35 +08:00
龙腾猫跃 f79fc5267b Merge pull request #7427 from PCL-Community/#7422
fix(validate): 超过 Int 存储范围
2025-12-16 00:42:37 +08:00
龙腾猫跃 15c04d46d4 明确弹窗需求 2025-11-29 00:10:06 +08:00
龙腾猫跃 526d1fe700 typo 2025-11-28 18:24:25 +08:00
wyc-26 43db6f073e Merge branch 'main' into fix#6230 2025-11-15 11:20:58 +08:00
wyc-26 ae187a346d feat(cape): 更换披风时显示当前使用的披风 2025-11-15 11:17:44 +08:00
龙腾猫跃 ad757f54b7 2.11.2 2025-11-06 23:10:47 +08:00
wyc-26 865f5a93e7 fix(validate): 超过 Int 存储范围 2025-11-02 08:22:22 +08:00
龙腾猫跃 efd480f06c 2.11.1 2025-11-01 11:51:54 +08:00
龙腾猫跃 a067271995 Merge pull request #7398 from PCL-Community/#7375
fix(validate): 没有判断前导零
2025-10-30 22:18:22 +08:00
龙腾猫跃 f85361d009 Merge pull request #7396 from PCL-Community/#7353
fix(cape): 家园披风没有被正常翻译
2025-10-30 22:15:01 +08:00
wyc-26 4a5cc3f95b fix(validate): 没有判断前导零 2025-10-30 22:01:08 +08:00
wyc-26 4c0f525cf3 fix(cape): 家园披风没有被正常翻译 2025-10-30 21:28:32 +08:00
龙腾猫跃 8d2eb85aec 2.11.0 2025-10-24 22:39:55 +08:00
龙腾猫跃 e5dccf3a13 Merge pull request #7339 from MoYuan-CN/main
允许通过 UrlScheme 打开 Preview 基岩版
2025-10-24 05:18:50 +08:00
墨渊 8ba86ffed8 Allow Preview 2025-10-21 16:15:16 +08:00
龙腾猫跃 76f4e761b1 Merge pull request #7318 from Light-Beacon/main
feat(ModMain): varible 替换标记支持设置默认值
2025-10-20 19:53:54 +08:00
龙腾猫跃 e503a39baf Merge pull request #7323 from ruollin/feat/copper-cape
feat(cape): 添加 Copper (铜披风)的中文译名
2025-10-20 19:49:29 +08:00
若琳酱 5ed4a9bbbf feat(cape): 添加 Copper (铜披风)的中文译名
Resolve https://github.com/Meloong-Git/PCL/issues/7322
2025-10-16 13:24:48 +08:00
Nattiden 382deaa197 feat(ModMain): varible 替换标记支持设置默认值 2025-10-15 14:13:27 +08:00
龙腾猫跃 00966bec6a 2.10.9 正式版 2025-10-08 18:43:40 +08:00
龙腾猫跃 a41579b1da 2.10.9 2025-10-07 04:57:14 +08:00
龙腾猫跃 86ed0e9ae0 Merge pull request #5862 from Open-KFC/feat5684
Feat:支持一个按钮运行多个自定义事件
2025-10-06 01:19:23 +08:00
龙腾猫跃 ab697054c8 Merge branch 'main' into feat5684 2025-10-06 01:14:23 +08:00
龙腾猫跃 0ba2fd1edb Merge pull request #7190 from LuoYun-Team/revert-6216-dialog
Revert(dialog): 撤销更改
2025-10-04 23:43:25 +08:00
龙腾猫跃 49984a2913 2.10.8 正式版 2025-09-13 19:22:29 +08:00
龙腾猫跃 6f6744dfd2 2.10.8 2025-09-12 23:40:01 +08:00
龙腾猫跃 2210da6fda 2.10.7 2025-09-10 01:07:30 +08:00
薄奚梦灵 08e5e64ca4 Revert "fix:文件删除没有无法放入回收站的提示" 2025-09-09 22:09:35 +08:00
龙腾猫跃 ee385912f8 Merge pull request #6933 from pynickle/fix/issue-6876
imp(VersionOverall): 优化版本重命名处理逻辑
2025-09-09 20:31:50 +08:00
龙腾猫跃 47c3c163ed Merge pull request #6664 from shatyuka/fix/setup-binding
fix: 动画绑定失败
2025-09-09 20:18:27 +08:00
龙腾猫跃 45b6582f30 Merge branch 'main' into fix/setup-binding 2025-09-09 20:18:18 +08:00
龙腾猫跃 6f9262ef4f Merge pull request #7020 from PCL-Community/#7003
chore(ModCrash): 崩溃识别增加
2025-09-07 04:31:39 +08:00
龙腾猫跃 46fd4c1e8d Merge pull request #7031 from PCL-Community/#6908
fix(ModModpack): 安装整合包时忽略配置中设置的 IsStar 值
2025-09-04 23:13:19 +08:00
龙腾猫跃 c3d79e1f18 Merge pull request #7028 from PCL-Community/#6978
feat(PageVersionExport): 打包整合包时支持添加 TaCZ 枪包
2025-09-04 23:11:29 +08:00
龙腾猫跃 f63e9a896c Merge pull request #7043 from PCL-Community/#7030
feat(PageSelectLeft): 将要删除的游戏文件夹是系统关键目录时警告用户
2025-09-04 23:01:38 +08:00
龙腾猫跃 9c9d98084a Merge pull request #7052 from pynickle/imp/issue-7032-official
imp(mod-crash): 优化玩家实体导致崩溃的提示信息
2025-09-04 21:21:31 +08:00
龙腾猫跃 cfbde4ad1c Merge pull request #7056 from Open-KFC/IsSwaped-delegate
fix(MyCard): IsSwaped 未正确转发
2025-09-04 21:14:23 +08:00
龙腾猫跃 46b96d6408 Merge pull request #7050 from pynickle/style/chinese-official
style: 去除中文逗号
2025-09-04 21:05:40 +08:00
龙腾猫跃 6f3bad680c Merge pull request #7057 from PCL-Community/#7025
fix(ModLaunch): 未将部分错误识别为超时
2025-09-04 17:58:10 +08:00
龙腾猫跃 ad5207470d Merge pull request #7058 from pynickle/fix/issue-7023-official
fix(mod-launch): 修复刷新登录时出现 400 报错,未进行错误处理
2025-09-04 17:50:36 +08:00
龙腾猫跃 b936608ec7 Merge pull request #7071 from PCL-Community/#7070
fix(ModCrash): 错误分析误报 night config 存在问题,但实际上是配置文件损坏
2025-09-04 00:19:47 +08:00
龙腾猫跃 dc7f01dfdd Merge pull request #7151 from lingting/main
Java搜索 支持jet 的基础sdk 以及 ~/.jdks 没匹配到可用项修复
2025-09-02 20:52:38 +08:00
龙腾猫跃 3528bf5a3d Merge pull request #6866 from LuoYun-Team/MusicCount
fix(music): 计算音乐文件数量逻辑有误
2025-09-01 20:57:39 +08:00
龙腾猫跃 f211df1ea2 Merge pull request #6648 from PCL-Community/feat#6610
feat: 允许为单个版本关闭 Mod 更新功能
2025-09-01 20:33:07 +08:00
lingting 9a3df4adec feat(java): Java搜索支持 Jet的sdk且同时忽略jet 的jcef sdk 2025-08-27 16:35:46 +08:00
lingting b09750ffff fix(java): 尝试修复从 ~/.jdks 中搜索Java时未匹配到任何可用项 2025-08-27 16:32:56 +08:00
wyc-26 2f74640de5 fix(ModCrash): 忘写 Then 2025-08-16 18:45:02 +08:00
wyc-26 78a5536bc7 fix(ModCrash): 错误分析误报 night config 存在问题,但实际上是配置文件损坏 2025-08-16 16:12:52 +08:00
ENC_Euphony 54c45ff54b 加入封号处理 2025-08-15 20:37:30 +08:00
wyc-26 80f89cdf4f fix(PageDownloadInstall): 编译错误 2025-08-15 16:45:27 +08:00
ENC_Euphony fe39495f2e Update ModLaunch.vb 2025-08-15 16:25:03 +08:00
wyc-26 20f9a71f68 Merge branch 'main' into fix#6230 2025-08-15 16:22:47 +08:00
ENC_Euphony e2c24a7d44 fix(mod-launch): 修复刷新登录时出现 400 报错,未进行错误处理 2025-08-15 15:18:03 +08:00
wyc-26 37b069c572 fix(ModLaunch): 未将部分错误识别为超时 2025-08-15 14:33:10 +08:00
Open-KFC 5622e50a54 fix(MyCard): IsSwaped 未正确转发 2025-08-15 12:29:27 +08:00
ENC_Euphony 86d1d387a5 imp(mod-crash): 优化玩家实体导致崩溃的提示信息 2025-08-15 01:08:27 +08:00
ENC_Euphony 7f2f7df05f style: 去除中文逗号 2025-08-15 00:44:50 +08:00
wyc-26 b285ffa401 feat(PageSelectLeft): 将要删除的游戏文件夹是系统关键目录时警告用户 2025-08-14 15:36:32 +08:00
龙腾猫跃 8e095ddaed 2.10.6 正式版 2025-08-14 01:29:27 +08:00
wyc-26 afc08cc243 fix(ModModpack): 安装整合包时忽略配置中设置的 IsStar 值 2025-08-13 16:15:32 +08:00
wyc-26 bac349a54c feat(PageVersionExport): 打包整合包时支持添加 TaCZ 枪包 2025-08-13 16:04:18 +08:00
wyc-26 02c766ac08 chore(ModCrash): 崩溃识别增加 2025-08-13 10:12:38 +08:00
龙腾猫跃 273cee69e5 fix: IsSwaped 在加壳后失效 2025-08-13 01:33:11 +08:00
龙腾猫跃 adf52d56b7 2.10.6 2025-08-13 01:08:21 +08:00
龙腾猫跃 1d0947181f Merge pull request #6970 from youzi-3222/auto-repl-colon
feat(PageVersionSetup): 自动将服务器地址替换为半角冒号
2025-08-12 19:47:21 +08:00
龙腾猫跃 f67ef9c2c5 Merge pull request #6904 from MoYuan-CN/pr/term-optimize
imp: 修改参数展示名
2025-08-12 19:11:57 +08:00
龙腾猫跃 28640dcdf5 Merge pull request #6942 from LuoYun-Team/MMC-Modpack-Fix
fix(Launch): 修复 HMCL 安装的部分整合包没法启动
2025-08-12 18:59:49 +08:00
龙腾猫跃 88e87011af Merge pull request #6971 from pynickle/style/swapped
style: 一个 typo 修复
2025-08-12 04:17:46 +08:00
龙腾猫跃 c22f925807 Merge pull request #6976 from LuoYun-Team/inst-tip
chore(tip): 移除提示
2025-08-12 04:14:30 +08:00
龙腾猫跃 5785ffdfb3 Merge pull request #6980 from Light-Beacon/main
主页按钮加入非加密设置的写入功能
2025-08-12 04:11:13 +08:00
龙腾猫跃 59857c3391 Merge pull request #6984 from Light-Beacon/change-news-homepage-link
新闻主页链接使用SSL
2025-08-12 03:31:06 +08:00
龙腾猫跃 c90d96e65d Merge pull request #6709 from ABA2396/pcl2
fix: 规避 WPF 剪贴板卡顿,修复剪切不清除文本的问题
2025-08-11 22:04:01 +08:00
Nattiden 838cb3c842 news homepage use ssl 2025-08-08 21:37:53 +08:00
Nattiden d133138984 实现 #6973 2025-08-08 14:55:38 +08:00
薄奚梦灵 128b8001ff chore(tip): 移除注册表项目 2025-08-08 11:02:50 +08:00
薄奚梦灵 c7e0dd3816 chore(tip): 移除提示 2025-08-08 10:59:56 +08:00
龙腾猫跃 8e0f53c881 2.10.5 正式版 2025-08-08 03:11:24 +08:00
youzi-3222 68483876f3 feat(PageVersionSetup): 替换中文冒号不弹窗提示 2025-08-07 21:20:42 +08:00
ENC_Euphony 10a5a30968 Update Plain Craft Launcher 2.vbproj 2025-08-07 19:30:37 +08:00
ENC_Euphony fdc56a2a4b style: 一个 typo 修复 2025-08-07 16:15:40 +08:00
youzi-3222 490626a293 feat(PageVersionSetup): 自动将服务器地址替换为半角冒号 2025-08-07 12:53:25 +08:00
ENC_Euphony 9bc39f8a95 fix(ModDownload): 修复mc同一版本更新日志可能多次出现 2025-08-03 18:40:52 +08:00
薄奚梦灵 ce91fe6245 fix(launch): missing " 2025-08-03 13:29:35 +08:00
薄奚梦灵 5a841de3ba fix(Launch): 修复 HMCL 安装的部分整合包没法启动 2025-08-03 13:26:16 +08:00
ENC_Euphony fc910b1955 ref(VersionOverall): 优化代码 2025-08-02 18:33:43 +08:00
ENC_Euphony 340e7457f8 imp(VersionOverall): 优化版本重命名处理逻辑 2025-08-01 23:27:22 +08:00
墨渊 b76266a69d imp: 修改参数展示名 2025-07-29 02:07:54 +08:00
墨渊 5fc0693892 imp: 修改参数展示名
JVM 参数头部 → Java 虚拟机参数
2025-07-29 02:06:41 +08:00
薄奚梦灵 73d9eed9a9 fix(music): 计算音乐文件数量逻辑有误 2025-07-24 20:03:04 +08:00
龙腾猫跃 28ef67ee0f 2.10.5 2025-07-22 23:49:12 +08:00
龙腾猫跃 add4153067 2.10.4 2025-07-22 21:09:24 +08:00
龙腾猫跃 3e474b89ef Merge pull request #6845 from LuoYun-Team/actions
ci(build): 让工作流支持新版本代码的自动替换
2025-07-22 19:23:58 +08:00
薄奚梦灵 b5cfebe8aa ci(build): 让工作流支持新版本代码的自动替换 2025-07-22 12:54:02 +08:00
龙腾猫跃 4486e7ea2c Merge pull request #6445 from PCL-Community/fix#6443
fix(PageVersionOverall): 重命名版本时会修改后缀名且会修改不应重命名的文件或文件夹
2025-07-22 00:41:09 +08:00
龙腾猫跃 4d91d0b27e Merge pull request #6706 from shatyuka/fix/search-forge-mods
fix: 搜索旧版本 Forge mod 崩溃
2025-07-20 19:20:26 +08:00
龙腾猫跃 14e14b9810 Merge pull request #6772 from LuoYun-Team/Drop
fix(Drop): 文件拖拽修复
2025-07-20 18:57:17 +08:00
龙腾猫跃 d6575df6ab Merge pull request #6685 from MoYuan-CN/issue/6673
imp(download): 改进可选组件互斥逻辑
2025-07-20 03:19:48 +08:00
龙腾猫跃 eed6c92f5a Merge pull request #6594 from PCL-Community/fix#6588
fix(ModSecret): 开源版本龙猫蓝配色错误
2025-07-19 04:30:54 +08:00
龙腾猫跃 ae8de397ae Merge pull request #6717 from xrlzu-Community/main
feat:去掉“刷新头像”和“修改披风”之间的线
2025-07-18 19:39:53 +08:00
龙腾猫跃 2de22b780c Merge pull request #6795 from lingting/main
chore: 自动搜索java路径添加 ~/.jdks
2025-07-18 19:36:34 +08:00
龙腾猫跃 4d954ae45d Merge pull request #6701 from LuoYun-Team/skin-url
fix(skin): 可能尝试使用 HTTPS 协议从没有 SSL 证书的服务器下载皮肤文件
2025-07-18 19:27:53 +08:00
龙腾猫跃 3d63ceba57 Merge pull request #6644 from PCL-Community/optimize-1
feat(PageDownloadCompDetail): 为每种资源区分 CachedFolder
2025-07-18 19:16:46 +08:00
龙腾猫跃 8b234d8e4f Merge pull request #6635 from zkitefly/patch-4
fix: 修复微软登录提示语句问题
2025-07-18 19:11:02 +08:00
龙腾猫跃 d6a5a8b83e Merge pull request #6626 from Light-Beacon/main
chore: 更新仓库指向仓库的链接
2025-07-18 19:10:15 +08:00
龙腾猫跃 885b3eada6 Merge pull request #6587 from LuoYun-Team/java-version
fix(java): JVM 崩溃导致错误将版本号识别为 JDK/JRE 64
2025-07-18 05:09:34 +08:00
龙腾猫跃 deb902904b Merge pull request #6585 from hongggggv/fix
chore: 修改资源筛选版本
2025-07-18 04:58:44 +08:00
龙腾猫跃 1458ce95ce Merge pull request #6527 from Baiyuexiao-Studio/security-interrupt
feat: 支持识别因为微软账号被标记为可疑活动导致登陆失败
2025-07-18 04:39:20 +08:00
龙腾猫跃 d8efb76bf0 Merge pull request #6505 from LuoYun-Team/CustomKey
feat(Secret):允许使用环境变量覆盖部分内容
2025-07-18 04:37:46 +08:00
hongggggv a843b22f38 Merge branch 'Meloong-Git:main' into fix 2025-07-17 23:14:58 +08:00
Luo 08e4414f34 将版本号改为 1.21.8 2025-07-17 23:13:23 +08:00
wyc-26 8e3adbf473 fix(ModMinecraft): 15w14a 版本号识别错误导致部分功能失效 2025-07-15 10:14:54 +08:00
lingtingand龙腾猫跃 ff225e9f37 chore: 文件夹以\结尾
Co-authored-by: 龙腾猫跃 <[email protected]>
2025-07-14 10:21:42 +08:00
龙腾猫跃 96b6ef294b Merge pull request #6473 from PCL-Community/fix#6439
fix(PageDownloadCompDetail): 部分原版可用的光影无法显示可下载的游戏版本
2025-07-14 04:38:53 +08:00
龙腾猫跃 f271d7212c Merge pull request #6797 from PCL-Community/#6774
fix: 选择图片不能选择 jpeg 格式
2025-07-14 01:44:43 +08:00
wyc-26 ad583c29c2 fix: 选择图片不能选择 jpeg 格式 2025-07-13 22:08:27 +08:00
龙腾猫跃 f98dcd8fef Merge pull request #6407 from LuoYun-Team/HttpClient
Refactor(Network):用 HttpClient 代替已经过时的 WebRequest、WebClient、ServicePointManager
2025-07-13 20:53:35 +08:00
龙腾猫跃 9b49235cda Merge pull request #6287 from PCL-Community/fix#5252
fix(ModLaunch): 证书不信任导致无法登录皮肤站
2025-07-13 20:42:46 +08:00
龙腾猫跃 f090a63c54 Merge pull request #6216 from LuoYun-Team/dialog
fix:文件删除没有无法放入回收站的提示
2025-07-13 20:39:50 +08:00
龙腾猫跃 c332a4dc94 Merge pull request #6027 from PCL-Community/feat#6026
feat: 点击“下载新 Mod”按钮跳转到下载页面时自动填充搜索框的内容
2025-07-13 20:23:32 +08:00
龙腾猫跃 6770e55214 Merge pull request #5798 from CiiLu/music
解决 #5762 :仅合法音乐纳入背景音乐数量统计
2025-07-13 20:03:51 +08:00
lingting dd028bb6d8 chore: 自动搜索java路径添加 ~/.jdks 2025-07-13 17:40:18 +08:00
薄奚梦灵 5126b257f8 chore: 将旧代码还原获取 2025-07-11 18:04:03 +08:00
薄奚梦灵 35398052bb fix(Drop): 文件拖拽修复 2025-07-11 17:57:46 +08:00
xrlzu 17031e1a65 feat:去掉“刷新头像”和“修改披风”之间的线 2025-07-04 20:14:42 +08:00
uye 5aad129121 fix: 规避 WPF 剪贴板卡顿,修复剪切不清除文本的问题
支持 (Rich)TextBox 复制/剪切/粘贴 和 DataGrid 复制,TextBox默认生效,可通过附加属性 ClipboardInterceptor.EnableSafeClipboard 启用
2025-07-03 22:44:56 +08:00
shatyuka e40a41a761 fix: 搜索旧版本 Forge mod 崩溃 2025-07-02 21:56:46 +08:00
薄奚梦灵 5ae813825c fix(skin): 可能尝试使用 HTTPS 协议从没有 SSL 证书的服务器下载皮肤文件 2025-07-01 21:42:01 +08:00
hongggggv 48684f8f85 将版本号改为 1.21.7 2025-07-01 21:05:18 +08:00
墨渊 3f5392d76d fix(version): 应当是 1.20.5+ 2025-06-30 05:21:18 +00:00
MoYuan-CN 9af03d499a imp(download): 改进可选组件互斥逻辑
在 1.21.5 及以后的版本中不允许同时选择 OptiFine 和 Fabric
选择了 Fabric 时会给出 Blue 等级的提示以帮助用户理解和寻找光影加载代替方案
2025-06-30 12:25:40 +08:00
shatyuka 065b69b346 fix: 动画绑定失败 2025-06-28 05:44:00 +08:00
wyc-26 c708aca3fb refactor: 移到高级启动选项里面 2025-06-27 09:32:47 +08:00
wyc-26 e188f7392c feat: 允许为单个版本关闭 Mod 更新功能 2025-06-26 16:01:29 +08:00
wyc-26 d6043c7d32 feat(PageDownloadCompDetail): 为每种资源区分 CachedFolder 2025-06-25 21:52:27 +08:00
Luo a38e20870b 回退上次更改 2025-06-25 16:22:19 +08:00
Luo 72a2b5da8c 修改 Mod 支持所有常用版本的描述 2025-06-25 16:18:22 +08:00
Zkitefly f3017b6b62 Update MyMsgLogin.xaml.vb 2025-06-25 16:02:26 +08:00
Nattiden 5274ae8499 chore: 链接更新 2025-06-23 21:10:38 +08:00
薄奚梦灵andtangge233 83e34b84fa Update Plain Craft Launcher 2/Modules/Base/ModNet.vb
Co-authored-by: tangge233 <[email protected]>
2025-06-23 20:45:34 +08:00
薄奚梦灵 65812b8754 chore: 删除无用代码 2025-06-20 18:38:28 +08:00
wyc-26 6616afde6d chore: 一不小心动了注释位置 2025-06-20 15:15:54 +08:00
wyc-26 7cd5747f4f fix(ModSecret): 开源版本龙猫蓝配色错误 2025-06-20 15:14:34 +08:00
薄奚梦灵 70ec0b66f9 fix(java): JVM 崩溃导致错误将版本号识别为 JDK/JRE 64 2025-06-19 12:36:47 +08:00
Luo 74e5eb82f5 修改筛选版本 2025-06-19 11:14:30 +08:00
龙腾猫跃 fdf5407ea3 2.10.3 2025-06-17 16:51:31 +08:00
龙腾猫跃 ebc9f5e31f 2.10.2 2025-06-17 05:25:51 +08:00
龙腾猫跃 4585f31082 2.10.2 正式版 2025-06-17 05:19:00 +08:00
龙腾猫跃 a63c5b46a6 Merge pull request #5872 from Open-KFC/feat4311
Feat:模组管理页面无需点击右侧控件即可触发全选快捷键
2025-06-17 03:20:14 +08:00
龙腾猫跃 01b26cdf3c Merge pull request #6472 from PCL-Community/fix#6451
fix(ModWatcher): Quilt 加载器错误窗口关闭后提示启动成功
2025-06-17 03:13:12 +08:00
龙腾猫跃 27b4df0e85 Merge pull request #6117 from LuoYun-Team/ModComp
pref(ModComp):提高代码可维护性
2025-06-17 03:11:21 +08:00
龙腾猫跃 a219eca488 Merge pull request #6440 from MoYuan-CN/issue/6438
typo: unexpected "s" appear in shader filter options
2025-06-17 03:06:29 +08:00
龙腾猫跃 666243e072 Merge pull request #6249 from PCL-Community/feat#6244
feat(ModDownloadLib): 自动创建资源包文件夹
2025-06-17 02:56:33 +08:00
龙腾猫跃 66bf71a7f3 Merge pull request #6456 from CreeperIsASpy/main
fix(PageSetupSystem.xaml.vb): 修复弹窗中页面名称错误的问题
2025-06-17 02:48:46 +08:00
龙腾猫跃 f1c046986c Merge pull request #6482 from tangge233/fix/HRKill
chore: 修复火绒的特征查杀
2025-06-17 02:40:37 +08:00
龙腾猫跃 d1fa7109ed Merge pull request #6570 from LuoYun-Team/curseforge-link
chore(CurseForge): 增加新的 Fallback
2025-06-17 02:36:27 +08:00
龙腾猫跃 6c426b09cc Merge pull request #6437 from LuoYun-Team/Skin
fix:可能在获取正版皮肤时失败
2025-06-17 02:29:27 +08:00
薄奚梦灵 cbf038d51f chore(CurseForge): 增加新的 Fallback 2025-06-16 22:39:37 +08:00
白月晓 5e31a1d937 Update MyMsgLogin.xaml.vb 2025-06-09 21:44:24 +08:00
白月晓 81619cbfc7 feat: 支持识别因为微软账号被标记为可疑活动导致登陆失败
From https://github.com/PCL-Community/PCL2-CE/issues/769
2025-06-09 18:51:58 +08:00
薄奚梦灵 300d1ecf66 feat(Secret):允许使用环境变量覆盖部分内容 2025-06-07 17:01:16 +08:00
薄奚梦灵andbaiyuexiao496 6dbb5c98cc fix: 少了特定标头和异常抛出
CO-Authored-By: baiyuexiao496 <[email protected]>
2025-06-07 14:18:00 +08:00
wyc-26 2991e43b45 chore: 小改 2025-06-05 19:52:27 +08:00
wyc-26 0c1b9e389a fix(ModWatcher): 忘写对应的 WatcherLog 2025-06-05 19:40:09 +08:00
tangge233 809a281a7e style: 修改变量名称 2025-06-04 22:54:56 +08:00
wyc-26 da8a50abd3 fix(PageDownloadCompDetail): 给资源包也加上 2025-06-04 21:06:48 +08:00
tangge233 cddcd9e8b2 chore: 修复火绒的特征查杀 (感谢火绒论坛的 UnknownOoo 老哥) 2025-06-04 18:43:38 +08:00
薄奚梦灵 7ecf306445 chore: 弄错了 2025-06-02 13:44:17 +08:00
薄奚梦灵andjiangyanyue 3adf47aa2c fix: 编译错误
CO-Authored-by: jiangyanyue <[email protected]>
2025-06-02 13:40:43 +08:00
薄奚梦灵 70a2f28073 revert: 错误的文件更改 2025-06-02 13:27:23 +08:00
薄奚梦灵 9c9216c02c revert: 错误的文件更改 2025-06-02 13:20:21 +08:00
wyc-26 f2ae0fbaa9 fix(PageDownloadCompDetail): 部分原版可用的光影无法显示可下载的游戏版本 2025-06-02 12:20:50 +08:00
wyc-26 c2a476feff fix(ModWatcher): Quilt 加载器错误窗口关闭后提示启动成功 2025-06-02 11:55:18 +08:00
wyc-26 36c03d1654 fix(PageComp): 重复的代码 2025-06-02 11:03:36 +08:00
wyc-26 ebd20195be Merge branch 'main' into feat#6026 2025-06-02 10:59:14 +08:00
薄奚梦灵andqiuyue712 5a94854809 Refactor(ModNet): 使用 HttpClient 代替过失函数 Feat(ModNet): 支持 GZip 等压缩格式响应的读取
CO-authored-by: qiuyue712 <[email protected]>
2025-06-02 00:08:22 +08:00
仿生猫梦见苦力怕 dcac683bfd Update PageSetupLeft.xaml.vb 2025-06-01 21:25:40 +08:00
薄奚梦灵 d2e210320b Merge pull request #51 from Hex-Dragon/main
sync
2025-06-01 15:45:07 +08:00
wyc-26 73d705aa5b fix(PageVersionOverall): 重命名版本时会修改后缀名且会修改不应重命名的文件或文件夹 2025-05-31 23:08:11 +08:00
墨渊 878ff980dc Update PageDownloadShader.xaml 2025-05-31 20:49:49 +08:00
薄奚梦灵 e19f910251 fix:可能在获取正版皮肤时失败 2025-05-31 18:41:48 +08:00
龙腾猫跃 6b0c923777 2.10.1 2025-05-31 16:53:00 +08:00
龙腾猫跃 03a64108d9 Merge pull request #5771 from Open-KFC/fix5665
fix(PageVersionMod): 在模组列表中划动鼠标选择时如果鼠标在列表之外松开会导致无法与悬浮卡片交互
2025-05-30 23:10:26 +08:00
龙腾猫跃 bf121009ae Merge pull request #4401 from tmdakm/main
读取MCBBS整合包文件中的Jvm和Launch参数
2025-05-29 23:08:49 +08:00
龙腾猫跃 971db70ae9 Merge pull request #6347 from Open-KFC/default-mcwindow-size
fix(LaunchArg): 默认窗口大小错误
2025-05-29 02:28:03 +08:00
龙腾猫跃 2359776803 Merge pull request #6257 from LuoYun-Team/enforce-https
fix:使用 HTTP 协议获取皮肤导致被劫持
2025-05-28 17:15:56 +08:00
龙腾猫跃 8f8819d0f0 Merge pull request #6321 from Open-KFC/title-bar-pic
fix(ImageTitleLogo): 保证在重新设置标题栏图片时得到刷新
2025-05-28 17:02:42 +08:00
龙腾猫跃 8e383ed436 Merge pull request #6318 from PCL-Community/fix#6317
fix(PageSetupUI): 修正功能隐藏中的选项卡名称
2025-05-28 17:00:53 +08:00
龙腾猫跃 34123d5944 Merge pull request #6300 from written143/cape
添加普通披风、薄煎饼披风和创始人披风的翻译
2025-05-28 17:00:03 +08:00
龙腾猫跃 0c8ccafd46 Merge pull request #6227 from CreeperIsASpy/main
fix: 极小概率在主页卡片卡出“内容见 ModSetup” 的 Hint
2025-05-28 16:57:00 +08:00
龙腾猫跃 deefff4504 Merge pull request #6234 from LuoYun-Team/resourcepack
chore:修改命名
2025-05-28 16:52:55 +08:00
龙腾猫跃 83856e5192 Merge pull request #6247 from PCL-Community/feat#6246
feat(PageDownloadCompDetail): 支持为资源包和光影包选择默认下载位置
2025-05-28 16:23:55 +08:00
龙腾猫跃 81d6e501cd Merge pull request #6340 from Open-KFC/drag-over-flowdocument
fix(FileDrag): 鼠标位于可编辑文字控件上无法完成向窗口的文件拖放
2025-05-28 15:34:40 +08:00
龙腾猫跃 65f17ba6f5 Merge pull request #6369 from PCL-Community/fix#2294
fix(PageVersionOverall): 缓存问题导致切换语言失败
2025-05-28 15:26:23 +08:00
龙腾猫跃 cc81df2e26 Merge pull request #6397 from Pigeon0v0/export
imp(export): 排除 HMCL 版本配置与自定义 Log4j2 文件
2025-05-28 15:18:17 +08:00
薄奚梦灵 9a0ad08498 Merge pull request #49 from LuoYun-Team/modnet
pref(ModNet):使用 HttpClient 替代过时的 WebRequest 和 WebClient
2025-05-26 13:44:49 +08:00
Pigeon0v0 3ce005b06b imp(export): 排除 HMCL 版本配置与自定义 Log4j2 文件 2025-05-25 11:10:46 +08:00
Open-KFC 30556c2347 fix #5665 在模组列表中划过鼠标时如果鼠标在模组条目列表之外松开会导致无法与悬浮卡片的按钮交互 2025-05-24 20:12:09 +08:00
Open-KFC 2a15bcf2c6 fix(MyScrollViewer): 滚动鼠标致使显示的ToolTip永远不可见 2025-05-24 20:00:52 +08:00
wyc-26 fa2d66cc45 fix(PageVersionOverall): 缓存问题导致切换语言失败 2025-05-18 10:15:30 +08:00
Open-KFC ae8b807ad8 fix(LaunchArg): 默认窗口大小错误 2025-05-14 10:43:09 +08:00
Open-KFC b15c3e1da2 fix(FileDrag): 用户无法将文件拖放到可编辑文字控件上 2025-05-13 06:52:26 +08:00
Open-KFC 1c0bd6d02a fix(ImageTitleLogo): 保证在重新设置标题栏图片时得到刷新 2025-05-09 23:29:15 +08:00
wyc-26 ca3eb311b6 fix(PageSetupUI): 修正功能隐藏中的选项卡名称 2025-05-09 18:13:21 +08:00
written143 0f8632a10b Fix 2025-05-09 17:53:49 +08:00
written143 5594dbdb8e 添加薄煎饼披风和创始人披风的翻译 2025-05-08 20:04:13 +08:00
written143 c72cf1e3d5 添加普通披风的翻译 2025-05-07 00:30:18 +08:00
wyc-26 443d306d31 fix(ModLaunch): 证书不信任导致无法登录皮肤站*2 2025-05-05 14:30:09 +08:00
wyc-26 acaff10bf0 fix(ModLaunch): 证书不信任导致无法登录皮肤站 2025-05-05 14:15:36 +08:00
龙腾猫跃 bf6fa718c8 2.10.0 正式版 2025-05-04 20:49:18 +08:00
薄奚梦灵 85bd89fbc0 fix(PageSetupUI):错误显示了内容见 ModSetup 的提示 2025-05-03 14:13:13 +08:00
薄奚梦灵 825334cb9d Revert(88be968866): fix Hex-Dragon#6222 2025-05-03 14:10:30 +08:00
wyc-26 ea9bb8f5ed chore(PageDownloadInstall): 删除一行无用代码 2025-05-02 16:47:59 +08:00
wyc-26 26ea4bed1c fix(ModDownload): 愚人节版本无法下载 NeoForge 2025-05-02 16:44:53 +08:00
wyc-26 e6d7c06f29 refactor(PageDownloadCompDetail): 优化选择默认保存位置部分代码 2025-05-01 20:32:59 +08:00
薄奚梦灵 cfa7ce16bd fix:强制使用 HTTPS 避免获取皮肤错误 2025-05-01 18:43:03 +08:00
wyc-26 fa12897ea9 feat(ModDownloadLib): 自动创建资源包文件夹 2025-05-01 10:41:25 +08:00
wyc-26 1a71b72ab9 feat(PageDownloadCompDetail): 支持为资源包和光影包选择默认下载位置 2025-05-01 09:33:39 +08:00
薄奚梦灵 124f5c2f8b Merge pull request #43 from Hex-Dragon/main
u
2025-04-30 14:49:34 +08:00
薄奚梦灵 c492d74568 chore:修改命名 2025-04-30 07:00:24 +08:00
CreeperIsASpy 88be968866 fix #6222 2025-04-29 21:36:19 +08:00
龙腾猫跃 44aea3e962 2.10.0 2025-04-29 18:18:46 +08:00
龙腾猫跃 9c8be3a22e Merge pull request #5976 from PCL-Community/fix#5848
fix(PageDownloadInstall): 打断安装预览页面进入动画后再次切换至下载页面时显示错误
2025-04-29 01:18:29 +08:00
龙腾猫跃 da71d25a8c Merge pull request #5776 from Open-KFC/fix4677
Fix:通过网络获取到更多可更新模组时未同步至‘可更新’模组列表视图
2025-04-29 01:11:45 +08:00
龙腾猫跃 1cd42280de Merge pull request #5775 from Open-KFC/fix4596
Fix: 在点击刷新按钮后对应的MyListItem显示不正确
2025-04-29 01:08:39 +08:00
龙腾猫跃 967ff2a9c8 Merge pull request #6197 from LuoYun-Team/abuse
feat(MyMsgLogin):增加账号被封禁时的错误提示
2025-04-29 01:03:01 +08:00
龙腾猫跃 7d0b4b72c1 Merge pull request #6137 from Open-KFC/task-bar-progress
fix(ModLoader): 不应当显示的加载器的进度也被计入到显示的总进度中
2025-04-29 00:59:44 +08:00
龙腾猫跃 b29977ee2f Merge pull request #6019 from PCL-Community/fix#6000
fix(ModLaunch): 启动脚本乱码
2025-04-29 00:57:35 +08:00
薄奚梦灵 f3680f0b78 fix:大文件删除没有无法放入回收站的提示 2025-04-28 11:52:19 +08:00
薄奚梦灵 67197bfa94 Merge pull request #33 from Hex-Dragon/main
u
2025-04-28 11:41:35 +08:00
薄奚梦灵 effdf5a3a9 Merge pull request #32 from Hex-Dragon/main
u
2025-04-28 11:41:04 +08:00
龙腾猫跃 e51a5cb6c2 fix: 编译错误 2025-04-28 02:53:53 +08:00
龙腾猫跃 5177d3ca0b fix: 哪儿来的 FormLoginOAuth 2025-04-28 02:49:05 +08:00
龙腾猫跃 e7bb3d2261 Merge pull request #4360 from Pigeon0v0/datapacks
支持下载数据包
2025-04-28 02:42:51 +08:00
龙腾猫跃 cc850c8850 Merge branch 'main' into datapacks 2025-04-28 02:42:43 +08:00
龙腾猫跃 aee0ea9147 Merge pull request #4359 from Pigeon0v0/shaders
支持下载光影包
2025-04-28 02:39:30 +08:00
龙腾猫跃 0b214f20e4 Merge branch 'main' into shaders 2025-04-28 02:39:20 +08:00
龙腾猫跃 3abb4493a2 Merge pull request #4020 from Pigeon0v0/resourcepacks
支持下载资源包
2025-04-28 02:36:25 +08:00
龙腾猫跃 580a76ab8e Merge branch 'main' into resourcepacks 2025-04-28 02:36:17 +08:00
薄奚梦灵 4823a834c6 Merge pull request #24 from Hex-Dragon/main
uuu
2025-04-27 16:23:35 +08:00
薄奚梦灵 1e98e91d4a Merge pull request #19 from Hex-Dragon/main
v10�u�B�Jh24.���ʛ�qށ����(�je��N�'�?j��������o^��*
2025-04-27 07:34:08 +08:00
龙腾猫跃 9b9d138e40 Merge pull request #5773 from LuoYun-Team/check-rename
fix(BtnDisplayRename_Click):在重命名前检查路径
2025-04-25 21:50:01 +08:00
薄奚梦灵 d33cbdc3e0 chore:先丢回去 2025-04-25 21:21:24 +08:00
薄奚梦灵 c8f9066ef2 style(PageVersionOverall):移除无用代码 2025-04-25 21:10:52 +08:00
薄奚梦灵 c44d1f066c Merge branch 'main' into check-rename 2025-04-25 21:09:38 +08:00
龙腾猫跃 38b22c5242 Merge pull request #5781 from LuoYun-Team/skip-download-folder
fix(ModJava):错误将下载目录的 Java 当作特定路径 Java
2025-04-25 19:48:55 +08:00
wyc-26 7cc3595e75 fix(ModLaunch): 同步 #5909 中修改的选择编码的方式 2025-04-25 19:43:51 +08:00
龙腾猫跃 41334e69b3 Merge pull request #5896 from LuoYun-Team/tips
fix(PageSetupUI):修改显示文本
2025-04-25 19:29:10 +08:00
龙腾猫跃 31434a9b47 Merge pull request #5912 from LuoYun-Team/uac
fix:为可能出错的部分加个 Try Catch
2025-04-25 19:15:46 +08:00
龙腾猫跃 5353c109b7 Merge pull request #6101 from PCL-Community/fix#6070
fix(ModModPack): 解压压缩包进度可能超过 100%
2025-04-25 19:04:11 +08:00
龙腾猫跃 59bdc75342 Merge pull request #5792 from Open-KFC/fix5655
Fix:鼠标保持移动时抬起仍可继续改变窗口大小
2025-04-25 19:01:57 +08:00
龙腾猫跃 fd5b64bffc Merge pull request #6150 from LuoYun-Team/ignore-system32
chore:忽略 system32 的 Java
2025-04-25 18:37:04 +08:00
龙腾猫跃 5d3b1786d7 Merge pull request #6116 from LuoYun-Team/2025
fix:识别问题
2025-04-25 18:36:15 +08:00
龙腾猫跃 03b5f79d44 Merge pull request #6138 from Open-KFC/dl-spd-lim
fix(Setup): 下载速度限制的配置项不会在打开程序时被读取
2025-04-25 18:31:49 +08:00
龙腾猫跃 8b16e1fe90 Merge pull request #6076 from PCL-Community/fix#6073
fix(PageVersionExport): 无法导出某些版本的 Xearo 文件
2025-04-25 18:31:14 +08:00
龙腾猫跃 c2b4c5bb6b Merge pull request #6053 from PCL-Community/fix#6049
chore:  筛选器增加版本 1.21.5
2025-04-25 18:28:37 +08:00
龙腾猫跃 3d1833fd38 Merge pull request #5967 from PCL-Community/fix#5966
chore(MySkin): 增加渴望披风的翻译
2025-04-25 18:28:13 +08:00
龙腾猫跃 0c553219b2 Merge pull request #6042 from PCL-Community/fix#6041
fix(FormMain): 2.9.3 没有更新日志
2025-04-25 18:09:58 +08:00
薄奚梦灵 a197845749 fix: 少了 $ 2025-04-23 20:30:03 +08:00
薄奚梦灵andMichaelshenmo de7dffefe8 style(MyMsgLogin):统一格式
Co-authored-by: Michaelshenmo <[email protected]>
2025-04-23 20:28:24 +08:00
薄奚梦灵 a13a8dd83f feat(MyMsgLogin):增加账号被封禁时的错误提示 2025-04-22 07:14:45 +08:00
龙腾猫跃 acde605ce5 Update LICENCE 2025-04-22 05:43:25 +08:00
龙腾猫跃 ec06c310db 修正 3.5 的歧义 2025-04-22 05:38:23 +08:00
龙腾猫跃 dc611a982f Update LICENCE 2025-04-21 19:28:20 +08:00
薄奚梦灵 7cdada22a9 chore:忽略 system32 的 Java 2025-04-14 16:32:18 +08:00
薄奚梦灵 74657d54c7 pref(ModNet):使用 HttpClient 替代过时的 WebRequest 和 WebClient 2025-04-13 15:21:57 +08:00
Open-KFC 68f07aa6c2 fix(Setup): 下载速度限制的配置项不会在打开程序时被读取 2025-04-13 07:31:41 +08:00
Open-KFC 2775ccc4fe fix(ModLoader): 不应当显示的加载器的进度也被计入到显示的总进度中 2025-04-13 06:24:31 +08:00
薄奚梦灵andallMagic 683410743a style:空格
Co-authored-by: allMagic <[email protected]>
2025-04-12 21:45:24 +08:00
薄奚梦灵andallMagic 5302b405b7 style:空格
Co-authored-by: allMagic <[email protected]>
2025-04-12 21:45:14 +08:00
薄奚梦灵 35c5b69747 fix:shao'o'e 2025-04-11 20:28:43 +08:00
薄奚梦灵 8dc1148ebc pref(ModComp):提高代码可读性 2025-04-11 20:25:30 +08:00
薄奚梦灵 4ef328e595 fix:版本名称写错了 2025-04-11 17:27:44 +08:00
薄奚梦灵 a849c3cfd1 fix:识别问题 2025-04-11 08:49:13 +08:00
wyc-26 bd24d4c304 fix(ModModpack): 初始值不为 0 2025-04-10 16:36:01 +08:00
wyc-26 0604427492 Merge branch 'main' into fix#5966 2025-04-09 19:33:14 +08:00
wyc-26 d915fc2362 fix(ModModPack): 解压压缩包进度可能超过 100% 2025-04-09 18:21:16 +08:00
Open-KFC cd736be212 feat(ModEvent): 支持一个按钮执行多个事件
没给 HelpEntry 加这个功能,其他控件都加了
2025-04-07 07:01:54 +08:00
wyc-26 2f0958afb7 fix(PageVersionExport): 无法导出某些版本的 Xearo 文件 2025-04-06 20:10:56 +08:00
wyc-26 e038911973 chore: 筛选器增加版本 1.21.5 2025-04-06 00:38:32 +00:00
wyc-26 9021e96684 fix(FormMain): 2.9.3 没有更新日志 2025-04-05 19:01:32 +08:00
龙腾猫跃 16e09c792c 2.9.3 2025-04-05 18:22:00 +08:00
wyc-26 52ddb97938 chore(PageDownloadMod): 还是这样好了…… 2025-04-05 07:46:04 +08:00
wyc-26 31f0b81652 fix(PageDownloadMod): 小问题 2025-04-04 18:05:14 +08:00
wyc-26 ca729067c1 feat: 点击“下载新 Mod”按钮跳转到下载页面时自动填充搜索框的内容 2025-04-04 08:25:22 +08:00
龙腾猫跃 36cc718efb Merge pull request #6025 from PCL-Community/fix#6024
fix(ModDownloadLib): #5920 造成的无法下载 OptiFine
2025-04-03 23:57:48 +08:00
wyc-26 13340edaf0 fix(ModDownloadLib): #5920 造成的无法下载 OptiFine 2025-04-03 22:16:09 +08:00
龙腾猫跃 ca19c3cb35 Merge pull request #5899 from Open-KFC/fix5886
Fix:重选Fabric后FabricAPI不再自动选择
2025-04-03 03:58:35 +08:00
龙腾猫跃 8d85bb47c9 Merge pull request #6017 from LuoYun-Team/2025
支持识别 2025 年愚人节版本
2025-04-02 20:18:12 +08:00
wyc-26 56cf544bb8 fix(ModLaunch): 启动脚本乱码 2025-04-02 18:34:41 +08:00
薄奚梦灵 dcab78dcc4 黑历史+1 2025-04-02 13:46:58 +08:00
薄奚梦灵 c2a7882a4d pref(ModMinecraft):增加 2025 年愚人节版本的识别支持 2025-04-02 12:10:32 +08:00
wyc-26 c00d4e2bd6 Merge branch 'main' into fix#5966 2025-04-01 20:18:43 +08:00
薄奚梦灵 4cd8840385 style(modbase):无用换行 2025-04-01 13:41:30 +08:00
龙腾猫跃 e00e78c88c Merge pull request #5888 from shanlisi567/main
添加家园披风和入侵披风的翻译
2025-03-31 23:46:14 +08:00
龙腾猫跃 d1a6a88a7f Merge pull request #5938 from PCL-Community/fix#2294
fix(ModLaunch): 缓存问题导致切换语言失败
2025-03-31 23:43:46 +08:00
龙腾猫跃 1746b16da3 Merge pull request #5920 from PCL-Community/fix#5917
fix(ModDownloadLib): 1.14 及以上版本无法下载 OptiFine
2025-03-31 23:09:29 +08:00
龙腾猫跃 f518d12c0a Merge pull request #5996 from tangge233/fix/ValidServerCheck
chore: 防止小白无脑复制上面的 LittleSkin 地址到自动进入服务器
2025-03-31 23:00:19 +08:00
tangge233 0b888f7b01 chore: 防止小白无脑复制上面的 LittleSkin 地址 2025-03-31 22:35:09 +08:00
wyc-26 d91515c27d fix(PageDownloadInstall): 打断安装预览页面进入动画后再次切换至下载页面时显示错误*2 2025-03-28 22:35:47 +08:00
wyc-26 38c5f5e9a1 fix(PageDownloadInstall): 打断安装预览页面进入动画后再次切换至下载页面时显示错误 2025-03-28 20:35:35 +08:00
wyc-26 7d9923908b chore(MySkin): 增加渴望披风的翻译 2025-03-26 20:19:35 +08:00
wyc-26 59e93114a9 fix(ModLaunch): 缓存问题导致切换语言失败 2025-03-23 21:33:39 +08:00
wyc-26 d546528904 chore(ModDownloadLib): 按意见进行修改 2025-03-22 15:21:33 +00:00
wyc-26 f2b6ef27ed chore(ModMain): 优化代码 2025-03-22 15:11:33 +00:00
wyc-26 207ebb4e27 chore(ModMain): 修改描述 2025-03-22 22:30:51 +08:00
wyc-26 6b9bfbf4f6 fix(ModDownloadLib): 1.14 及以上版本无法下载 OptiFine 2025-03-22 22:28:12 +08:00
薄奚梦灵 4e14427349 fix(ModModPack):还不让用 Finally 了是吧 2025-03-22 15:53:01 +08:00
薄奚梦灵 0aabb69e16 fix(ShellOnly):只为可能出错的部分弹出提示 2025-03-22 15:43:56 +08:00
薄奚梦灵 d1d19a4bb3 fix(ModBase):加个 Try Catch 2025-03-22 09:14:23 +08:00
shanlisi567 5f7c5b2770 更改“威胁披风”为“入侵披风” 2025-03-21 12:35:08 +08:00
Open-KFC 502af24cec fix: 重选Fabric后FabricAPI不再自动选择 2025-03-21 09:07:00 +08:00
薄奚梦灵 44be631d4a fix(PageSetupUI):修改显示文本 2025-03-21 07:36:59 +08:00
Open-KFC 1f48948ca6 fix: 通过网络获取到更多可更新模组时未同步至‘可更新’模组列表视图 2025-03-20 22:52:51 +08:00
OpenKFC 443cffa285 Merge branch 'Hex-Dragon:main' into fix5655 2025-03-20 16:28:44 +08:00
OpenKFC 7eb3591353 Merge branch 'Hex-Dragon:main' into fix4596 2025-03-20 16:18:31 +08:00
OpenKFC ba3ed00e4b Merge branch 'Hex-Dragon:main' into feat4311 2025-03-20 16:09:44 +08:00
shanlisi567 c41b6b361e cape 2025-03-19 22:54:31 +08:00
单莉斯 34697c0d27 Ctrl+C Ctrl+V MySkin.xaml.vb 2025-03-19 20:32:52 +08:00
单莉斯 a4284dbc50 从全角更改到半角MySkin.xaml.vb 2025-03-19 20:21:59 +08:00
单莉斯 fce8781b22 Update MySkin.xaml.vb 2025-03-19 19:25:48 +08:00
龙腾猫跃 05d51a1336 fix: 重命名 Result 枚举为 ProcessReturnValues 2025-03-18 22:16:47 +08:00
薄奚梦灵 d42f67066a Merge pull request #10 from Hex-Dragon/main
111
2025-03-18 09:31:32 +08:00
薄奚梦灵 b8ce4ec899 Merge pull request #8 from Hex-Dragon/main
awa
2025-03-18 09:30:53 +08:00
薄奚梦灵 92eeba5fff fix(MyMsgInput):validate 输入
不要相信用户输入
2025-03-17 12:01:47 +08:00
Open-KFC aa3f0cd432 feat #4311: 模组管理页面无需点击右侧控件即可触发全选快捷键 2025-03-17 10:03:52 +08:00
Ciilu 855ff55495 扩展名排除 2025-03-08 07:18:05 +08:00
Ciilu a174c4856a update 2025-03-07 22:12:58 +08:00
Open-KFC 16755cd4a9 fix #5655: 鼠标保持移动时抬起仍可继续改变窗口大小 2025-03-07 03:43:21 +08:00
薄奚梦灵 7c3d820f12 chore(ModJava):删掉多余的让龙猫写 2025-03-04 21:59:30 +08:00
薄奚梦灵 045c378526 chore(ModJava):注释 2025-03-04 21:57:38 +08:00
薄奚梦灵 287d3aeb45 fix(ModJava):排除下载目录的 Java 2025-03-04 21:47:43 +08:00
薄奚梦灵 da6b64c9bc fix(btnDisplayDesc_Click):少了个标记 2025-03-04 12:30:43 +08:00
薄奚梦灵 80a004ceef fix(BtnDisplayRename_Click):在重命名前检查路径 2025-03-04 12:04:17 +08:00
Open-KFC 0ee9215b8e fix #4596: 在点击刷新按钮后对应的MyListItem显示不正确 2025-03-03 23:32:05 +08:00
薄奚梦灵 40e0afefdd fix(ModValidate):未检查路径是否存在 2025-03-03 16:30:02 +08:00
huangyu 63d36f6595 Merge branch 'Hex-Dragon:main' into main 2025-01-27 15:47:35 +08:00
Pigeon0v0 4a6a17e380 ui: 从#4770 偷来了一个 Icon 2024-10-01 14:39:08 +08:00
Pigeon0v0 4de4577103 ui: 从#4770 偷来了一个 Icon 2024-10-01 14:38:18 +08:00
Pigeon0v0 0d8b8fc267 fix: 多打了一个 s 导致有些光影下不了 2024-09-22 13:14:10 +08:00
Pigeon0v0 893f791bb2 chores: 同步 #4360 有关下载任务名称的修改 2024-09-22 10:30:14 +08:00
Pigeon0v0 f0b32ab6f2 chores: 同步 #4360 有关下载任务名称的修改 2024-09-22 10:24:01 +08:00
Pigeon0v0 ff6239bb89 Merge branch 'main' into shaders 2024-09-22 10:20:14 +08:00
Pigeon0v0 d1eac50d15 Merge branch 'main' into datapacks 2024-09-22 10:16:53 +08:00
Pigeon0v0 9d3be21090 fix: 修复了查看数据包工程时崩溃的问题,但可能会炸掉点别的东西,需要测试 2024-09-22 10:15:49 +08:00
Pigeon0v0 bec86c0022 Merge branch 'main' into resourcepacks 2024-08-28 19:44:46 +08:00
huangyu c0f8b5c701 End if 2024-08-15 15:07:37 +08:00
huangyuandallMagic 53578a8de9 Update Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
Co-authored-by: allMagic <[email protected]>
2024-08-03 21:46:42 +08:00
huangyu 40e421ee34 launchArgument to VersionAdvanceGame 2024-08-03 21:15:18 +08:00
huangyu 7534f9da1f Read JvmArgs and LaunchArgs in ModPack 2024-08-03 21:04:57 +08:00
Pigeon0v0 c1ba65783d feat: 支持加载数据包列表(但是下不了一点) 2024-07-27 18:33:04 +08:00
Pigeon0v0 51e3f47bac chores: 修改标签,消除歧义 2024-07-27 17:01:20 +08:00
Pigeon0v0 94ccc00f95 fix: 光影包列表加载动画文本错误
fix: 无法获取 Modrinth 上 Vanilla Shader 分类的光影(原 URL 写错了)
2024-07-27 16:45:04 +08:00
Pigeon0v0 f45322905f feat: 数据包下载 (WIP) 2024-07-27 12:54:27 +08:00
Pigeon0v0 c556dbb39c feat: 光影包下载 2024-07-27 11:03:59 +08:00
Pigeon0v0 da56c5b44b Merge branch 'main' into resourcepacks 2024-07-11 20:38:14 +08:00
Pigeon0v0 7b8cb73115 同步 #3319 修改 2024-07-04 22:59:18 +08:00
Pigeon0v0 e3c27678b6 fix: 合并了 Curse 和 Modrinth 的部分相似标签(去重) 2024-06-27 08:27:09 +08:00
Pigeon0v0 8e58cb0dc6 fix: 错误的 ComboBox 大小 2024-06-15 19:18:48 +08:00
Pigeon0v0 937f034a1b feat: 支持下载资源包 2024-06-13 22:23:36 +08:00
277 changed files with 54372 additions and 40819 deletions

No files matched your search

+20 -1
View File
@@ -1,4 +1,23 @@
[*.{cs,vb}]
# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
root = true
[*]
charset = utf-8
[*.cs]
# IDE0320: 使匿名函数成为静态函数
csharp_prefer_static_anonymous_function = false
# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
dotnet_diagnostic.CS8618.severity = suggestion
# IDE0305: 使用 [.. ] 简化集合初始化
dotnet_diagnostic.IDE0305.severity = none
# CS8524: switch 表达式不会处理其输入类型的某些值(它不是穷举),这包括未命名的枚举值。
dotnet_diagnostic.CS8524.severity = suggestion
[*.{cs,vb}]
# IDE0028: 简化集合初始化
dotnet_style_collection_initializer = true:silent
# IDE0130: 命名空间与文件夹结构不匹配
dotnet_diagnostic.IDE0130.severity = silent
+1 -1
View File
@@ -1,3 +1,3 @@
# These are supported funding model platforms
custom: ['https://afdian.com/a/LTCat']
custom: ['https://meloong.com/afd/a/LTCat']
+3 -3
View File
@@ -8,11 +8,11 @@ body:
label: "检查项"
description: "请逐个检查下列项目,并勾选确认。"
options:
- label: "**我所启动的游戏不是整合包,且未安装任何 Mod。** 安装 Mod 后的游戏崩溃基本不是 PCL 的原因,请 **不要** 提交反馈。你可以在论坛或社区找人求助,但这里并不是你求助的地方。"
- label: "**我所启动的游戏不是整合包,且未安装任何 Mod。** 安装 Mod 后的崩溃基本不是 PCL 导致的,请 **不要** 提交反馈。你可以前往 [PCL 汉医堂](https://qm.qq.com/q/JJTzwEAmcw) 等民间 QQ 群求助,但这里不是求助的地方……"
required: false
- label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)"
required: true
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
required: true
- type: textarea
id: "yml-2"
@@ -36,4 +36,4 @@ body:
description: "在 HMCL 中进入版本列表,点击版本右侧的三个点,选择 生成启动脚本。"
placeholder: "先点击这个文本框,然后再将文件直接拖拽到文本框中以上传。"
validations:
required: false
required: false
+1 -1
View File
@@ -12,7 +12,7 @@ body:
required: false
- label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。"
required: true
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
required: true
- type: textarea
id: "yml-2"
+1 -1
View File
@@ -8,7 +8,7 @@ body:
label: "检查项"
description: "请逐个检查下列项目,并勾选确认。"
options:
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
required: true
- type: textarea
id: "yml-2"
+2 -2
View File
@@ -8,9 +8,9 @@ body:
label: "检查项"
description: "请逐个检查下列项目,并勾选确认。"
options:
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一建议未被提交过。"
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一建议未被提交过。"
required: true
- label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。"
- label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。"
required: true
- type: textarea
id: "yml-2"
+3 -3
View File
@@ -1,14 +1,14 @@
blank_issues_enabled: false
contact_links:
- name: 主页预设反馈
url: https://github.com/Hex-Dragon/PCL2/discussions/categories/自定义主页
url: https://github.com/Meloong-Git/PCL/discussions/categories/自定义主页
about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈
- name: 帮助文档反馈
url: https://github.com/LTCatt/PCL2Help/issues
about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈
- name: 提问
url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE
url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE
about: 我想问一些 PCL 相关的问题……
- name: 讨论
url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E8%AE%A8%E8%AE%BA
url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E8%AE%A8%E8%AE%BA
about: 我想讨论一些 PCL 相关的事情……
+2 -2
View File
@@ -8,9 +8,9 @@ body:
label: "检查项"
description: "请逐个检查下列项目,并勾选确认。"
options:
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一提案未被提交过。"
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一提案未被提交过。"
required: true
- label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。"
- label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。"
required: true
- label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……"
required: true
+10 -7
View File
@@ -21,9 +21,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-depth: 1
submodules: true
- name: Set Describe
shell: bash
@@ -32,13 +33,12 @@ jobs:
echo "describe=$describe" >> $GITHUB_ENV
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v2
uses: microsoft/setup-msbuild@v3
with:
msbuild-architecture: x64
- name: Update Help
run: |
$workpath = [System.Environment]::CurrentDirectory
Set-Location ..
git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1
Set-Location PCL2Help
@@ -52,15 +52,18 @@ jobs:
- name: Replace
run: |
(gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const OAuthClientId As String = ""', 'Public Const OAuthClientId As String = "${{ secrets.CLIENT_ID }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
(gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const CurseForgeAPIKey As String = ""', 'Public Const CurseForgeAPIKey As String = "${{ secrets.CURSEFORGE_API_KEY }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
(gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")', 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "${{ secrets.CLIENT_ID }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
(gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")', 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "${{ secrets.CURSEFORGE_API_KEY }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
(gc "Plain Craft Launcher 2\Modules\Base\ModBase.vb") -replace 'Public Const CommitHash As String = ""', 'Public Const CommitHash As String = "${{ github.sha }}"' | Out-File "Plain Craft Launcher 2\Modules\Base\ModBase.vb"
- name: Restore NuGet Packages
run: dotnet restore PCLCS\PCLCS.csproj
- name: Build
run: msbuild "Plain Craft Launcher 2\Plain Craft Launcher 2.vbproj" -p:Configuration=${{ matrix.configuration }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.configuration }} ${{ env.describe }}
path: Plain Craft Launcher 2\obj\${{ matrix.configuration }}\Plain Craft Launcher 2.exe
+3
View File
@@ -0,0 +1,3 @@
[submodule "MeloongCore"]
path = MeloongCore
url = https://github.com/Meloong-Git/MeloongCore.git
+39 -22
View File
@@ -1,33 +1,50 @@
本文件仅作为指引您合理使用本存储库内容的指南,不是法律文件或协议。
《PCL 分发有限许可》
一、承诺与声明。
详见 https://shimo.im/docs/rGrd8pY8xWkt6ryW#anchor-X0bo
  开发者承诺,在您遵守本指南且善意使用的情况下,不会追究您的责任,但最终裁量权仍由开发者保留。
  法律上,开发者仍保留所有权利(All Rights Reserved),且可能随时修改本指南,本指南并不是永久性保证。
————————————————————————————————————
二、少量使用情形。
《PCL 存储库合理使用指南》
  如果您仅合理使用本存储库中的少量内容,或是参考实现思路,您必须:
  1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。
  2、不得以任何方式暗示开发者为您或您的使用作担保。
本指南仅作为说明,不是法律文件或协议。
除《PCL 分发有限许可》明确授予的权利外,开发者保留软件及其相关知识产权中的全部权利。但开发者承诺,在遵守本指南,且善意、合理使用的情况下,不会追究责任。
三、大量使用情形。
一、轻度使用
  如果您基于该存储库创作衍生作品,或合理使用了本存储库中的较大量内容,您必须:
  1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。
  2、不得以任何方式暗示开发者为您或您的使用作担保。
  3、给出适当的说明,以明确表示该衍生作品是非官方内容。
  4、使用本指南文件作为协议,不得附加额外条款。
  5、该衍生作品的名称必须以 “PCL” 或 “Plain Craft Launcher” 开头,并在其后添加后缀。例如 “PCL 整合包优化版”、“PCL PR Collection”。
  6、该衍生作品不得包含 PCL 的赞助解锁功能,或与其相近的功能或内容。例如将配色修改为蓝色色系以外的颜色。
  7、以善意目的进行合理使用,不要用 PCL 来干坏事、恰烂钱、整烂活。开发者可能会将撤销所有授权作为最终手段。
若您仅参考了本存储库中极小部分的内容,或您在制作与 PCL 的实质功能不存在任何重叠的软件,则为轻度使用。
其常见情形例如:
· 使用或参考存储库中极小部分的内容,来开发启动器或其他软件。
· 虽参考了较大量的内容,但制作的软件与 PCL 的实质功能不存在任何重叠。
————————————
若属于轻度使用,您需要:
1、以适当的形式给出署名。
2、不得将您的内容与 PCL 混淆,或暗示您的内容与 PCL 有关。
 例如:您的内容不得使用 Potato Craft Launcher、PCL 手机版 等会造成混淆或暗示这与 PCL 有关的名称。
二、重度使用
若不符合轻度使用的情形,即为重度使用。
其常见情形例如:
· 基于本存储库进行修改。
· 参考存储库中较大量的内容,以制作或重新实现 PCL 的部分实质功能(例如启动或下载 Java 版 Minecraft,下载或管理 Mod 等)。
若属于重度使用,您需要:
1、明确表示您的内容是第三方基于 PCL 独立进行二次创作的产物。
2、您的内容的名称必须以 Plain Craft Launcher (PCL) 开头,并在其后添加足以表现其由第三方修改的后缀。
 允许的示例:PCL 某服务器魔改版、PCL PR Collection、PCL (XX Bug Fix)、PCL XXX修改版。
 禁止的示例:PCL 某服务器特供版(暗示由官方提供)、PCL 3、PCL 手机版、PCL++、PCL Pro(这像是官方的后续作品)。
3、在关于页面的首位给出龙腾猫跃的署名及对应的赞助链接,公开源代码,且继续使用本文件作为指南(可参考 CC BY-SA 协议)。
4、软件若有 Minecraft 启动功能,则应实现与 McLaunchPrecheck 函数一致(或更激进)的 Minecraft 正版购买劝导与赞助劝导。
5、软件在 Windows 系统上运行时,不应有与 PCL 的赞助解锁功能类似的功能或表现。
 例如:界面的主要色相必须为蓝色(即使没有修改功能,改为其他色相也和需要赞助解锁的主题功能类似)。
————————————————————————————————————
说人话的备注:
因为没有法律专业人士检查,所以这不敢作为正式的法律协议,如果有专业人士愿意帮忙改一改我也很乐意……
这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
这些规则主要是为了阻止恶意的使用、倒卖和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
如果你只是想参考一下,或者用了一小部分代码,署个名就行啦。
如果你整了个魔改版,或者用了一大堆代码,请仔细参考上面的第三条。
如果你只是参考了一小段代码,署个名就行啦,不用担心。
如果对具体细节有疑问,或是希望修改这些规则,欢迎在 https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE 发帖!
多谢大家啦!
Submodule
+1
Submodule MeloongCore added at 98157d65c7.
+246
View File
@@ -0,0 +1,246 @@
# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
root = true
# c# 文件
[*.cs]
#### Core EditorConfig 选项 ####
# 缩进和间距
indent_size = 4
indent_style = space
tab_width = 4
# 新行首选项
end_of_line = crlf
insert_final_newline = false
#### .NET 代码操作 ####
# 类型成员
dotnet_hide_advanced_members = false
dotnet_member_insertion_location = with_other_members_of_the_same_kind
dotnet_property_generation_behavior = prefer_throwing_properties
# 符号搜索
dotnet_search_reference_assemblies = true
#### .NET 编码约定 ####
# 组织 Using
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. 和 Me. 首选项
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# 语言关键字与 bcl 类型首选项
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# 括号首选项
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# 修饰符首选项
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# 表达式级首选项
dotnet_prefer_system_hash_code = true
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# 字段首选项
dotnet_style_readonly_field = true
# 参数首选项
dotnet_code_quality_unused_parameters = all
# 禁止显示首选项
dotnet_remove_unnecessary_suppression_exclusions = none
# 新行首选项
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### c# 编码约定 ####
# var 首选项
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied 成员
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# 模式匹配首选项
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true:suggestion
csharp_style_prefer_switch_expression = true
# Null 检查首选项
csharp_style_conditional_delegate_call = true
# 修饰符首选项
csharp_prefer_static_anonymous_function = true
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# 代码块首选项
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_prefer_system_threading_lock = true
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true
# 表达式级首选项
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_implicitly_typed_lambda_expression = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_unbound_generic_type_in_nameof = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# "using" 指令首选项
csharp_using_directive_placement = outside_namespace
# 新行首选项
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# 格式规则 ####
# 新行首选项
csharp_new_line_before_catch = false
csharp_new_line_before_else = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_anonymous_types = false
csharp_new_line_before_members_in_object_initializers = false
csharp_new_line_before_open_brace = none
csharp_new_line_between_query_expression_clauses = false
# 缩进首选项
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = flush_left
csharp_indent_switch_labels = false
# 空格键首选项
csharp_space_after_cast = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = ignore
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# 包装首选项
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### 命名样式 ####
# 命名规则
dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
# 符号规范
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.类型.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.类型.required_modifiers =
dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.非字段成员.required_modifiers =
# 命名样式
dotnet_naming_style.帕斯卡拼写法.required_prefix =
dotnet_naming_style.帕斯卡拼写法.required_suffix =
dotnet_naming_style.帕斯卡拼写法.word_separator =
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
dotnet_naming_style.以_i_开始.required_prefix = I
dotnet_naming_style.以_i_开始.required_suffix =
dotnet_naming_style.以_i_开始.word_separator =
dotnet_naming_style.以_i_开始.capitalization = pascal_case
+28
View File
@@ -0,0 +1,28 @@
namespace PCLCS;
public static class Configs {
public static readonly DynamicConfigEntry<McInstallManifest> McInstallManifest = new("MinecraftInstallManifest", null);
// Java
/// <summary> 当前 Java 相关配置的版本。0 代表尚未从老的 Settings 系统中迁移。</summary>
public static readonly ConfigEntry<int> JavaConfigVersion = new("JavaConfigVersion", 0);
public static readonly ConfigEntry<ConcurrentList<Java>> JavaList = new("JavaList", []);
public static readonly ConfigEntry<List<string>> JavaRemovedList = new("JavaRemovedList", []);
public static readonly DynamicConfigEntry<bool> JavaMigrated = new("InstanceMigratedJava", false);
public static readonly DynamicConfigEntry<Java> JavaForced = new("InstanceForcedJava", null);
// 提示
public static readonly ConfigEntry<bool> HintModifyInstanceWarning = new("HintModifyInstanceWarning", false);
static Configs() {
/// 当 JavaForced 获取到一个不在 JavaList 中的值时,将其重置为 null。
JavaForced.PreviewGet += (args, provider) => {
if (args.Value is not null && !JavaList.Get()!.Contains(args.Value)) {
args.Value = null;
JavaForced.Reset(provider);
}
};
}
}
+84
View File
@@ -0,0 +1,84 @@
namespace PCLCS;
/// <summary>
/// 社区资源的类型。
/// </summary>
[Flags]
public enum ResourceTypes {
/// <summary>Mod。</summary>
Mod = 1,
/// <summary>整合包。</summary>
ModPack = 2,
/// <summary>资源包。</summary>
ResourcePack = 4,
/// <summary>光影包。</summary>
Shader = 8,
/// <summary>数据包。</summary>
DataPack = 16,
/// <summary>服务端插件。</summary>
Plugin = 32,
/// <summary>地图。</summary>
Map = 64,
/// <summary>同时包含数据包以及 Mod。</summary>
ModOrDataPack = Mod | DataPack,
/// <summary>允许任意种类,或种类未知。</summary>
Any = Mod | ModPack | ResourcePack | Shader | DataPack | Plugin | Map,
}
/// <summary>
/// 社区资源的来源平台。
/// </summary>
[Flags]
public enum ResourcePlatforms {
CurseForge = 1,
Modrinth = 2,
Any = CurseForge | Modrinth,
}
/// <summary>
/// Mod 加载器的类型。
/// </summary>
[Flags]
public enum ModLoaders {
None = 0,
Forge = 1,
LiteLoader = 2,
Fabric = 4,
NeoForge = 16,
All = Forge | LiteLoader | Fabric | NeoForge,
}
/// <summary>
/// 在爱发电中的赞助等级。
/// </summary>
public enum DonationRank {
None = 0,
Rank6 = 6,
Rank12 = 12,
Rank23 = 23,
Rank54 = 54,
Rank98 = 98,
}
public static class Versions {
/// <summary>
/// 土豆码版本号。
/// </summary>
public const char PotatoVersion = '1';
/// <summary>
/// 版本管理中 Mod 信息缓存的版本号。
/// </summary>
public const int LocalModCacheVersion = 21;
/// <summary>
/// Minecraft 本地实例信息缓存的版本号。
/// </summary>
public const int McInstanceCacheVersion = 39;
/// <summary>
/// Java 相关配置的版本号。
/// </summary>
public const int JavaConfigVersion = 1;
/// <summary>
/// Minecraft 安装清单的版本号。
/// </summary>
public const int McInstallManifestVersion = 1;
}
+252
View File
@@ -0,0 +1,252 @@
namespace PCLCS;
/// <summary>
/// 单个 Java 的实例。
/// </summary>
[Serializable] public class Java(string folder, Version? version = null) : IEquatable<Java> {
/// <summary>
/// java.exe 文件所在的文件夹路径,这通常是 bin 文件夹。以 \ 结尾。
/// </summary>
public readonly string Folder = PathUtils.AddSlashSuffix(PathUtils.ForCompare(folder));
/// <summary>
/// java.exe 文件的完整路径。
/// </summary>
[JsonIgnore] public string JavaExePath => Path.Combine(Folder, "java.exe");
/// <summary>
/// Java 的版本号(例如 16.0.1),保证 <see cref="Version.Major"/> 为其主版本。
/// <para/> 若为 null,则还需要调用 <see cref="CheckAsync"/> 以获取版本号。
/// </summary>
public Version? Version = version;
/// <summary>Java 的默认字符编码。</summary>
[JsonIgnore] public string? FileEncodingName;
/// <summary>
/// Java 根据系统环境确定的本地字符编码,仅 Java 17+ 可用。
/// <para/>若系统开启了 UTF-8,部分 Java 会错误地返回 JDK,因此不能使用 <see cref="Encoding.Default"/>。
/// </summary>
[JsonIgnore] public string? NativeEncodingName;
/// <summary>
/// 检查该 Java 是否存在问题,并获取其版本号。
/// 返回其是否通过了检查。
/// </summary>
public async Task<bool> CheckAsync(CancellationToken cancellationToken = default) {
if (Interlocked.Exchange(ref isChecked, 1) == 1 && available != null) return available!.Value; // 已检查过
string? output = null;
try {
if (!FileUtils.Exists(JavaExePath)) throw new FileNotFoundException("未找到 java.exe 文件", JavaExePath);
// 运行 -version
output = (await TaskUtils.RunProgramAsync(JavaExePath, "-XshowSettings:properties -version", 15000, cancellationToken: cancellationToken).NoCapture()).Output.Lower();
if (output == "") throw new InvalidOperationException("尝试运行该 Java 失败");
Logger.Trace($"Java 检查输出:{JavaExePath}{Environment.NewLine}{output}");
if (output.Contains("/lib/ext exists")) throw new InvalidOperationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试");
if (output.Contains("a fatal error") || output.Contains("error: ")) throw new InvalidOperationException("无法运行该 Java,该 Java 或系统存在问题");
// 获取版本号
var versionString = (output.RegexSeek(@"(?<=version "")[^""]+") ?? output.RegexSeek("(?<=openjdk )[0-9]+"))?.Replace("_", ".").Replace("+", ".").BeforeFirst("-");
if (string.IsNullOrEmpty(versionString)) throw new InvalidOperationException($"未找到该 Java 的版本号{(output.Length < 500 ? $"\n原始输出为:\n{output}" : "")}");
if (versionString.StartsWithF("1.")) versionString = versionString!.AfterFirst("."); // 去除开头的 1.
var segments = versionString!.Split(".", true).ToList();
while (segments.Count < 4) segments.Add("0");
Version = new Version(segments.Take(4).Join("."));
if (Version.Major is <= 4 or >= 100) throw new InvalidOperationException($"分析详细信息失败,获取的版本为 {Version}{(output.Length < 500 ? $"\n原始输出为:\n{output}" : "")}");
// 获取 Java 的相关属性
if (!output.Contains("64-bit")) throw new InvalidOperationException("该 Java 为 32 位版本,请安装 64 位的 Java");
string? GetProperty(string name) {
string prefix = name + " = ";
string? line = output.Split('\n').Select(l => l.Trim()).FirstOrDefault(l => l.StartsWithF(prefix));
return line?[prefix.Length..].Trim();
}
FileEncodingName = GetProperty("file.encoding");
NativeEncodingName = GetProperty("native.encoding") ?? FileEncodingName; // 旧版本没有 native.encoding
Logger.Info($"检查 Java 成功:{this}");
available = true;
} catch (Exception ex) {
if (ex.IsCanceled()) {
isChecked = 0;
throw;
}
Logger.Warn($"检查 Java 失败({JavaExePath}),其输出为:\n{output ?? "无程序输出"}");
available = false;
}
return available!.Value;
}
public void InvalidateChecked() {
isChecked = 0;
available = null;
}
[JsonIgnore] private int isChecked = 0;
/// <summary>若已进行过检查,表示该检查是否成功。若未检查,则为 null。</summary>
[JsonIgnore] internal bool? available = null;
// 相同检查
public bool Equals(Java? other)
=> other is not null && PathUtils.IsSame(Folder, other.Folder);
public override bool Equals(object? obj) => obj is Java other && Equals(other);
public override int GetHashCode() => StringComparer.CurrentCultureIgnoreCase.GetHashCode(PathUtils.ForCompare(Folder));
/// <summary>用户友好的描述文本。</summary>
public override string ToString()
=> $"Java {Version?.Major.ToString() ?? "尚未检查"} ({Version}):{Folder}";
}
public static class JavaUtils {
/// <summary>
/// 检查所有指定的 Java 是否存在问题,并获取其版本号。
/// </summary>
/// <returns>所有通过检查的 Java。</returns>
public static async Task<List<Java>> CheckAllAsync(IEnumerable<Java> javas, CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
await TaskUtils.WhenAll(javas.Where(j => j.available == null).Select(
async java => await java.CheckAsync(cancellationToken).NoCapture()
), progress).NoCapture();
return javas.Where(j => j.available == true).ToList();
}
/// <summary>
/// 将 Java 列表进行排序。
/// 这同时会检查所有未检查的 Java。
/// </summary>
public static async Task<List<Java>> SortAsync(List<Java> javas, CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
javas = await JavaUtils.CheckAllAsync(javas, cancellationToken, progress).NoCapture();
javas.Sort((left, right) => {
// 优先使用带完整 Java 的文件夹列表中的 Java
var isLeftInCandidateFolder = JavaUtils.CandidateFolders.Value.Any(folder => PathUtils.IsParentOf(folder, left.Folder));
var isRightInCandidateFolder = JavaUtils.CandidateFolders.Value.Any(folder => PathUtils.IsParentOf(folder, right.Folder));
if (isLeftInCandidateFolder != isRightInCandidateFolder) return isLeftInCandidateFolder ? -1 : 1;
// 其次优先使用主版本号接近 21 的 Java
var leftDistance = left.Version is null ? int.MaxValue : Math.Abs(left.Version.Major - 21);
var rightDistance = right.Version is null ? int.MaxValue : Math.Abs(right.Version.Major - 21);
return leftDistance.CompareTo(rightDistance);
});
return javas;
}
#region 搜索
/// <summary>
/// 存在完整 Java 的文件夹列表。
/// </summary>
internal static readonly Lazy<List<string>> CandidateFolders = new(() => {
List<string> folders = [
Paths.AppData + @".minecraft\runtime\", // 这也是 PCL 下载 Java 的路径
Paths.AppData + @".hmcl\java\",
Paths.AppData + @"ATLauncher\runtimes\minecraft\",
Paths.AppData + @"ModrinthApp\meta\java_versions\",
Paths.AppData + @"PrismLauncher\java\",
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\curseforge\minecraft\Install\runtime\",
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.jdks\",
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.sdkman\candidates\java\",
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\.ftba\bin\runtime\",
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime\",
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + @"\Minecraft Launcher\runtime\",
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + @"\Minecraft\runtime\",
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Curse\Minecraft\Install\runtime\",
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Java\",
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Eclipse Adoptium\",
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Amazon Corretto\",
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Zulu\",
..DirectoryUtils.EnumerateDirectories(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Microsoft\", searchPattern: "jdk-*"),
// 环境变量
..(Environment.GetEnvironmentVariable("JDK_HOME") + ";" + Environment.GetEnvironmentVariable("JAVA_HOME")).
Split(";", true).Select(path => path.Trim(' ', '"'))
];
return folders.Select(f => {
try {
return PathUtils.ForCompare(f);
} catch (Exception ex) {
Logger.Warn(ex, $"Java 候选文件夹路径无效({f})");
return null;
}
}).Where(f => f is not null).Cast<string>().ToList();
});
/// <summary>
/// 从常见文件夹和环境变量中搜索 Java,并将搜索结果写入设置。
/// </summary>
public static async Task RefreshListAsync(CancellationToken c = default, ProgressProvider? p = null) {
// 搜索
List<Java> javaList = await SearchFoldersAsync(true, CandidateFolders.Value, c, p?.SplitTo(0.5)).NoCapture();
// 将新发现的 Java 排在最前,原有列表保留既有顺序排在后面
var oldJavaList = Configs.JavaList.Get()!;
oldJavaList = await JavaUtils.CheckAllAsync(oldJavaList, c, p?.SplitTo(0.9)).NoCapture(); // 检查原有列表中的 Java
javaList.RemoveAll(oldJavaList); // 此时 javaList 中只剩下新发现的 Java
javaList = await JavaUtils.SortAsync(javaList, c).NoCapture(); // 将所有新发现的 Java 按优先级排序
javaList.AddRange(oldJavaList);
javaList = javaList.Distinct().ToList();
// 删掉已被移除的 Java
javaList.RemoveIf(j => Configs.JavaRemovedList.Get()!.Contains(j.Folder, StringComparer.OrdinalIgnoreCase));
// 写入设置
Configs.JavaList.Set([..javaList]);
Logger.Info($"Java 搜索完成,发现 {javaList.Count} 个 Java");
}
/// <summary>
/// 并行搜索所有指定的文件夹及其子文件夹中的 Java,并检查其有效性。
/// </summary>
public static async Task<List<Java>> SearchFoldersAsync(bool includeSubDirectories, IEnumerable<string> folders, CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
// 并行搜索
ConcurrentBag<(string Folder, bool HasReparsePoint, bool IsSpecialPath)> results = [];
await TaskUtils.ForEachAsync(folders.Distinct(StringComparer.OrdinalIgnoreCase), 4, async (folder, cp) => {
await Task.Run(() => {
try {
foreach (var file in DirectoryUtils.EnumerateFiles(folder, includeSubDirectories, "java.exe")) {
cancellationToken.ThrowIfCancellationRequested();
string targetFolder = PathUtils.AddSlashSuffix(PathUtils.ForCompare(PathUtils.RemoveLastPart(file)));
// 判断文件夹是否包含重解析点(例如符号链接)
static bool HasReparsePoint(FileSystemInfo info) {
if (PathUtils.IsParentOf(Paths.AppData + @".minecraft\runtime\", info.FullName)) return false; // 不筛查 PCL 的下载路径(#8928)
do {
if (info.Attributes.HasFlagF(FileAttributes.ReparsePoint)) return true;
info = info is FileInfo fileInfo ? fileInfo.Directory : ((DirectoryInfo) info).Parent;
} while (info is not null);
return false;
}
// 结果
var hasReparsePoint = HasReparsePoint(FileUtils.GetInfo(Path.Combine(targetFolder, "java.exe")));
var isSpecialPath = targetFolder.ContainsIgnoreCase("java8path_target_") || targetFolder.ContainsIgnoreCase("javapath_target_") || targetFolder.ContainsIgnoreCase("javatmp") || targetFolder.ContainsIgnoreCase("system32");
results.Add((targetFolder, hasReparsePoint, isSpecialPath));
}
} catch (UnauthorizedAccessException) {
Logger.Info($"快速查找 Java 时没有权限({folder})");
} catch (Exception ex) {
ex.ThrowIfCanceled();
Logger.Warn(ex, $"快速查找 Java 时出错({folder})");
}
}, cancellationToken).NoCapture();
}, cancellationToken, progress?.SplitTo(0.2)).NoCapture();
if (!results.Any()) return [];
// 筛除重解析点
if (results.All(r => r.HasReparsePoint)) {
Logger.Warn("找到的所有 Java 都包含重解析点,保留全部结果");
} else if (results.Any(r => r.HasReparsePoint)) {
Logger.Warn($"找到的以下 Java 包含重解析点,将它们从列表中排除:{results.Where(r => r.HasReparsePoint).Select(r => r.Folder).Join('、')}");
results = [..results.Where(r => !r.HasReparsePoint)];
}
// 筛除特殊路径
if (results.All(r => r.IsSpecialPath)) {
Logger.Warn("找到的所有 Java 都包含特殊路径,保留全部结果");
} else if (results.Any(r => r.IsSpecialPath)) {
Logger.Warn($"找到的以下 Java 包含特殊路径,将它们从列表中排除:{results.Where(r => r.IsSpecialPath).Select(r => r.Folder).Join('、')}");
results = [..results.Where(r => !r.IsSpecialPath)];
}
// 检查并返回
List<Java> javaList = results.Select(r => r.Folder).Distinct(StringComparer.OrdinalIgnoreCase).Select(folder => new Java(folder)).ToList();
javaList = (await JavaUtils.CheckAllAsync(javaList, cancellationToken, progress?.SplitTo(1)).NoCapture()).ToList();
javaList = await JavaUtils.SortAsync(javaList, cancellationToken).NoCapture();
Logger.Info($"快速查找 Java 完成,共有 {javaList.Count} 个候选:{javaList.Join('、')}");
return javaList;
}
#endregion
}
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
namespace PCLCS;
public static class LaunchUtils {
/// <summary>
/// 释放补丁文件并返回完整文件路径。
/// </summary>
/// <param name="patchFilePath">补丁文件路径
/// <param name="outputDirectory">补丁文件的释放目录。</param>
public static string ExtractPatch(string patchFilePath, string outputDirectory) {
Logger.Info($"选定的资源 {patchFilePath} 输出路径:{outputDirectory}");
lock (ExtractPatchLock) { // 避免 OptiFine 和 Forge 安装时同时释放导致冲突
try {
FileUtils.ExtractResources(patchFilePath, Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath)), typeof(LaunchUtils));
} catch (Exception ex) {
if (!FileUtils.Exists(outputDirectory)) throw new FileNotFoundException($"释放 {patchFilePath} 失败", ex);
Logger.Warn(ex, $"{patchFilePath} 文件重新释放失败,将尝试更换文件名重新生成");
patchFilePath = Path.Combine(outputDirectory, $"{PathUtils.GetFileNameWithoutExtension(patchFilePath)}2.{PathUtils.GetExtension(patchFilePath)}");
try {
FileUtils.ExtractResources(patchFilePath, Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath)), typeof(LaunchUtils));
} catch (Exception ex2) {
throw new FileNotFoundException($"释放 {patchFilePath} 最终尝试失败", ex2);
}
}
}
return Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath));
}
private static readonly object ExtractPatchLock = new();
}
Binary file not shown.
+47
View File
@@ -0,0 +1,47 @@
using System.Globalization;
namespace PCLCS;
public static class McUtils {
/// <summary>
/// 比较两个版本名;左侧较新返回正数,相同返回 0,右侧较新返回负数。
/// 无法比较两个预发布版的大小。
/// 支持的格式:未知版本, 26.1-snapshot-1,1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6
/// </summary>
public static int CompareVersion(string left, string right) {
if (left == right) return 0;
if (left == "未知版本" || right == "未知版本") return left == "未知版本" ? 1 : -1;
left = left.Lower();
right = right.Lower();
var leftParts = left.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList();
var rightParts = right.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList();
for (int i = 0; ; i++) {
if (i >= leftParts.Count && i >= rightParts.Count) return string.Compare(left, right, StringComparison.Ordinal); // 两边均缺失,感觉是一个东西
string leftValue = i < leftParts.Count ? leftParts[i] : "0";
string rightValue = i < rightParts.Count ? rightParts[i] : "0";
if (leftValue == rightValue) continue;
int leftNumber = leftValue switch {
"rc" => -1,
"pre" => -2,
"snapshot" => -3,
"experimental" => -4,
_ => int.TryParse(leftValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int leftResult) ? leftResult : 0
};
int rightNumber = rightValue switch {
"rc" => -1,
"pre" => -2,
"snapshot" => -3,
"experimental" => -4,
_ => int.TryParse(rightValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int rightResult) ? rightResult : 0
};
if (leftNumber == 0 && rightNumber == 0) return string.Compare(leftValue, rightValue, StringComparison.Ordinal);
if (leftNumber != rightNumber) return leftNumber.CompareTo(rightNumber);
}
}
/// <summary>
/// 比较两个版本名;左侧不旧于右侧返回 True。
/// </summary>
public static bool CompareVersionGE(string left, string right) => CompareVersion(left, right) >= 0;
}
+71
View File
@@ -0,0 +1,71 @@
namespace PCLCS;
/// <summary>
/// MC 版本安装清单及 PCL 管理文件清单。
/// </summary>
/// <param name="SchemaVersion"> 清单格式版本;无版本的旧清单为 0。</param>
/// <param name="Components"> 安装目标及受管理组件版本。</param>
/// <param name="LastModifiedUtc"> 最后修改时间(UTC)。</param>
/// <param name="ManagedFiles"> 受管理文件;没有文件时为空列表。</param>
public record McInstallManifest(int SchemaVersion, McComponents Components, DateTime LastModifiedUtc, IReadOnlyList<McInstallManifest.ManagedFile> ManagedFiles) {
/// <summary>
/// 清理以 Mod 形式安装的、已删除或修改的组件的记录,并更新清单。
/// </summary>
public McInstallManifest RefreshModComponents(string gameFolder) {
List<ManagedFile> invalidFiles = [];
foreach (ManagedFile entry in ManagedFiles) {
if (entry.Component is null) continue; // 不检查非组件文件
FileInfo file = new(Path.Combine(gameFolder, entry.RelativePath));
if (!file.Exists || file.LastWriteTimeUtc != entry.LastWriteTimeUtc.ToUniversalTime()) invalidFiles.Add(entry);
}
if (!invalidFiles.Any()) return this;
return this with {
Components = Components with {
OptiFineEntry = invalidFiles.Any(file => file.Component == "OptiFine") ? null : Components.OptiFineEntry,
FabricApiVersion = invalidFiles.Any(file => file.Component == "FabricApi") ? null : Components.FabricApiVersion,
OptiFabricVersion = invalidFiles.Any(file => file.Component == "OptiFabric") ? null : Components.OptiFabricVersion
},
ManagedFiles = ManagedFiles.Where(file => !invalidFiles.Contains(file)).ToList(),
LastModifiedUtc = DateTime.UtcNow
};
}
/// <summary>
/// 由 PCL 管理并记录的版本内文件。
/// </summary>
/// <param name="RelativePath"> 相对于游戏目录的文件路径。确保路径为全小写且可比较。</param>
/// <param name="Sha1"> 小写的 SHA1。</param>
/// <param name="LastWriteTimeUtc"> 目标文件的最后修改时间(UTC)。</param>
/// <param name="Component"> 安装器组件名;为空时表示文件由整合包管理。</param>
public record ManagedFile(string RelativePath, string Sha1, DateTime LastWriteTimeUtc,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Component = null);
}
/// <param name="MinecraftVersion"> 目标 Minecraft 版本。</param>
/// <param name="ModLoaderVersions"> 目标中各个 Mod Loader 的版本;原版为空字典。</param>
/// <param name="OptiFineEntry"> OptiFine 条目信息,不安装时为 <see langword="null"/>。</param>
/// <param name="FabricApiVersion"> 由 PCL 管理的 Fabric API 版本;不安装时为 <see langword="null"/>。</param>
/// <param name="OptiFabricVersion"> 由 PCL 管理的 OptiFabric 版本;不安装时为 <see langword="null"/>。</param>
[method: JsonConstructor]
public record McComponents(string MinecraftVersion, Dictionary<ModLoaders, string> ModLoaderVersions,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] JObject? OptiFineEntry = null,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? FabricApiVersion = null,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? OptiFabricVersion = null) {
[JsonIgnore]
public string? OptiFineVersion => OptiFineEntry?["InstanceName"]?.Value<string>()?.AfterLast("-OptiFine_");
public McComponents(string minecraftVersion, JObject? optiFineEntry, string? forgeVersion,
string? neoForgeVersion, string? fabricVersion, string? fabricApiVersion, string? optiFabricVersion, string? liteLoaderVersion)
: this(minecraftVersion, [], optiFineEntry, fabricApiVersion, optiFabricVersion) {
if (forgeVersion is not null) ModLoaderVersions[ModLoaders.Forge] = forgeVersion;
if (neoForgeVersion is not null) ModLoaderVersions[ModLoaders.NeoForge] = neoForgeVersion;
if (fabricVersion is not null) ModLoaderVersions[ModLoaders.Fabric] = fabricVersion;
if (liteLoaderVersion is not null) ModLoaderVersions[ModLoaders.LiteLoader] = liteLoaderVersion;
}
public bool IsEquivalentTo(McComponents other) =>
MinecraftVersion == other.MinecraftVersion &&
OptiFineVersion == other.OptiFineVersion &&
FabricApiVersion == other.FabricApiVersion &&
OptiFabricVersion == other.OptiFabricVersion &&
ModLoaderVersions.Count == other.ModLoaderVersions.Count &&
ModLoaderVersions.All(entry => other.ModLoaderVersions.TryGetValue(entry.Key, out string? version) && version == entry.Value);
}
+295
View File
@@ -0,0 +1,295 @@
using System.Globalization;
namespace PCLCS;
/// <summary>
/// Minecraft 版本号和组件信息
/// </summary>
public sealed class McVersion {
/// <summary>
/// 原版版本名,如 26.1、26.1-snapshot-1、1.12.2、16w01a。
/// </summary>
public string VanillaName {
get => _vanillaName;
set {
_vanillaName = value ?? "Unknown";
Vanilla = NameToVersion(_vanillaName);
}
}
private string _vanillaName = "Unknown";
/// <summary>
/// 可比较的三段式原版版本号。
/// 旧格式会将次版本号移到主版本号,例如 1.20.1 会转换为 20.0.1,若没有版本号,例如旧快照,则为 9999.0.0。
/// </summary>
public Version Vanilla = new(9999, 0, 0);
/// <summary> 原版版本号是否可靠(不是通过猜测获取)。</summary>
public bool Reliable;
/// <summary> 原版版本号是否有效。</summary>
public bool Valid => Vanilla.Major < 1000;
/// <summary> 可供比较的原版 Drop 序数,例如 26.3.2 为 263,1.21.5 为 210;若没有版本号,例如旧快照,则直接指定为 209。</summary>
public int Drop => Valid ? Vanilla.Major * 10 + Vanilla.Minor : 209;
/// <summary> 已安装的 Mod Loader,可能有多个。未安装时为 <see cref="ModLoaders.None"/>。</summary>
public ModLoaders ModLoaders => ModLoaderVersions.Keys.Aggregate(ModLoaders.None, (result, loader) => result | loader);
/// <summary> 已安装的各个 Mod Loader 及其版本;无法确定精确版本时为“未知版本”。</summary>
public Dictionary<ModLoaders, string> ModLoaderVersions { get; } = [];
/// <summary> OptiFine 版本名,如 C8、C9_pre10;未识别到时为 null。</summary>
public string? OptiFine;
/// <summary> OptiFine 是否以 Mod 形式安装。</summary>
public bool OptiFineAsMod;
/// <summary> 版本的发布时间。</summary>
public DateTime ReleaseTime = new(1970, 1, 1, 15, 0, 0);
/// <summary> 是否为愚人节版本。</summary>
public bool IsFool;
/// <summary> 是否为快照、预发布版、发布候选或实验性版本。</summary>
public bool IsSnapshot;
/// <summary> 是否识别到已安装的 OptiFine。</summary>
public bool HasOptiFine => OptiFine is not null;
/// <summary>
/// 可供比较的 OptiFine 版本序数。
/// </summary>
public int OptiFineCode {
get {
if (OptiFine is null or "未知版本" || OptiFine.Length == 0) return 0;
// 字母编号,如 G2 中的 G(7)
int result = char.ToUpperInvariant(OptiFine[0]) - 'A' + 1;
// 末尾数字,如 C5 beta4 中的 5
result = result * 100 + (int.TryParse(OptiFine.Substring(1).RegexSeek(@"\d+"), out int mainVersion) ? mainVersion : 0);
// 测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x)
result *= 100;
string? preview = OptiFine.RegexSeek(@"(?i)(pre|beta)\d*");
if (preview is null) return result + 99;
int sequence = int.TryParse(preview.RegexSeek(@"\d+"), out int value) ? value : 1; // 为 pre 或 beta 结尾,视作 1
return result + (preview.StartsWithF("pre", true) ? 50 : 0) + sequence;
}
}
/// <summary>
/// 可供比较的类 Forge 版本序数。
/// </summary>
public int ForgelikeCode {
get {
if (!ModLoaderVersions.TryGetValue(ModLoaders.Forge, out string? version)) {
ModLoaderVersions.TryGetValue(ModLoaders.NeoForge, out version);
}
if (version is null or "未知版本") return 0;
// 提取所有数字段,以兼容 beta、alpha 和 snapshot 等非数字后缀。
int[] segments = version.RegexSearch(@"\d+")
.Select(segment => int.TryParse(segment, out int value) ? value : 0).ToArray();
return segments.Length switch {
>= 4 => segments[0] * 1000000 + segments[1] * 10000 + segments[3],
3 => segments[0] * 1000000 + segments[1] * 10000 + segments[2],
2 => segments[0] * 1000000 + segments[1] * 10000,
1 => segments[0] * 1000000,
_ => 0
};
}
}
/// <summary>
/// 创建默认 Minecraft 版本。
/// </summary>
/// <param name="vanillaName">原版版本名。</param>
/// <param name="reliable">版本名是否来自可靠依据。</param>
public McVersion(string vanillaName, bool reliable = true) {
Reliable = reliable;
VanillaName = vanillaName;
IsFool = IsFoolName(VanillaName);
IsSnapshot = IsSnapshotName(VanillaName);
}
/// <summary>
/// 通过 Components 创建 Minecraft 版本信息。
/// </summary>
public McVersion(McComponents components) : this(components.MinecraftVersion) {
OptiFine = components.OptiFineVersion?.AfterLast("HD_").AfterFirst("_");
foreach (var loader in components.ModLoaderVersions) ModLoaderVersions[loader.Key] = loader.Value;
}
/// <summary>
/// 通过 JSON 识别 Minecraft 版本和已安装组件。
/// </summary>
/// <param name="instanceName">实例文件夹名。</param>
/// <param name="json">展开的版本 JSON。</param>
/// <param name="jarVersionJson">版本 JAR 中的 version.json。</param>
public McVersion(string instanceName, JObject json, JObject? jarVersionJson = null) {
string jsonText = json.ToString(Formatting.None);
DateTime releaseTime = new(1970, 1, 1, 15, 0, 0); // 未知版本也可能显示为 1970 年
try {
if (json["releaseTime"] is JToken releaseTimeToken) releaseTime = releaseTimeToken.ToObject<DateTime>();
} catch {
}
bool reliable = true;
// 获取游戏版本
string vanillaName = (GetVanillaName(instanceName, json, jarVersionJson, releaseTime, ref reliable) ?? "Unknown").Replace("_unobfuscated", "").Replace(" Unobfuscated", "");
Reliable = reliable;
VanillaName = vanillaName;
ReleaseTime = releaseTime;
// 确定版本分类
string jsonType = json.Value<string>("type") ?? "";
// OptiFine
OptiFine = jsonText.ContainsIgnoreCase("optifine") ? jsonText.RegexSeek(@"(?<=HD_U_)[^\"":/]+") ?? "未知版本": null;
// 愚人节与快照版本
DateTime fixedReleaseTime = releaseTime.ToUniversalTime().AddHours(2);
IsFool = jsonType == "fool" || IsFoolName(vanillaName) || (fixedReleaseTime.Month == 4 && fixedReleaseTime.Day == 1 && jsonType == "snapshot");
IsSnapshot = IsSnapshotName(vanillaName) || instanceName.ContainsIgnoreCase("combat") || jsonType is "snapshot" or "pending" or "fool";
if (!Reliable) Logger.Info($"无法完全确认 MC 版本号的版本:{VanillaName}");
// LiteLoader
if (jsonText.ContainsIgnoreCase("liteloader")) {
ModLoaderVersions[ModLoaders.LiteLoader] = jsonText.RegexSeek(@"(?<=com\.mumfrey:liteloader:)[^\"":/]+") ?? "未知版本";
}
// Fabric、Forge、NeoForge
if (jsonText.ContainsIgnoreCase("net.fabricmc:fabric-loader") || jsonText.ContainsIgnoreCase("org.quiltmc:quilt-loader")) {
ModLoaderVersions[ModLoaders.Fabric] = jsonText.RegexSeek(@"(?<=(net\.fabricmc:fabric-loader:|org\.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?")?
.Replace("+build", "") ?? "未知版本";
} else if (jsonText.ContainsIgnoreCase("net.neoforge")) {
// 1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99"
// 1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta"
ModLoaderVersions[ModLoaders.NeoForge] = FindArgumentValue(json, "--fml.neoForgeVersion") ?? FindArgumentValue(json, "--fml.forgeVersion") ?? "未知版本";
} else if (jsonText.ContainsIgnoreCase("minecraftforge")) {
ModLoaderVersions[ModLoaders.Forge] = jsonText.RegexSeek(@"(?<=forge:[0-9\.]+(?:_pre[0-9]*)?-)[0-9\.]+")
?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+")
?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+") ?? "未知版本";
}
}
private static string? GetVanillaName(string instanceName, JObject json, JObject? jarVersionJson, DateTime releaseTime, ref bool reliable) {
// 获取发布时间并判断是否为老版本
if (releaseTime.Year is > 2000 and < 2013) return "Old";
// 实验性快照
if (json.Value<string>("type") == "pending") return "pending";
// 从 PCL 下载的版本信息中获取版本号
if (json["clientVersion"] is JToken clientVersion) return clientVersion.ToString();
// 从 HMCL 下载的版本信息中获取版本号
if (json["patches"] is JArray patches) {
JToken? gameVersion = patches.OfType<JObject>().FirstOrDefault(patch => patch.Value<string>("id") == "game")?["version"];
if (gameVersion is not null) return gameVersion.ToString();
}
// 从 Forge / NeoForge Arguments 中获取版本号
if (FindArgumentValue(json, "--fml.mcVersion") is { Length: > 0 } fmlVersion) return fmlVersion;
// 从继承版本中获取版本号
string inherit = json["inheritsFrom"]?.ToString() ?? "";
if (inherit.Length > 0) {
string jar = json["jar"]?.ToString() ?? ""; // LiteLoader 优先使用 Jar
return jar.Length > 0 ? jar : inherit;
}
// 从下载地址中获取版本号
string? download = json["downloads"]?.ToString(Formatting.None).RegexSeek(@"(?<=launcher.mojang.com/mc/game/)[^/]*");
if (download is not null) return download;
// 从 Forge/OptiFine/Fabric 版本中获取版本号
string librariesText = json["libraries"]!.ToString();
string[] libraryPatterns = [
@"(?<=net.minecraftforge:forge:)[0-9]{1,2}.[0-9+.]+",
@"(?<=net.minecraftforge:fmlloader:)[0-9]{1,2}.[0-9+.]+",
@"(?<=optifine:OptiFine:)[0-9]{1,2}.[0-9+.]+",
@"(?<=((fabricmc)|(quiltmc)):intermediary:)[^\""]*"
];
foreach (string pattern in libraryPatterns) {
if (librariesText.RegexSeek(pattern) is string match) return match;
}
// 从 jar 项中获取版本号
if (json["jar"] is JToken jarToken) return jarToken.ToString();
// 从 jar 文件的 version.json 中获取版本号
if (jarVersionJson?["name"] is JToken jarNameToken && jarNameToken.ToString() is { Length: < 32 } jarName) { // 因为 wiki 说这玩意儿可能是个 hash,虽然我没发现
Logger.Info($"从版本 jar 中的 version.json 获取到版本号:{jarName}");
return jarName;
}
string VersionPattern = @"(([1-9][0-9]w[0-9]{2}[a-g])|((1|[2-9][0-9])\.[0-9]+(\.[0-9]+)?(-(pre|rc|snapshot-?)[1-9]*| Pre-Release( [1-9])?)?))(_unobfuscated)?";
// 从 JSON 的 ID 中获取
if (json["id"]!.ToString().RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string idMatch) return idMatch;
// 非准确的版本判断警告
Logger.Info($"无法完全确认 MC 版本号的版本:{instanceName}");
reliable = false;
// 从文件夹名中获取
if (instanceName.RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string folderMatch) return folderMatch;
// 从 JSON 出现的版本号中获取
JObject withoutLibraries = (JObject) json.DeepClone();
withoutLibraries.Remove("libraries");
return withoutLibraries.ToString(Formatting.None).RegexSeek(VersionPattern, RegexOptions.IgnoreCase);
}
private static bool IsFoolName(string name) {
name = name.Lower();
return name.StartsWithF("2.0") || name is "15w14a" or "1.rv-pre1" or
"3d shareware v1.34" or "20w14∞" or "22w13oneblockatatime" or "23w13a_or_b" or
"24w14potato" or "25w14craftmine" or "26w14a" || name.StartsWithF("20w14inf");
}
private static bool IsSnapshotName(string name) =>
new[] { "w", "snapshot", "rc", "pre", "experimental", "-" }.Any(name.ContainsIgnoreCase);
/// <summary>
/// 读取 arguments 中指定参数的字符串值。
/// </summary>
private static string? FindArgumentValue(JObject json, string name) {
JArray? arguments = json["arguments"]?["game"] as JArray;
if (arguments is null) return null;
for (int i = 0; i + 1 < arguments.Count; i++) {
if (arguments[i]?.ToString() == name) return arguments[i + 1]?.ToString();
}
return null;
}
/// <summary>
/// 判断版本字符串是否符合 Minecraft 原版格式,例如 1.x、26.x。
/// </summary>
public static bool IsStandardFormat(string? version) {
if (version is null) return false;
if (Regex.IsMatch(version, @"^1\.\d")) return true;
return double.TryParse(version.RegexSeek(@"^[2-9]\d\.\d+"), NumberStyles.Float,
CultureInfo.InvariantCulture, out double value) && value >= 26;
}
/// <summary>
/// 将版本名转换为 Drop 序数;无法转换时返回 209。
/// </summary>
public static int VersionToDrop(string? vanillaName) {
if (vanillaName is null) return 209;
Version version = NameToVersion(vanillaName);
return version.Major >= 1000 ? 209 : version.Major * 10 + version.Minor;
}
/// <summary>
/// 将 Drop 序数转换为版本字符串。
/// </summary>
public static string DropToVersion(int drop) {
if (drop >= 250) return $"{drop / 10}.{drop % 10}";
if (drop != 209) return $"1.{drop / 10}";
Logger.Warn(new Exception("尝试将旧快照版 Drop 序数转换为版本字符串,这不应该发生!"));
return "1.20";
}
/// <summary>
/// 将版本名转换为可比较的三段式原版版本号。
/// 对老版本格式,例如 1.20.3,会被转换为 20.0.3。
/// 对愚人节版本,返回其基于的正式版版本号。
/// 若没有版本号,例如旧快照,则为 9999.0.0。
/// </summary>
public static Version NameToVersion(string? name) {
if (name is null) return new Version(9999, 0, 0);
name = name.Lower().Replace("_unobfuscated", "").Replace(" unobfuscated", "");
// 愚人节版本
if (name.StartsWithF("2.0")) return NameToVersion("1.5.1");
if (name == "15w14a") return NameToVersion("1.8.3");
if (name.ContainsIgnoreCase(".rv-pre")) return NameToVersion("1.9.2");
if (name.ContainsIgnoreCase("shareware")) return NameToVersion("1.13.2");
if (name.StartsWithF("20w14") && name != "20w14a") return NameToVersion("1.15.2");
if (name.ContainsIgnoreCase("oneblockatatime")) return NameToVersion("1.18.2");
if (name.ContainsIgnoreCase("23w13a") && name != "20w13a") return NameToVersion("1.19.4");
if (name == "24w14potato") return NameToVersion("1.20.4");
if (name == "25w14craftmine") return NameToVersion("1.21.4");
if (name == "26w14a") return NameToVersion("26.1.1");
// 其余版本
static int ParseSegment(string[] segments, int index) {
if (index >= segments.Length) return 0;
// 只读取该段开头连续的数字,忽略其后的预览版标记。
return int.TryParse(segments[index].RegexSeek(@"^\d+"), out int value) ? value : 0;
}
string[] segments = name.Split([' ', '_', '-', '.'], StringSplitOptions.None);
if (name.StartsWithF("1.")) {
return new Version(ParseSegment(segments, 1), 0, ParseSegment(segments, 2));
} else if (name.RegexCheck(@"^[2-9][0-9]\.")) {
return new Version(ParseSegment(segments, 0), ParseSegment(segments, 1), ParseSegment(segments, 2));
} else {
return new Version(9999, 0, 0);
}
}
}
+423
View File
@@ -0,0 +1,423 @@
{
"f3a3c9d792bdb48595405d52c02cc7baf1db4ecb": "OptiFine_1.7.2_HD_U_E3.jar",
"657169c421ce280f80c73b05ab71ad582d2b37fd": "OptiFine_1.7.2_HD_U_E4.jar",
"99f20d449912cd860b409e4679c54dd8da43d55e": "OptiFine_1.7.2_HD_U_E7.jar",
"8253f6ed418a2a61a612cc9e69fd1fea1d11fa7d": "OptiFine_1.7.2_HD_U_E8.jar",
"379e52fc02cd75562d201b0c505269b6109b2136": "OptiFine_1.7.2_HD_U_F3.jar",
"fb04b91e47b5eaa172d2b76c843e488dba8f909b": "OptiFine_1.7.2_HD_U_F7.jar",
"b4b6c2c99f5734e4267030a47f2eaad2796eb2a7": "OptiFine_1.7.10_HD_U_D3.jar",
"a0b533bc97b24fe771f5b08536c6339d04f92bae": "OptiFine_1.7.10_HD_U_D4.jar",
"5e9327dd05a1e762c23c3c0227445d90f193922f": "OptiFine_1.7.10_HD_U_D6.jar",
"7a7d2adf21038d241dd375faa7316fb2c74d34d3": "OptiFine_1.7.10_HD_U_D7.jar",
"2345677be44dd56a0ae5b90e747a590c3cc00ff8": "OptiFine_1.7.10_HD_U_D8.jar",
"23f133173428aee712eb11f200b461e3114bde31": "OptiFine_1.7.10_HD_U_E3.jar",
"2130d7266224da6a7325051e37444f7e7c19277c": "OptiFine_1.7.10_HD_U_E7.jar",
"55c9c475ecd0f62cc1776ce52be04c5937112936": "OptiFine_1.8.0_HD_U_H5.jar",
"bb2c2997cdc66cfb3c311a3dc065752a4845c1a8": "OptiFine_1.8.0_HD_U_H6.jar",
"692b0b7e878c3416f21b4347fab4516f3cc86dfc": "OptiFine_1.8.0_HD_U_H7.jar",
"5464962b66b9b0dacb3d787e6a71d2365ae50bca": "OptiFine_1.8.0_HD_U_H8.jar",
"b7718b03111bec04036fd3352ce374eea273171c": "OptiFine_1.8.0_HD_U_I3.jar",
"0d1dcf64b4d104a71e8f65a1362a163ed81a9030": "OptiFine_1.8.0_HD_U_I7.jar",
"e5e5eb03748af0fa160b502a28359c0144e254d2": "OptiFine_1.8.8_HD_U_H5.jar",
"9294bc4562a143c35ece03a6b3d3a000109def5f": "OptiFine_1.8.8_HD_U_H6.jar",
"1c06c011904ab22952a96f73d4b575a5c265d207": "OptiFine_1.8.8_HD_U_H7.jar",
"3984e4dd222bb62cb7e28e4d3092029cc7534fd1": "OptiFine_1.8.8_HD_U_H8.jar",
"0dc33a9761ceac19ca0bbb1ea6813c0160d43aa1": "OptiFine_1.8.8_HD_U_I3.jar",
"30cbf4d61052cbfb736a78117da333df23c129a3": "OptiFine_1.8.8_HD_U_I7.jar",
"d8240cfa7387b994fdf2a64bdc8a8860857adc18": "OptiFine_1.8.9_HD_U_H5.jar",
"2636689b31810896cf80493f6fc4d8c1783048ea": "OptiFine_1.8.9_HD_U_H6.jar",
"e4544c9210bc056abffa1986a33c4e72623d4f31": "OptiFine_1.8.9_HD_U_H7.jar",
"4b73738913453bcbca969d3e77f05ae0cf5b8a7e": "OptiFine_1.8.9_HD_U_H8.jar",
"d5cd8b902077075bbf1d865cfd5b3362a6863ace": "OptiFine_1.8.9_HD_U_I3.jar",
"58bec8f8766918dcad2b8412f1fbb6f30cc9319f": "OptiFine_1.8.9_HD_U_I7.jar",
"dcab4849a24ccad5a6a3496c67bc5a88411d2a35": "OptiFine_1.8.9_HD_U_L5.jar",
"d362d58a28f5373b141b9e426e8e160638bfafcd": "OptiFine_1.8.9_HD_U_M5.jar",
"1f9dcc9735e94ebb5195c4014300ea462eb12878": "preview_OptiFine_1.8.9_HD_U_L6_pre1.jar",
"6e0b3b86e51c2647c98e277475ee7e68f32a0d0e": "preview_OptiFine_1.8.9_HD_U_M5_pre2.jar",
"7287fcd1a415ab87eca5e89457cc3507aa3fa579": "preview_OptiFine_1.8.9_HD_U_M6_pre1.jar",
"0bb792be16ec78d8b62586cc4856950befb3391b": "preview_OptiFine_1.8.9_HD_U_M6_pre2.jar",
"fd87c53197312614c34669feb7608f74a30da712": "OptiFine_1.9.0_HD_U_B1.jar",
"f99941037f3633926ffebb3eda67f296a701c019": "OptiFine_1.9.0_HD_U_B2.jar",
"34c47106cda1371ecf13e61aed4c1b53a3a53163": "OptiFine_1.9.0_HD_U_B3.jar",
"ebc7258e13344097588c73a0b48457083069e1df": "OptiFine_1.9.0_HD_U_B5.jar",
"999df92e8904f9268e549135edbec322d332ef62": "OptiFine_1.9.0_HD_U_D7.jar",
"0fe031e95f9c01481c9376d9a1df68f9e9857db8": "OptiFine_1.9.0_HD_U_D8.jar",
"f4bc2eb5e039e842eb07181ea564eba487513228": "OptiFine_1.9.0_HD_U_E3.jar",
"78870a94ec3c4db8aebc4c29d4d39bdda2bf05da": "OptiFine_1.9.0_HD_U_E7.jar",
"d3d55bb37e76d1932cb3ad9dde4b9127993f856d": "OptiFine_1.9.0_HD_U_H5.jar",
"b5faec3b84f42dd396512a3ce7c1b57c0b972b70": "OptiFine_1.9.0_HD_U_I5.jar",
"92742ecac143b1d28c215ca145bd9775a01c5085": "preview_OptiFine_1.9.0_HD_U_I5_pre1.jar",
"c87029938376017deba9108322bcb8424d64e92c": "OptiFine_1.9.2_HD_U_B1.jar",
"d3e5e1fa480e66603ca037d74966a95d25968d51": "OptiFine_1.9.2_HD_U_B2.jar",
"fbd545f921d098235a28f87cda55a0504e831954": "OptiFine_1.9.2_HD_U_B3.jar",
"1ae8e6daeeccb6ee48618e8ccdc0b6276d47944f": "OptiFine_1.9.2_HD_U_D7.jar",
"3350c829b29d170e9ac19809a3ae45cb099277b5": "OptiFine_1.9.2_HD_U_D8.jar",
"c032b2ae0b6087077efef8ae89b612e0d4ea81b7": "OptiFine_1.9.2_HD_U_E3.jar",
"3d8cbc19bcad9e9ecc98f607e10f65673a09b482": "OptiFine_1.9.4_HD_U_B4.jar",
"b6b9e66b34ddcda5294b016602fb17d741ff6f54": "OptiFine_1.9.4_HD_U_B5.jar",
"060ddd5a0b7b7ac5586de04ae53cf923ae5c7290": "OptiFine_1.9.4_HD_U_B6.jar",
"2c39611a89dc583cdb403f402c2efd2bd8c6da63": "OptiFine_1.9.4_HD_U_D7.jar",
"4b26926208de270225eb9d3af24a4ff3841238f5": "OptiFine_1.9.4_HD_U_D8.jar",
"012f82fce41aee5e6e56e551d450a07b2b361646": "OptiFine_1.9.4_HD_U_E3.jar",
"cf9c075fa71dab34cdf1c376a76bd08295c69ce6": "OptiFine_1.9.4_HD_U_E7.jar",
"b3a76b92e0e9594f6fa135f1156b9cc13712db20": "OptiFine_1.9.4_HD_U_H5.jar",
"68a206db0d425dd85f6eba33e52122b01e844c46": "OptiFine_1.9.4_HD_U_I5.jar",
"2e48d20605340cfad844a6587d3848e328fd4399": "preview_OptiFine_1.9.4_HD_U_I5_pre1.jar",
"d7a1e5f40e28fa3283b82bf3af7d3f433b91df08": "OptiFine_1.10_HD_U_B6.jar",
"717a02b21136e022cdb4d7d6ec6a4125f4ed2312": "OptiFine_1.10_HD_U_B7.jar",
"0b5d1719592a57cf414643c5b56f4bb3c23a5b94": "OptiFine_1.10_HD_U_C1.jar",
"236747126edcf0c97fc6a20fa7f6491accbf0108": "OptiFine_1.10_HD_U_D7.jar",
"c5fd78710e821f06f871994032e951065c15c40d": "OptiFine_1.10_HD_U_D8.jar",
"1bea8b0bae04ca4b25c6824a6f23a5442d5aa926": "OptiFine_1.10_HD_U_E3.jar",
"7a7195348ea583458b657573b5fcc72e4adc8308": "OptiFine_1.10_HD_U_E7.jar",
"3bcc46f718711cd6fe3db49de4cc810b75420de8": "OptiFine_1.10_HD_U_H5.jar",
"a0070afe9c1c0ced4bcfcda85359e07917d569bd": "OptiFine_1.10_HD_U_I5.jar",
"f9a36eb0246ad6ba303e670082be2c2515c50dcd": "preview_OptiFine_1.10_HD_U_I5_pre1.jar",
"3ea5dae651b6d64900562d1cde5070ebd9db2936": "OptiFine_1.10.2_HD_U_C1.jar",
"41968442e8d1e30e9a32e7a5594caca22312d9d6": "OptiFine_1.10.2_HD_U_C2.jar",
"9c8bc96d3125199e96f5dbfda685ea5dd92c253e": "OptiFine_1.10.2_HD_U_C3.jar",
"7ec4ccc213b482936baaffb9f1937f7147796893": "OptiFine_1.10.2_HD_U_D1.jar",
"3ee5aecc8e11567fb46de0b94e23cf1428f4c9fc": "OptiFine_1.10.2_HD_U_D2.jar",
"98059ed7e93d0fc058203569ccfe965b022fb18f": "OptiFine_1.10.2_HD_U_D3.jar",
"c3165d0a0207d72e18f4533e7aa5ca30e96819a9": "OptiFine_1.10.2_HD_U_D4.jar",
"347359aef422d64711b470f4cf0803cb072da0fa": "OptiFine_1.10.2_HD_U_D6.jar",
"4ddf7c45e00839031f5793451541bca002f9257f": "OptiFine_1.10.2_HD_U_D7.jar",
"5c3685e279c664a818c41ca213b2bc9bfc4340b0": "OptiFine_1.10.2_HD_U_D8.jar",
"129581af3d7d73b506b42d822cb2d3fce68c8e2e": "OptiFine_1.10.2_HD_U_E3.jar",
"f052d269109154d851cc7a8e766c78802a76e9b2": "OptiFine_1.10.2_HD_U_E7.jar",
"f285cfe6753c5d987e4cb0940da05e9513334d98": "OptiFine_1.10.2_HD_U_H5.jar",
"12fd8dd33150f9e4436f23bac08265305f949e20": "OptiFine_1.10.2_HD_U_I5.jar",
"adac69b25bb95242422a635965c7f17503c694be": "preview_OptiFine_1.10.2_HD_U_I5_pre1.jar",
"06031e4c828b66facefdfa32cd29c585909d9e45": "OptiFine_1.11_HD_U_B1.jar",
"449aef14cee17cf1c79d4bad73b69107202d3c1c": "OptiFine_1.11_HD_U_B2.jar",
"7663e1ee572c742e96a71a355f0775baf7cbc6ef": "OptiFine_1.11_HD_U_B3.jar",
"bde9d34f84a030734c7b9d09eda700c7c828eb96": "OptiFine_1.11_HD_U_B5.jar",
"579cfb75b86f3404c5f6e8f694c51b09b7c9b261": "OptiFine_1.11_HD_U_B6.jar",
"350af22b31dbc27d1b928e7144582fd361deb193": "OptiFine_1.11_HD_U_B7.jar",
"236161183cc5cd5faf84dd27824cc3eaa107ed5f": "OptiFine_1.11_HD_U_B8.jar",
"9ca32d42c7e5ad9de9665ffee9ddd41dddd33b66": "OptiFine_1.11_HD_U_C3.jar",
"2e45de9e9fb385f24c6eecb1a64537de78778105": "OptiFine_1.11_HD_U_C7.jar",
"32df58fec7148e75b599b18691f80e69b7f9cd1b": "OptiFine_1.11_HD_U_F5.jar",
"71057cb7a3d0971c3b56e1fbbc333304a72e1352": "OptiFine_1.11_HD_U_G5.jar",
"c2ee1e1019ab26bee56447f4df8a2639d2029925": "preview_OptiFine_1.11_HD_U_G5_pre1.jar",
"083a002215e2174c0d40af20d2f3844ea349a354": "OptiFine_1.11.2_HD_U_B5.jar",
"eb92ce00048faa95b184d121fa667074532fb9db": "OptiFine_1.11.2_HD_U_B6.jar",
"7449a2db44e86303d588b526dc73127c16b0e94f": "OptiFine_1.11.2_HD_U_B7.jar",
"3e1006c935fe27d83459e77d336460ac5b5c81b4": "OptiFine_1.11.2_HD_U_B8.jar",
"049e7e134e8c7364ab61810d84289394e17921de": "OptiFine_1.11.2_HD_U_B9.jar",
"4871f2edf7ded30c374aa770e9ea4adcc0b786d0": "OptiFine_1.11.2_HD_U_C1.jar",
"e7a54d9e203a1b951f8373ab6dcdedb348e536b1": "OptiFine_1.11.2_HD_U_C2.jar",
"8d7fd0facc2299f3dad9dc7c19cc13f72a780343": "OptiFine_1.11.2_HD_U_C3.jar",
"20db26eaafb1c4ebd99e1dc2fffe4a62456bf60e": "OptiFine_1.11.2_HD_U_C7.jar",
"e4efcf05086389fe18d6fe62eb8ab7512a07323c": "OptiFine_1.11.2_HD_U_F5.jar",
"65c425ab628d1748b489dee8619271402428509e": "OptiFine_1.11.2_HD_U_G5.jar",
"f253807a4689e44da030acd5c2187d2184885e87": "preview_OptiFine_1.11.2_HD_U_G5_pre1.jar",
"1fdacab6f3b0df1f46142acf9c427db2a653d680": "OptiFine_1.12_HD_U_C2.jar",
"a62a02c8437a0dccaf2ea89ab8392fd40dc92eb1": "OptiFine_1.12_HD_U_C3.jar",
"36d6f6f64048f68dd248a4d3c43a6f5e51fbb9c5": "OptiFine_1.12_HD_U_C4.jar",
"fd8e0e3ba43568e5ca0ce31c87373806d269b7e5": "OptiFine_1.12_HD_U_C5.jar",
"25ba634e5105e6d1e615dad0046905b68313cc9f": "OptiFine_1.12_HD_U_C7.jar",
"b9cd2775fc49bf009f82d3ce36235b1e702d46bb": "OptiFine_1.12_HD_U_F5.jar",
"3b8369d33c3257cb247c22b13a41b7db0924e948": "OptiFine_1.12_HD_U_G5.jar",
"aeee885ccf821c34d86537184389873d8c312ae1": "preview_OptiFine_1.12_HD_U_G5_pre1.jar",
"c754771bf6a7371a7084459505c244cbec61a7cf": "OptiFine_1.12.1_HD_U_C5.jar",
"4362a9d38542b5f4ba07782fc4d07949f4fede08": "OptiFine_1.12.1_HD_U_C6.jar",
"38785968beeb796cde55f91995c6cf8bfbc3b062": "OptiFine_1.12.1_HD_U_C7.jar",
"e6f9e6b573119c052062192695fed4ef388db9c1": "OptiFine_1.12.1_HD_U_F5.jar",
"3c9cd74810cad75de5091633a1e0c7d3d76ea09c": "OptiFine_1.12.1_HD_U_G5.jar",
"aaaaff3ae13a0cdffa2475632cf6303b9f86e0b0": "preview_OptiFine_1.12.1_HD_U_G5_pre1.jar",
"a9d21f084a485bc12fce8c9bea1660c6d4b08855": "OptiFine_1.12.2_HD_U_C5.jar",
"2159a0a754828d63446fca7b6f8fa57b710c6048": "OptiFine_1.12.2_HD_U_C6.jar",
"8ed8009fc82b43909190882927f335d5250a0e19": "OptiFine_1.12.2_HD_U_C7.jar",
"a55768a83db4ad0599f68d9bf3fa8041e2ae3852": "OptiFine_1.12.2_HD_U_C8.jar",
"e0236d21a2a164f2af1cfff8f9f325f53c9125f2": "OptiFine_1.12.2_HD_U_C9.jar",
"b91a0d82c24eef95be0af92993b14e94b842f166": "OptiFine_1.12.2_HD_U_D1.jar",
"04c28238506dc3561addb6efb87c9878d5b82ffe": "OptiFine_1.12.2_HD_U_D2.jar",
"b784000951d41f54d67685a0a275d9f61f810514": "OptiFine_1.12.2_HD_U_D3.jar",
"343f814410401ebdbb3314deb17c27cbab67bfac": "OptiFine_1.12.2_HD_U_E1.jar",
"5c87706b6d14061fed248506d8e3f95b7824ad18": "OptiFine_1.12.2_HD_U_E2.jar",
"e805d4be5c2a3343488c573145606e90bb13816d": "OptiFine_1.12.2_HD_U_E3.jar",
"0e8d45666e80829c9ab74527628dace9187fbfb5": "OptiFine_1.12.2_HD_U_F4.jar",
"84ada0f48bcfb16c37c49283627597bb74d935f6": "OptiFine_1.12.2_HD_U_F5.jar",
"ca3aea3a09ce215906c346fe190907fe0347b0c4": "OptiFine_1.12.2_HD_U_G5.jar",
"23a8be042f354ec97c99672906c3e61fdf54ca2c": "preview_OptiFine_1.12.2_HD_U_F6_pre1.jar",
"b71f993a364c7945081cc6cde9a20d29b4e43f1f": "preview_OptiFine_1.12.2_HD_U_G5_pre1.jar",
"ab2118b1ed23444d6decf081e0295e4c679af6c2": "preview_OptiFine_1.12.2_HD_U_G6_pre1.jar",
"25498de48dd9b7ed2ba5b83464950ad132e61c1f": "preview_OptiFine_1.14_HD_U_F1_pre6.jar",
"396ce7f0e22aae3543134baabd61ad547334c0aa": "OptiFine_1.14.2_HD_U_F1.jar",
"c8c6da3e2ae32d020d9001d4489aad702680f50f": "preview_OptiFine_1.14.2_HD_U_F1_pre15.jar",
"52420ef34128a8b15d7c8f5ed8f7ebc94b708efd": "OptiFine_1.14.3_HD_U_F1.jar",
"887d648f26f7ba17ea34c0c2dcff4096f7a226b1": "OptiFine_1.14.3_HD_U_F2.jar",
"ad8a8f60931f48e2873238d1460268bb4111f6c9": "OptiFine_1.14.4_HD_U_F2.jar",
"c9710081fb7012ddb56838ac54653ac2258a2404": "OptiFine_1.14.4_HD_U_F3.jar",
"c09f4d1ad66df8e3039334023422f1bc8cc211b8": "OptiFine_1.14.4_HD_U_F4.jar",
"3408258990309de773cc5a8ab71cb4137718a82b": "OptiFine_1.14.4_HD_U_F5.jar",
"117c01a5431244a8b6a3ac393374dac9624f9be6": "OptiFine_1.14.4_HD_U_G5.jar",
"ac602fe6a39d2fb4cadfe08e87af0b0daa2d8ac9": "preview_OptiFine_1.14.4_HD_U_G5_pre1.jar",
"630a32be9b711b5a479507159d5d2598189fed33": "OptiFine_1.15.2_HD_U_G6.jar",
"8339858b33056ec8975f6c0c9e1694a11f0d209a": "preview_OptiFine_1.15.2_HD_U_G1_pre1.jar",
"6cdf96d972b5f70c4575ff875a031c4931985686": "preview_OptiFine_1.15.2_HD_U_G1_pre10.jar",
"489d2284f2d50bd7c7cf4c216a766503d37e1f3c": "preview_OptiFine_1.15.2_HD_U_G1_pre12.jar",
"c66d412c4cb46c6f2c76c224dc45a62d30ee8b43": "preview_OptiFine_1.15.2_HD_U_G1_pre13.jar",
"582eb1d6470cc133db563bd1c2c8bc2068215806": "preview_OptiFine_1.15.2_HD_U_G1_pre18.jar",
"64d738862683c54a22e1856b2c13379599ca79ce": "preview_OptiFine_1.15.2_HD_U_G1_pre19.jar",
"3da9d466c847c50a73dc634dabc6a57f3b24e2f7": "preview_OptiFine_1.15.2_HD_U_G1_pre2.jar",
"e6583246a85dba26a478dd4925c298bb5b3f277a": "preview_OptiFine_1.15.2_HD_U_G1_pre20.jar",
"84c5d50e26aaa02733fa15a8c13ab845fbdea97f": "preview_OptiFine_1.15.2_HD_U_G1_pre21.jar",
"c55e449386eeca34df5b428d85927242fc841438": "preview_OptiFine_1.15.2_HD_U_G1_pre22.jar",
"459e17c77059671ef5daa9ab794400e8ae25095d": "preview_OptiFine_1.15.2_HD_U_G1_pre23.jar",
"2c6e4f61b1ceea92b76e0be40bc672e5b6d58efe": "preview_OptiFine_1.15.2_HD_U_G1_pre24.jar",
"4ea53e3d202239c5ffa2affa809e36998194f1bc": "preview_OptiFine_1.15.2_HD_U_G1_pre26.jar",
"29861ba50021b08200ef33f77c776f9f5b7135f7": "preview_OptiFine_1.15.2_HD_U_G1_pre27.jar",
"454dc0e03dab9d30a2ccce5823b7a0b309f5c859": "preview_OptiFine_1.15.2_HD_U_G1_pre28.jar",
"3c3623d75dc47722149edfb93fdca121c32e8987": "preview_OptiFine_1.15.2_HD_U_G1_pre29.jar",
"942e23fa0169c693fb1b645fae4db9217c6b5916": "preview_OptiFine_1.15.2_HD_U_G1_pre3.jar",
"bdbd92e5252c0afc0f22d6d5840f96762e5d01ea": "preview_OptiFine_1.15.2_HD_U_G1_pre30.jar",
"5daa9461c5762bdd647665f49d2e0ef0eb348910": "preview_OptiFine_1.15.2_HD_U_G1_pre4.jar",
"0029ab50050d19f4009f0a7d708982dde736f022": "preview_OptiFine_1.15.2_HD_U_G1_pre5.jar",
"646b279927cab6036e8f295efd5bda268acd85cf": "preview_OptiFine_1.15.2_HD_U_G1_pre7.jar",
"f11e79a7bc91a5fd985cb756445fc88e8c2dd31b": "preview_OptiFine_1.15.2_HD_U_G1_pre9.jar",
"1e9869b63a89a8ab81a58e1473dd2c2291fbd943": "preview_OptiFine_1.15.2_HD_U_G6_pre5.jar",
"87533b8ac0dfeb11241041f197f8f95c0ffa8c08": "OptiFine_1.16.1_HD_U_G2.jar",
"3e610c9bd89ae2a54cc06de6dfb1b00cd45e9895": "preview_OptiFine_1.16.1_HD_U_G2_pre1.jar",
"df817e1ab0e577be1dfb6c20a9b628d3d725c780": "preview_OptiFine_1.16.1_HD_U_G2_pre10.jar",
"dbca331e7abbe64d6aa360e6a93beec2935d24e3": "preview_OptiFine_1.16.1_HD_U_G2_pre11.jar",
"6fef880338cabe32443557762ca43fb36fe01f7c": "preview_OptiFine_1.16.1_HD_U_G2_pre12.jar",
"ea7bea6511f3b20aa672544d28dbc6d1a2ee8b41": "preview_OptiFine_1.16.1_HD_U_G2_pre13.jar",
"efdc788f59e1c1bcde3b64430322d8990ce7c7d6": "preview_OptiFine_1.16.1_HD_U_G2_pre14.jar",
"388100110b4dc8ef28eaa2f3a4ae2f33a8beb5ae": "preview_OptiFine_1.16.1_HD_U_G2_pre15.jar",
"63f5f097ce2e678d69767981b09b133ab8eac4f5": "preview_OptiFine_1.16.1_HD_U_G2_pre16.jar",
"c56031c3f6e5dcad536115e9f6a1fb5710b72391": "preview_OptiFine_1.16.1_HD_U_G2_pre2.jar",
"da269cdb109b6441ebed179a8d9f974590006cbb": "preview_OptiFine_1.16.1_HD_U_G2_pre3.jar",
"ad9b5d4e0bdd5697d4f6a6b9c30a64977bb8834e": "preview_OptiFine_1.16.1_HD_U_G2_pre4.jar",
"f6d5ea6cedff1f80bed298f9b456de2448b1f268": "preview_OptiFine_1.16.1_HD_U_G2_pre5.jar",
"f57a4269d404d4025de4d864b88510504e785e19": "preview_OptiFine_1.16.1_HD_U_G2_pre6.jar",
"28b46592e2f510a246a8bf62a2b12e808094167c": "preview_OptiFine_1.16.1_HD_U_G2_pre7.jar",
"26207daec6acc66250116e4f73295c9a031b491a": "preview_OptiFine_1.16.1_HD_U_G2_pre8.jar",
"6902e902866b765ff8e31716980c2067d491e004": "preview_OptiFine_1.16.1_HD_U_G2_pre9.jar",
"dde4274c696063496323731ad7b58e0f6d7265af": "OptiFine_1.16.2_HD_U_G3.jar",
"5b5627a814e8c95aa7ac27fa4cf2cc0b8bd4519b": "OptiFine_1.16.2_HD_U_G5.jar",
"5dafc10061020c519ba4abe4982a7dc80eae30f6": "preview_OptiFine_1.16.2_HD_U_G3_pre1.jar",
"0c0d9828984da34f9d5967027c244bb2c83a4396": "preview_OptiFine_1.16.2_HD_U_G3_pre2.jar",
"7c791e4348808132b8c7b59e6350ae2ee25a1918": "preview_OptiFine_1.16.2_HD_U_G3_pre3.jar",
"9a30a2cbc81095d38b4dc17e7d30a4b84e10e235": "preview_OptiFine_1.16.2_HD_U_G3_pre4.jar",
"ec1a4ce97e29c9ee2ac259d98e35bc386035a66d": "preview_OptiFine_1.16.2_HD_U_G3_pre5.jar",
"e03b0cdfbbcf01b1371b3e1090dee64e60a759d8": "preview_OptiFine_1.16.2_HD_U_G3_pre6.jar",
"40ae5fbb4e3724d6e5525d2688445c09ecfe5d39": "preview_OptiFine_1.16.2_HD_U_G3_pre7.jar",
"dc83d55f74722e1e6f503817e00e5ba2e6cb4abb": "OptiFine_1.16.3_HD_U_G3.jar",
"89d9472ee507946caa227e226524e5260ed944db": "OptiFine_1.16.3_HD_U_G4.jar",
"6a8f57ca84628da47b011e608ff449cde7ec13ed": "OptiFine_1.16.3_HD_U_G5.jar",
"f53bad69adaac9e523577dcb1c65b158802f3dee": "preview_OptiFine_1.16.3_HD_U_G3_pre2.jar",
"5b970eeae2ba07316b3e3a53da1d71de87c9f9f2": "preview_OptiFine_1.16.3_HD_U_G4_pre1.jar",
"599d9de6b656077b03fbebc9478924c548a6e9e0": "OptiFine_1.16.4_HD_U_G5.jar",
"7cce72db1f77c6178b549fa430e241eadb318f69": "OptiFine_1.16.4_HD_U_G6.jar",
"f49c042fa099907eafcbe22d7035d84f8b5715f1": "OptiFine_1.16.4_HD_U_G7.jar",
"f0b1eb59acdae57e10bb928046ee26012ef220ad": "preview_OptiFine_1.16.4_HD_U_G6_pre12.jar",
"ded7894eebda787b7bd5013aa8c66577f2ba03fc": "preview_OptiFine_1.16.4_HD_U_G7_pre1.jar",
"57cfcec3dd99b9f0950d0d6305bc95ecd8563212": "OptiFine_1.16.5_HD_U_G6.jar",
"706fb33784045d69e7ac5730426d813a39511dcd": "OptiFine_1.16.5_HD_U_G7.jar",
"137363842c5bfdebb72356a4079faeef3157eb3c": "OptiFine_1.16.5_HD_U_G8.jar",
"ad0f1577ce02a703185e46ee9e24fdeb65479fd5": "preview_OptiFine_1.16.5_HD_U_G7_pre10.jar",
"11770876367980da546c68bc95c4f794582907f2": "preview_OptiFine_1.16.5_HD_U_G7_pre11.jar",
"c5b32d2bad3bd54c31ddb29869a55fe44409cfe2": "preview_OptiFine_1.16.5_HD_U_G7_pre2.jar",
"9fc9cc09e72a3060d577f62f3535820056b0c2d4": "preview_OptiFine_1.16.5_HD_U_G7_pre4.jar",
"577fae32b60ac8d1b91e3fa645dc004d4a9e513e": "preview_OptiFine_1.16.5_HD_U_G7_pre5.jar",
"4bccf7f371ac83523796b4706569cc5077c46aea": "preview_OptiFine_1.16.5_HD_U_G7_pre6.jar",
"97b8d413a909354e2e0f42943cf292941dfe4c38": "preview_OptiFine_1.16.5_HD_U_G7_pre7.jar",
"9c48a804ee990e526000c7061bde9aa6f75d62a6": "preview_OptiFine_1.16.5_HD_U_G7_pre8.jar",
"9a9b69a52a4514f5d25c9f47127a822e93458fa3": "preview_OptiFine_1.16.5_HD_U_G7_pre9.jar",
"d16a450ec4205d02319480799008d8177ba05d69": "preview_OptiFine_1.16.5_HD_U_G8_pre1.jar",
"539c215aa0e8b8f696b325e89f3f9a3658ab32c8": "preview_OptiFine_1.16.5_HD_U_G8_pre10.jar",
"311ed81a27d24047b6ade33f46ac21c962897016": "preview_OptiFine_1.16.5_HD_U_G8_pre11.jar",
"4bc50f85809a299d30c3e3e73cfd87cad61cf7e6": "preview_OptiFine_1.16.5_HD_U_G8_pre12.jar",
"7de1b8e9f7cd6d7ca941d90121e50206aba80c68": "preview_OptiFine_1.16.5_HD_U_G8_pre13.jar",
"a4e021a75aab7ed1eb83ff7b45182d497aa930f2": "preview_OptiFine_1.16.5_HD_U_G8_pre14.jar",
"0ddcc4f47e10b2d671ebbf9147eb16cf8f1bb92a": "preview_OptiFine_1.16.5_HD_U_G8_pre2.jar",
"b8600be4fde6f8cc3c28173cc9bde6923956ff42": "preview_OptiFine_1.16.5_HD_U_G8_pre3.jar",
"d46b10098309929d9a6583323f059e65ba40a5e5": "preview_OptiFine_1.16.5_HD_U_G8_pre4.jar",
"e835424b36fdae3d5f46c6bf096852848127f1e7": "preview_OptiFine_1.16.5_HD_U_G8_pre5.jar",
"5a84983221331597f58e9636d41f1fb0c9e3de61": "preview_OptiFine_1.16.5_HD_U_G8_pre6.jar",
"d03ce2209d787539e5e51f80a7cbffc69a49ce9f": "preview_OptiFine_1.16.5_HD_U_G8_pre7.jar",
"1c646dc058aa32898917589858d900407883ba3f": "preview_OptiFine_1.16.5_HD_U_G8_pre8.jar",
"9bf369aa609e5612247473326dc298e6e6c9fecf": "preview_OptiFine_1.16.5_HD_U_G8_pre9.jar",
"b88b05278fbbf400cb5ec0cc26e0c5b7306e0c1e": "preview_OptiFine_1.17_HD_U_G9_pre16.jar",
"a6beac3091d17a4ba420116c75c89faeaee48539": "preview_OptiFine_1.17_HD_U_G9_pre17.jar",
"2839c5e74f64500561ee3e8605c51d9f06727c22": "preview_OptiFine_1.17_HD_U_G9_pre18.jar",
"455243ff55177189fff140ba7f4c4777cc977ef6": "preview_OptiFine_1.17_HD_U_G9_pre19.jar",
"bcc390e34bd529a86e6ae0e25d50417f46ba482f": "preview_OptiFine_1.17_HD_U_G9_pre20.jar",
"d30c23d745df54fd6ea2374e9c0b4392f0a78199": "preview_OptiFine_1.17_HD_U_G9_pre21.jar",
"075e7f8473b6785c3c6bbeeb41a3728f6ff3c3ba": "preview_OptiFine_1.17_HD_U_G9_pre22.jar",
"2ffaee36c717ae918cc93661df739740636900c9": "preview_OptiFine_1.17_HD_U_G9_pre23.jar",
"4385493e7ce4c6f4a3945f1d96246bce0b151c7e": "preview_OptiFine_1.17_HD_U_G9_pre24.jar",
"bcc33e67f134861eb30b1f0b9a829183c8afdebf": "preview_OptiFine_1.17_HD_U_G9_pre25.jar",
"6a8e34605cc828091027d9f011b804d916808af4": "preview_OptiFine_1.17_HD_U_G9_pre26.jar",
"4714bdceaefc15e0af9d5c72de8c05cf3051d760": "OptiFine_1.17.1_HD_U_G9.jar",
"cc64084e73d6423baf7ccbdd9f3760dd56f2fce5": "OptiFine_1.17.1_HD_U_H1.jar",
"1ee73bd882385a7abb9896b9469586741a6850a8": "preview_OptiFine_1.17.1_HD_U_G9_pre26.jar",
"537c1fbee9ebe25b49a8e6fb93db162d217f1c83": "preview_OptiFine_1.17.1_HD_U_G9_pre27.jar",
"68e1124f4b0376773d8d581da78f9bb6fc76486e": "preview_OptiFine_1.17.1_HD_U_G9_pre28.jar",
"3f80a562b39439831491c650c65ecb762e70d5f7": "preview_OptiFine_1.17.1_HD_U_G9_pre29.jar",
"9751c69568a61ce5b2a602b4dcf77c1cea57c709": "preview_OptiFine_1.17.1_HD_U_G9_pre30.jar",
"162fa1b4a0d5b89dad6030b5fd5903a3f24d39c7": "preview_OptiFine_1.17.1_HD_U_G9_pre31.jar",
"c4f3a33d892948927cecfaa60b284217325557c5": "preview_OptiFine_1.17.1_HD_U_G9_pre32.jar",
"12dbbc8ccd8dc02806768fb3fbe4a2d8c7c11301": "preview_OptiFine_1.17.1_HD_U_G9_pre33.jar",
"f74d1dfcb24c43e633076c63dd4492b29fca14b3": "preview_OptiFine_1.17.1_HD_U_G9_pre34.jar",
"42e55681fb106475ef13d5c5ef7113fb631392d4": "preview_OptiFine_1.17.1_HD_U_G9_pre35.jar",
"245ddb68349f5f81d067f4d9402d62db3d439d50": "preview_OptiFine_1.17.1_HD_U_H1_pre1.jar",
"0ac033f5ba4089311af55172281e73df143b9ac4": "preview_OptiFine_1.17.1_HD_U_H1_pre2.jar",
"68a3727bcdb0eac0986ff4e883d93fef406eb1c3": "preview_OptiFine_1.17.1_HD_U_H1_pre3.jar",
"a6534877558f0438bda604dc43093d61c6de88ae": "preview_OptiFine_1.17.1_HD_U_H1_pre4.jar",
"3e18db17b8695feb0a188ecdcb81b561d9d7030c": "preview_OptiFine_1.17.1_HD_U_H1_pre5.jar",
"c2e0c4391a671cd06931f7b6e965085c04c58762": "preview_OptiFine_1.17.1_HD_U_H1_pre6.jar",
"1ffb7325df5bf01b1d99bbbed8006a477b861394": "preview_OptiFine_1.17.1_HD_U_H1_pre7.jar",
"f2fa727e67b4795ddea3cc37cc5ced42e9c3b0c5": "preview_OptiFine_1.17.1_HD_U_H1_pre8.jar",
"aee167aff9937a068d514c85e3f8c766db99ed43": "preview_OptiFine_1.17.1_HD_U_H1_pre9.jar",
"59709e09632c80ab80ffcc4b5bf380d073bf467f": "preview_OptiFine_1.17.1_HD_U_H2_pre1.jar",
"52629bc5319ec203edd42de1776cb8d67a0e6b1b": "OptiFine_1.18_HD_U_H3.jar",
"e5077b4be03956118613162b5dbcf90406b27747": "preview_OptiFine_1.18_HD_U_H3_pre7.jar",
"326c160481cc849bccb821939992c101a8fe163b": "OptiFine_1.18.1_HD_U_H4.jar",
"1679654fdf49fd1216b090d51d3d8ec906094619": "OptiFine_1.18.1_HD_U_H5.jar",
"c86534ee821a78efc741c9cb407eec76625e1bab": "OptiFine_1.18.1_HD_U_H6.jar",
"34c98382131efa1394cff06528238dc44d255794": "preview_OptiFine_1.18.1_HD_U_H4_pre1.jar",
"d6cd0a2d0243ecad107220bff639eb46277762ea": "preview_OptiFine_1.18.1_HD_U_H5_pre1.jar",
"e24d766864f9238b94be5b3ad65608d7eb521fad": "preview_OptiFine_1.18.1_HD_U_H5_pre2.jar",
"bc1b87a6379acf5a5dca2a56196080ace49e4864": "preview_OptiFine_1.18.1_HD_U_H5_pre3.jar",
"555a6b8e420bd63a04d9f338d61f3333b4ccf5d9": "preview_OptiFine_1.18.1_HD_U_H5_pre4.jar",
"1d02694393f10992cbbd11a802568b0890b4dc81": "preview_OptiFine_1.18.1_HD_U_H5_pre5.jar",
"e3a22adc6931dcd7513f26afa1adecd62a35641b": "preview_OptiFine_1.18.1_HD_U_H5_pre6.jar",
"445a8cb8a1d565d8a1fabc0ba984c5936edbf561": "OptiFine_1.18.2_HD_U_H6.jar",
"8d83d4d5e325887e000a462029d2bf7a6e77eebc": "OptiFine_1.18.2_HD_U_H7.jar",
"2a461188167c1da6b1cfd520d85a656f45c74d70": "OptiFine_1.18.2_HD_U_H9.jar",
"e591777f272df533b62260a6dbe0e6cde62066f6": "preview_OptiFine_1.18.2_HD_U_H6_pre1.jar",
"f7c727c99e7f58ef9b86cc96fb894f628391016c": "preview_OptiFine_1.18.2_HD_U_H7_pre1.jar",
"cb51529f94e0eff5b9da92c8f70b1d498662a115": "preview_OptiFine_1.18.2_HD_U_H7_pre2.jar",
"ceea3c6612d2ec36c6ef1ca4a9355a141df9089f": "preview_OptiFine_1.18.2_HD_U_H7_pre3.jar",
"596c3a399889c3e2ef1d8fee994e8cf5a91e457e": "preview_OptiFine_1.18.2_HD_U_H8_pre1.jar",
"85f545aeb9d232ae970b0efa24e833f9c5d0fe33": "preview_OptiFine_1.18.2_HD_U_H9_pre1.jar",
"b9bbabb8f31f2b04ca1a1a8645df1be701f705bd": "preview_OptiFine_1.18.2_HD_U_H9_pre2.jar",
"c2bf213b4f60b6a29e402e3eaf219a4c85a5d1e6": "preview_OptiFine_1.18.2_HD_U_H9_pre3.jar",
"64c8c35313d25a64a8a8677cb851a2c83556ddc8": "OptiFine_1.19_HD_U_H8.jar",
"c668eaf9b6ce4e64f26a29fb27e830229ab4bef3": "OptiFine_1.19_HD_U_H9.jar",
"cda9130e0d36f0d6e06950ab393a8929b502a440": "preview_OptiFine_1.19_HD_U_H8_pre1.jar",
"cd1ae81f544672df776dc4426269771d2e82d0b2": "preview_OptiFine_1.19_HD_U_H8_pre10.jar",
"cb3ad2a9b4c23675ca569058f55dd174eed67a38": "preview_OptiFine_1.19_HD_U_H8_pre2.jar",
"2f7139ea0888ed18006e3a39b7475e88a3fd545d": "preview_OptiFine_1.19_HD_U_H8_pre3.jar",
"85cd70bb714e65935abf351811889a47a648bb96": "preview_OptiFine_1.19_HD_U_H8_pre4.jar",
"c65cbae4c1cfb5b9a5a573e4b83424df2d81b172": "preview_OptiFine_1.19_HD_U_H8_pre5.jar",
"10cd57890ebf449f5c258cff1c60d8dcefcc6cf6": "preview_OptiFine_1.19_HD_U_H8_pre6.jar",
"eb03e60c2fa9904a47eec1ef4a2c8804ee6cc0b8": "preview_OptiFine_1.19_HD_U_H8_pre7.jar",
"40d0b514168161fdefc91594f0d3f774f831d7de": "preview_OptiFine_1.19_HD_U_H8_pre8.jar",
"8e5bedd1c1a3641caad0743c4629db51c11f8feb": "preview_OptiFine_1.19_HD_U_H9_pre1.jar",
"7745216a810f0d617ed17a520fda44e12456492f": "preview_OptiFine_1.19_HD_U_H9_pre2.jar",
"6d6ed920eb99836e76549a7803af85881fcdee34": "preview_OptiFine_1.19_HD_U_H9_pre3.jar",
"7604e63ca74876b4e8ea9c3a36dbe80111c776b1": "preview_OptiFine_1.19_HD_U_H9_pre4.jar",
"b42dcce86c88ad4bcfa52a9d297afa800477aa78": "OptiFine_1.19.1_HD_U_H9.jar",
"648427a02940b3c53b3678cd5df1ff6997bd1e6c": "preview_OptiFine_1.19.1_HD_U_H9_pre1.jar",
"7cc221a6df6831b62c3b676b5f958e82a4487fd8": "OptiFine_1.19.2_HD_U_H9.jar",
"f304c56c7ab9119ce4269f2b019cd4668f48376d": "OptiFine_1.19.2_HD_U_I1.jar",
"708c1ecc148a0bd7d341e8f5e005e670903650f7": "OptiFine_1.19.2_HD_U_I2.jar",
"e0e924990bd12cc499fc89439f0b2c50b6e147fa": "preview_OptiFine_1.19.2_HD_U_I1_pre1.jar",
"465b229481c0a285195a168bf6581549a5cd7373": "preview_OptiFine_1.19.2_HD_U_I1_pre2.jar",
"af4398d0ce0628bcc0204e61c157e3c8951b9ad5": "preview_OptiFine_1.19.2_HD_U_I1_pre3.jar",
"35ef18f953bbea9a4cba6e14d0731d56f888114f": "preview_OptiFine_1.19.2_HD_U_I1_pre4.jar",
"b24d8725a8d06e1c190d1d2b3bc51c899f74b36d": "OptiFine_1.19.3_HD_U_I3.jar",
"f5ce615d9886e377079fd13cc4700a51d5a9c7b6": "preview_OptiFine_1.19.3_HD_U_I2_pre1.jar",
"10e6d97ea2c97cf0575a7c587fb11e1ddb73aa71": "preview_OptiFine_1.19.3_HD_U_I2_pre2.jar",
"89718cd608225d7c23f7e180d011daa4a47ce6bd": "preview_OptiFine_1.19.3_HD_U_I2_pre3.jar",
"204baf0fd59432054aebd2f83c83fe15fdd3e611": "preview_OptiFine_1.19.3_HD_U_I2_pre4.jar",
"90a52b3adc94a1b9a70f03f4c4ea14dfdf3424d8": "preview_OptiFine_1.19.3_HD_U_I2_pre5.jar",
"050d787936cc239ea8db0aa250d2ed72a44fa71d": "OptiFine_1.19.4_HD_U_I3.jar",
"253e9d29c6826c134fbdeea074d1d91c3410225c": "OptiFine_1.19.4_HD_U_I4.jar",
"068b998d6c5db5b3bb7ad6a0a7d0c6aefc4dc191": "preview_OptiFine_1.19.4_HD_U_I3_pre3.jar",
"a8b9156811c4745dbd612ebbb1df6f93d2fa734a": "preview_OptiFine_1.19.4_HD_U_I3_pre4.jar",
"6f2793e6236a27c06a74e8a1876ea6b29be038a7": "preview_OptiFine_1.19.4_HD_U_I3_pre5.jar",
"9ad82ef9f32fb5a0e7e7e0c7e8906c9f319a9705": "preview_OptiFine_1.19.4_HD_U_I3_pre6.jar",
"cf02ee8b5cf6922fbc28d6ffc8183949fff55d49": "preview_OptiFine_1.19.4_HD_U_I3_pre7.jar",
"702e0e9987209f66a5b4f825e7ac658fd49dd4b1": "preview_OptiFine_1.19.4_HD_U_I3_pre8.jar",
"04c8eaeadf24c42929500812a8f3f5fe634f0d1f": "preview_OptiFine_1.19.4_HD_U_I3_pre9.jar",
"968784e051c2c44f10a4ab448c979572977969c0": "preview_OptiFine_1.19.4_HD_U_I4_pre1.jar",
"abe4b1dbda7d95eb47953ef8aa11ed8ed1d8e238": "preview_OptiFine_1.20_HD_U_I5_pre3.jar",
"6612ce97d846018126f91cdbae23f0eff6bd2bab": "preview_OptiFine_1.20_HD_U_I5_pre4.jar",
"f37d62b6a071f6cd3d6bdfa378b56f7f5b9ec7c6": "preview_OptiFine_1.20_HD_U_I5_pre5.jar",
"c44bd276edd10fa910d929a4e7f6b8c7ffe6c23f": "OptiFine_1.20.1_HD_U_I5.jar",
"227914ab1037762eae44a2e66a568b87d7a02365": "OptiFine_1.20.1_HD_U_I6.jar",
"7484cf60bd8e019c40fa448daa294bd31f52962b": "preview_OptiFine_1.20.1_HD_U_I5_pre4.jar",
"953b1b4f15f56329bc4ade851846ef004ebbd5d0": "preview_OptiFine_1.20.1_HD_U_I5_pre5.jar",
"ebbdb3076f8291d7f8e10cbec3b1d3c49a0a701d": "preview_OptiFine_1.20.1_HD_U_I5_pre6.jar",
"b831d59eeb63122893f87a62ec3e5b3f1cc5586a": "preview_OptiFine_1.20.1_HD_U_I5_pre7.jar",
"dc93d991b07b2c5c6d2cd50a553599900c256264": "preview_OptiFine_1.20.1_HD_U_I5_pre8.jar",
"040a73427541170bccf162c04f4129b3909b8da1": "preview_OptiFine_1.20.1_HD_U_I5_pre9.jar",
"9f5fd75ede7770ad35383443b0a203f026cef5db": "preview_OptiFine_1.20.1_HD_U_I6_pre1.jar",
"85d5566b440036b926c5dd306108e48e7fb9b6e8": "preview_OptiFine_1.20.1_HD_U_I6_pre2.jar",
"d09c110358117336472414720bf961a581690dc5": "preview_OptiFine_1.20.1_HD_U_I6_pre3.jar",
"3365577f5161f9a1bbfefb15675bb5ece27600e4": "preview_OptiFine_1.20.1_HD_U_I6_pre4.jar",
"f317c701ae8c533fc34809dbce914bcced4e9a03": "preview_OptiFine_1.20.1_HD_U_I6_pre5.jar",
"445b32b14a2c2802d5a0832a708712d2fabafbc2": "preview_OptiFine_1.20.1_HD_U_I6_pre6.jar",
"9ea6b1389e88b81797f54b9d8d1e870d3fe8532c": "preview_OptiFine_1.20.2_HD_U_I7_pre1.jar",
"f2475e8d5746477d1d2972cd5aae62a5d9c04264": "OptiFine_1.20.4_HD_U_I7.jar",
"fec28a2fa152744bc2b47b3c5db3217974aa57ee": "preview_OptiFine_1.20.4_HD_U_I7_pre2.jar",
"4068365a57f4b154f4ebbc670894dc9038cf3819": "preview_OptiFine_1.20.4_HD_U_I7_pre3.jar",
"2481e0bed7610bfdfe26dea5a6bd8f01537c4ea8": "preview_OptiFine_1.20.4_HD_U_I7_pre4.jar",
"150990ba068cfb55bbf9e525c5ab84db0b8903e1": "preview_OptiFine_1.20.4_HD_U_I7_pre5.jar",
"fcf0f0f278d1901e4341d5ec8022bede03572430": "preview_OptiFine_1.20.4_HD_U_I7_pre6.jar",
"1c3395efa928758505b808291162a2514523b399": "preview_OptiFine_1.20.4_HD_U_I7_pre7.jar",
"4bcfec24e5d917ee96e0de6ef095bfc08782217b": "preview_OptiFine_1.20.4_HD_U_I8_pre1.jar",
"c931df21309800f1d1ece59b318e6b3e39c98176": "preview_OptiFine_1.20.4_HD_U_I8_pre2.jar",
"83f5ad147536405d86bf4ee47a7f7970e1fa8329": "preview_OptiFine_1.20.4_HD_U_I8_pre3.jar",
"7c6669319db18c9622d896da2a061479be88acf5": "preview_OptiFine_1.20.4_HD_U_I8_pre4.jar",
"bda7bbdd5e2d8c73148372e8c3b34d7c2485a4e8": "preview_OptiFine_1.20.6_HD_U_J1_pre18.jar",
"24a20f949a719480523669669a4e5e7339df6186": "OptiFine_1.21.1_HD_U_J1.jar",
"6bc74af8f733864539404a3f087aeb6f0cadc6b1": "preview_OptiFine_1.21.1_HD_U_J1_pre10.jar",
"4813a6cde6b0f5b20223d486ba7adf0816236a77": "preview_OptiFine_1.21.1_HD_U_J1_pre11.jar",
"c99ea98d8537f99b7470f1bcfa81c7ce405fd97e": "preview_OptiFine_1.21.1_HD_U_J1_pre12.jar",
"f2f55e6d61a4b90bdaa31eb520f5af78f8a1833b": "preview_OptiFine_1.21.1_HD_U_J1_pre13.jar",
"53a15b75ea9d20b644b0d3e9c7280ba172ade053": "preview_OptiFine_1.21.1_HD_U_J1_pre14.jar",
"da5575e1138fc2a4c28779cfc5e7d260253ccbdb": "preview_OptiFine_1.21.1_HD_U_J1_pre15.jar",
"0930876341beadf7014f57e9ebdd44be445dacbe": "OptiFine_1.21.3_HD_U_J2.jar",
"350f63ec3e59d21227f803d4078883884e482660": "preview_OptiFine_1.21.3_HD_U_J2_pre10.jar",
"2c48a4210acd25082c220f9601f17d1cc7769098": "preview_OptiFine_1.21.3_HD_U_J2_pre11.jar",
"8f0a097b1f7c9d065396bac9230eab62412a8b54": "preview_OptiFine_1.21.3_HD_U_J2_pre12.jar",
"f97df5638fcda7f4380d9284b329569d4e1554aa": "preview_OptiFine_1.21.3_HD_U_J2_pre8.jar",
"77df50552deea51687592b60c6d24561ee11aa74": "preview_OptiFine_1.21.3_HD_U_J2_pre9.jar",
"bcc6a7a140320566a985b1499a59fcb085255d14": "OptiFine_1.21.4_HD_U_J3.jar",
"e7bff4575b7311e11d7111506e855b4ad910481f": "preview_OptiFine_1.21.4_HD_U_J3_pre10.jar",
"52b2b6defdb8d1c189afb42125079f1b76ff72e2": "preview_OptiFine_1.21.4_HD_U_J3_pre11.jar",
"708f70aa5f70919a40d02ba75fc30d2d60c8e7cd": "preview_OptiFine_1.21.4_HD_U_J3_pre12.jar",
"86b796325d03d4571809a155c54cf6cba7c7482b": "preview_OptiFine_1.21.4_HD_U_J3_pre13.jar",
"618719a6a69eb674ea50c82cffe3d4e3c2f67abf": "preview_OptiFine_1.21.4_HD_U_J3_pre14.jar",
"98dd84b0581fcf7a27aa96238acb615b5b719825": "preview_OptiFine_1.21.4_HD_U_J3_pre15.jar",
"5c8573ae921082a069a9e7269500178f0c580c97": "preview_OptiFine_1.21.4_HD_U_J3_pre4.jar",
"17d51e1bfc94dde95368ab91aa0bb56e89c706bf": "preview_OptiFine_1.21.4_HD_U_J3_pre5.jar",
"1a4311606ac94d682800714793ea887c3276b18d": "preview_OptiFine_1.21.4_HD_U_J3_pre6.jar",
"12bf1a1b5607b5c62405a8c6ab4e1bd02bef2c8d": "preview_OptiFine_1.21.4_HD_U_J3_pre7.jar",
"f1f56403dc575fd975a000bfb133060285cacd6f": "preview_OptiFine_1.21.4_HD_U_J3_pre8.jar",
"9584de0e8204f5c855b60741d58af967dfc599a8": "preview_OptiFine_1.21.4_HD_U_J3_pre9.jar",
"0b97631e166ace9346744de33b7772df7bf5fbeb": "preview_OptiFine_1.21.4_HD_U_J4_pre1.jar",
"a776ec6c072529d4b731e0e6929a8aee56d04e76": "preview_OptiFine_1.21.4_HD_U_J4_pre2.jar",
"2a3fdfcbfc9c572b22c7d15c6129b8ea58ea1712": "preview_OptiFine_1.21.8_HD_U_J6_pre13.jar",
"ba7ca56c9b33d2fc7f7d22e20ce2765e4d257299": "preview_OptiFine_1.21.8_HD_U_J6_pre14.jar",
"097d3acd315b0682a4565ab9cbb4cdc1cc17d81f": "preview_OptiFine_1.21.8_HD_U_J6_pre15.jar",
"d7effca0de34c9442f06776848679a684f1f166a": "preview_OptiFine_1.21.8_HD_U_J6_pre16.jar",
"696ed52cdfd0ac2eafab8e98575d146b400de530": "preview_OptiFine_1.21.10_HD_U_J7_pre10.jar",
"be7bc81f0c9ce15316f76a059b0a690f62c03a5d": "preview_OptiFine_1.21.10_HD_U_J7_pre11.jar",
"731e698abe94bbd90cda628f80b4ce6ac5af8188": "preview_OptiFine_1.21.10_HD_U_J7_pre6.jar",
"63f189a4b7ff5b53e5bc56391f5274eab8555def": "preview_OptiFine_1.21.10_HD_U_J7_pre7.jar",
"7ccfc0085ccdef3f4ec21918a059ddd05171711a": "preview_OptiFine_1.21.10_HD_U_J7_pre8.jar",
"aad53ad3346944a12ed088ffbba98cf9eccc474c": "preview_OptiFine_1.21.10_HD_U_J7_pre9.jar",
"2800efeb56565f23494d372e7edac29333113e11": "OptiFine_1.21.11_HD_U_J8.jar",
"cee35b83c75da33dc07ffbb344386cae5cdc5c5b": "OptiFine_1.21.11_HD_U_J9.jar",
"687046282c370e690ec4b4aa6d90a6b42ada3ee1": "preview_OptiFine_1.21.11_HD_U_J8_pre10.jar",
"c09b05637610072bc33023d1c901e2726b7d16ce": "preview_OptiFine_1.21.11_HD_U_J8_pre11.jar",
"88682b40f08f462c4daae8e6b25ea99b1ed5cd14": "preview_OptiFine_1.21.11_HD_U_J8_pre12.jar",
"1c69de725c249d487df676470c71a7c05ece943c": "preview_OptiFine_1.21.11_HD_U_J8_pre3.jar",
"921b68e014539d75b77a47ca2117fc9e09ace4ed": "preview_OptiFine_1.21.11_HD_U_J8_pre4.jar",
"2fee496a35eeebf39fbb43017b12d752f5c37dee": "preview_OptiFine_1.21.11_HD_U_J8_pre5.jar",
"e5b5da810d43c02dccedfd99f83ae8ad63e49966": "preview_OptiFine_1.21.11_HD_U_J8_pre6.jar",
"8309638bab4d99258354b4d13c3cd82ff7c9645b": "preview_OptiFine_1.21.11_HD_U_J8_pre7.jar",
"b3d208ec7b5015842fcc77b3399b04b24e0b54cb": "preview_OptiFine_1.21.11_HD_U_J8_pre8.jar",
"c4ac7401bb5f668e4e92121501d1537cdbe7343c": "preview_OptiFine_1.21.11_HD_U_J8_pre9.jar",
"fd96031b5fc4c41ad71581eea53eb122022fff5b": "preview_OptiFine_1.21.11_HD_U_J9_pre1.jar",
"a159a217aba02cd7897394a51bd346c3b658b9f9": "preview_OptiFine_1.21.11_HD_U_J9_pre2.jar",
"2a784437076b539ea1a3c1a37ec98ff21bc3952c": "preview_OptiFine_1.21.11_HD_U_J9_pre3.jar",
"ee32ce4b406e290a0e99685afd09dc44cad03ddd": "preview_OptiFine_1.21.11_HD_U_J9_pre4.jar"
}
+71
View File
@@ -0,0 +1,71 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<OutputType>Library</OutputType>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<OutDir>bin\</OutDir>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Configurations>Debug;Release;Snapshot</Configurations>
<Deterministic>true</Deterministic>
<LangVersion>13.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PolySharp" Version="1.15.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Using Include="System.Diagnostics.CodeAnalysis" />
<Using Include="System.Collections" />
<Using Include="System.Collections.Generic" />
<Using Include="System.Collections.Concurrent" />
<Using Include="System.Text" />
<Using Include="System.Text.RegularExpressions" />
<Using Include="System.Net" />
<Using Include="System.Runtime.CompilerServices" />
<Using Include="System.Runtime.InteropServices" />
<Using Include="Newtonsoft.Json" />
<Using Include="Newtonsoft.Json.Linq" />
<Using Include="MeloongCore" />
<Using Include="MeloongCore.Extensions" />
<Using Include="MeloongCore.Wpf" />
<Using Include="MeloongCore.Wpf.Extensions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MeloongCore\Shared\MeloongCore.csproj" />
<ProjectReference Include="..\MeloongCore\Wpf\MeloongCore.Wpf.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);SNAPSHOT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Snapshot|AnyCPU'">
<Optimize>True</Optimize>
</PropertyGroup>
<!-- 内嵌的资源 -->
<ItemGroup>
<EmbeddedResource Include="Launch\JavaWrapper.jar" LogicalName="Launch\JavaWrapper.jar" />
<EmbeddedResource Include="Launch\LwjglUnsafeAgent.jar" LogicalName="Launch\LwjglUnsafeAgent.jar" />
<EmbeddedResource Include="Resource\WikiEntries.txt" LogicalName="Resource\WikiEntries.txt" />
<EmbeddedResource Include="Minecraft\OptiFineHashes.json" LogicalName="Minecraft\OptiFineHashes.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\Plain Craft Launcher 2\Resources\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
+31
View File
@@ -0,0 +1,31 @@
namespace PCLCS;
public static class Resource {
/// <summary>
/// 将 CurseForge 的 ModLoaderType 映射为 <see cref="ModLoaders"/> 枚举。
/// </summary>
public static ModLoaders FromCurseForgeModLoaderType(int modLoaderType) => modLoaderType switch {
// https://docs.curseforge.com/rest-api/#tocS_ModLoaderType
1 => ModLoaders.Forge,
3 => ModLoaders.LiteLoader,
4 => ModLoaders.Fabric,
6 => ModLoaders.NeoForge,
_ => ModLoaders.None,
};
/// <summary>
/// 将 <see cref="ModLoaders"/> 映射为 CurseForge 的单一 ModLoaderType。
/// </summary>
public static int ToCurseForgeModLoaderType(ModLoaders loaders) {
if (loaders == ModLoaders.None) return 0;
if (!loaders.Flags().IsSingle()) throw new ArgumentException($"传入的枚举值 {loaders} 并非单个 Mod Loader", nameof(loaders));
return loaders switch {
ModLoaders.Forge => 1,
ModLoaders.LiteLoader => 3,
ModLoaders.Fabric => 4,
ModLoaders.NeoForge => 6,
_ => 0,
};
}
}
File diff suppressed because it is too large. Load diff
+83
View File
@@ -0,0 +1,83 @@
namespace PCLCS;
/// <summary>
/// MC 百科条目。
/// <para/>使用 <see cref="WikiEntry.All"/> 获取所有条目。
/// </summary>
public class WikiEntry {
/// <summary>
/// 在 MC 百科中的对应 ID。
/// </summary>
public int Id;
/// <summary>
/// 中文译名。
/// <c>null</c> 代表没有翻译。
/// </summary>
public string? ChineseName;
/// <summary>
/// 各个 Mod 平台的 Slug(例如 advanced-solar-panels)。
/// 若没有对应键则为不在该平台上。
/// </summary>
public Dictionary<ResourcePlatforms, string> Slugs = [];
/// <summary>
/// MC 百科的浏览量逆序排行,1 代表浏览量最低。
/// </summary>
public int Popularity;
/// <summary>
/// 内置数据库中的所有 MC 百科条目。
/// </summary>
public static readonly Lazy<List<WikiEntry>> All = new(() => {
var dataLines = FileUtils.ReadAsLines(@"Resource\WikiEntries.txt", type: typeof(WikiEntry)).ToList();
// 读取最后一行的浏览量
Queue<int> popularities = [];
for (int i = 0; i < dataLines[^1].Length; i += 3) { // 将每 3 个字符切割成一个元素
popularities.Enqueue(int.Parse(dataLines[^1].Substring(i, 3).ConvertRadix(86, 10)));
}
dataLines.RemoveAt(dataLines.Count - 1);
// 解析每一行
List<WikiEntry> results = [];
var lineNumber = 0;
foreach (var lineData in dataLines) {
lineNumber++;
if (lineData == "") continue;
var popularity = popularities.Dequeue();
foreach (var entryData in lineData.Split('¨')) {
var entry = new WikiEntry();
var parts = entryData.Split('|');
var slugs = parts[0];
if (slugs.StartsWithF("@")) {
entry.Slugs.Add(ResourcePlatforms.Modrinth, slugs.Replace("@", ""));
} else if (slugs.EndsWithF("@")) {
entry.Slugs.Add(ResourcePlatforms.CurseForge, slugs.TrimEnd('@'));
entry.Slugs.Add(ResourcePlatforms.Modrinth, entry.Slugs[ResourcePlatforms.CurseForge]);
} else if (slugs.Contains("@")) {
var splitSlugs = slugs.Split('@');
entry.Slugs.Add(ResourcePlatforms.CurseForge, splitSlugs[0]);
entry.Slugs.Add(ResourcePlatforms.Modrinth, splitSlugs[1]);
} else {
entry.Slugs.Add(ResourcePlatforms.CurseForge, slugs);
}
entry.Id = lineNumber;
entry.Popularity = popularity;
if (parts.Length >= 2) {
entry.ChineseName = parts[^1]; // 最后一项
if (entry.ChineseName.Contains("*")) { // 处理 *
var englishName = entry.Slugs.Values.First().Replace("-", " ").Capitalize();
entry.ChineseName = entry.ChineseName.Replace("*", $" ({englishName})");
}
}
results.Add(entry);
}
}
return results;
});
public override string ToString()
=> $"{Slugs.GetOrDefault(ResourcePlatforms.CurseForge)}&{Slugs.GetOrDefault(ResourcePlatforms.Modrinth)}|{Id}|{ChineseName},浏览量 {Popularity}";
}
+33 -6
View File
@@ -5,22 +5,49 @@ VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Plain Craft Launcher 2", "Plain Craft Launcher 2\Plain Craft Launcher 2.vbproj", "{4499F2D2-18EF-4238-B1F0-7BD547AFF925}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCLCS", "PCLCS\PCLCS.csproj", "{32DE4AAF-F4D6-42C5-9604-19236DE96C69}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeloongCore", "MeloongCore\Shared\MeloongCore.csproj", "{D22BBF3C-2177-0208-4E29-8466AA41AC6F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeloongCore.Wpf", "MeloongCore\Wpf\MeloongCore.Wpf.csproj", "{2DFFE2A0-1101-816B-D240-F644B2D74A68}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Beta|Any CPU = Beta|Any CPU
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
ReleaseUpdate|Any CPU = ReleaseUpdate|Any CPU
Snapshot|Any CPU = Snapshot|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Beta|Any CPU.ActiveCfg = BETA|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Beta|Any CPU.Build.0 = BETA|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Release|Any CPU.Build.0 = Release|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.ReleaseUpdate|Any CPU.ActiveCfg = ReleaseUpdate|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.ReleaseUpdate|Any CPU.Build.0 = ReleaseUpdate|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Release|Any CPU.Build.0 = Release|Any CPU
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Release|Any CPU.ActiveCfg = Debug|Any CPU
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Release|Any CPU.Build.0 = Debug|Any CPU
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Snapshot|Any CPU.ActiveCfg = Debug|Any CPU
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Snapshot|Any CPU.Build.0 = Debug|Any CPU
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Release|Any CPU.Build.0 = Release|Any CPU
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Release|Any CPU.Build.0 = Release|Any CPU
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+6 -9
View File
@@ -5,13 +5,10 @@
<httpWebRequest useUnsafeHeaderParsing="true"/>
</settings>
</system.net>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Net.DontEnableSchUseStrongCrypto=false" />
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/> <!-- #5337 -->
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" /> <!-- 长路径支持 -->
<generatePublisherEvidence enabled="false"/>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
@@ -26,10 +23,10 @@
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="13.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+15 -47
View File
@@ -1,24 +1,21 @@
<Application x:Class="Application"
<Application x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="clr-namespace:PCL.Controls.Behaviors"
xmlns:local="clr-namespace:PCL"
xmlns:core="clr-namespace:MeloongCore;assembly=MeloongCore"
xmlns:corewpf="clr-namespace:MeloongCore.Wpf;assembly=MeloongCore.Wpf"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="FormMain.xaml"
ShutdownMode="OnExplicitShutdown">
<Application.Resources>
<ResourceDictionary>
<!-- 本地化 -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Resources/Language/zh_CN.xaml" />
<ResourceDictionary Source="pack://application:,,,/Resources/Language/zh_CN.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Converter -->
<local:AdditionConverter x:Key="AdditionConverter"/>
<local:MultiplicationConverter x:Key="MultiplicationConverter"/>
<local:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
<local:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
<corewpf:AdditionConverter x:Key="AdditionConverter"/>
<corewpf:MultiplicationConverter x:Key="MultiplicationConverter"/>
<corewpf:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
<corewpf:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<!-- 颜色表 -->
@@ -66,7 +63,7 @@
<SolidColorBrush x:Key="ColorBrushWhite" Color="#ffffff" />
<SolidColorBrush x:Key="ColorBrushTransparent" Color="Transparent" />
<SolidColorBrush x:Key="ColorBrushSemiTransparent">#01eaf2fe</SolidColorBrush>
<SolidColorBrush x:Key="ColorBrushBackgroundTransparentSidebar" Color="#D2FFFFFF" />
<SolidColorBrush x:Key="ColorBrushBackgroundTransparentSidebar" Color="#F1FFFFFF" />
<!-- Window -->
<Style TargetType="{x:Type Window}" x:Key="MyWindow">
@@ -135,33 +132,6 @@
<Setter Property="FontFamily" Value="Resources/#PCL English, Microsoft YaHei UI" />
</Style>
<!-- ToolTip -->
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Loaded="TooltipLoaded" Unloaded="TooltipUnloaded" BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1.5" CornerRadius="4" MaxWidth="500" Background="#E5FFFFFF" Margin="0,0,4,4" SnapsToDevicePixels="True">
<Border.Effect>
<DropShadowEffect Opacity="0.3" BlurRadius="4" ShadowDepth="2" Color="{DynamicResource ColorObject1}" />
</Border.Effect>
<TextBlock Text="{TemplateBinding ContentControl.Content}" TextWrapping="Wrap" Foreground="{DynamicResource ColorBrush1}" Margin="7,5" FontSize="12"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExtendTextToolTip" TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Loaded="TooltipLoaded" Unloaded="TooltipUnloaded" BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1" CornerRadius="3" Background="White" Margin="0,0,4,4" SnapsToDevicePixels="True">
<TextBlock Text="{TemplateBinding ContentControl.Content}" TextWrapping="Wrap" Foreground="{DynamicResource ColorBrush1}" Margin="7,5" FontSize="12"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ScrollViewer -->
<Style x:Key="MyThumb" TargetType="Thumb">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
@@ -209,7 +179,7 @@
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:MyScrollViewer">
<Style TargetType="local:MyScrollViewer" x:Name="StyleMyScrollViewer">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="CanContentScroll" Value="False" />
<Setter Property="PanningMode" Value="VerticalOnly" />
@@ -225,6 +195,7 @@
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:PageResource" BasedOn="{StaticResource {x:Type local:MyScrollViewer}}" />
<Style TargetType="ScrollViewer" x:Key="ScrollViewerFullMargin">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="CanContentScroll" Value="False" />
@@ -289,7 +260,7 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyTextBox">
<Grid SnapsToDevicePixels="True" ToolTip="{TemplateBinding ToolTip}">
<Grid SnapsToDevicePixels="True">
<Grid.RowDefinitions>
<RowDefinition MinHeight="28" />
<RowDefinition Height="Auto" />
@@ -307,6 +278,7 @@
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
</Style>
<Style TargetType="PasswordBox">
<Setter Property="FontSize" Value="13" />
@@ -348,6 +320,7 @@
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
</Style>
<!-- ComboBox -->
@@ -367,7 +340,7 @@
<Setter Property="Background" Value="{StaticResource ColorBrushHalfWhite}" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="FontSize" Value="13" />
<Setter Property="MaxDropDownHeight" Value="160" />
<Setter Property="MaxDropDownHeight" Value="320" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
@@ -473,11 +446,6 @@
</Style>
<Style TargetType="ComboBoxItem" x:Key="MyComboBoxItem">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="ToolTipService.Placement" Value="Relative" />
<Setter Property="ToolTipService.VerticalOffset" Value="-2" />
<Setter Property="ToolTipService.HorizontalOffset" Value="200" />
<Setter Property="ToolTipService.InitialShowDelay" Value="150" />
<Setter Property="ToolTipService.BetweenShowDelay" Value="150" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Background" Value="{StaticResource ColorBrushTransparent}" />
<Setter Property="Template">
+178 -182
View File
@@ -1,25 +1,41 @@
Imports System.Reflection
Imports System.Reflection
Imports System.Windows.Threading
Public Class Application
#If DEBUG Then
''' <summary>
''' 用于开始程序时的一些测试。
''' </summary>
Private Sub Test()
Try
ModDevelop.Start()
Catch ex As Exception
Log(ex, "开发者模式测试出错", LogLevel.Msgbox)
End Try
End Sub
#End If
'开始
Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
Try
SecretOnApplicationStart()
'核心初始化
MeloongCore.Main.Init("PCL")
MeloongCore.Wpf.Main.Init()
If Not (e.Args.Contains("--update") OrElse e.Args.Contains("--gpu") OrElse e.Args.Contains("--memory")) Then '若是通过命令行调用,则不检查单例、不轮换日志(#9137)
WaitingMutex(e)
Logger.Instance = New PclLogger With {.logFolder = Paths.BaseThenName, .MinLevel = If(ModeDebug, LogLevel.Trace, LogLevel.Info)}
End If
'提升主线程优先级
Thread.CurrentThread.Priority = ThreadPriority.Highest
'执行开发版测试
If BuildType = BuildTypes.Debug Then
Try
ModDevelop.Start()
Catch ex As Exception
Logger.Error(ex, "开发者模式测试出错", LogBehavior.Alert)
End Try
End If
'检查 .NET Framework 版本
Try
If CInt(RegistryUtils.TryRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full", "Release", 999999)) < 528040 Then OldEnvironmentAssert()
Catch ex As Exception
Logger.Warn(ex, "检查 .NET Framework 版本失败")
End Try
'修复 WPF 字体加载(#3467)
Try
Dim _unused As New FormattedText("", Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Fonts.SystemTypefaces.First, 96, New MyColor, DPI)
Catch ex As UriFormatException
Environment.SetEnvironmentVariable("windir", Environment.GetEnvironmentVariable("SystemRoot"), EnvironmentVariableTarget.User)
Dim _unused As New FormattedText("", Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Fonts.SystemTypefaces.First, 96, New MyColor, DPI)
End Try
'检查参数调用
If e.Args.Length > 0 Then
If e.Args(0) = "--update" Then
@@ -34,7 +50,7 @@ Public Class Application
Catch ex As Exception
Environment.Exit(ProcessReturnValues.Fail)
End Try
ElseIf e.Args(0).StartsWithF("--memory") Then
ElseIf e.Args(0) = "--memory" Then
'内存优化
Dim Ram = My.Computer.Info.AvailablePhysicalMemory
Try
@@ -48,227 +64,215 @@ Public Class Application
Else
Environment.Exit((My.Computer.Info.AvailablePhysicalMemory - Ram) / 1024) '返回清理的内存量(K)
End If
#If DEBUG Then
'制作更新包
ElseIf e.Args(0) = "--edit1" Then
ExeEdit(e.Args(1), True)
Environment.Exit(ProcessReturnValues.TaskDone)
ElseIf e.Args(0) = "--edit2" Then
ExeEdit(e.Args(1), False)
Environment.Exit(ProcessReturnValues.TaskDone)
#End If
End If
End If
'初始化文件结构
Directory.CreateDirectory(Path & "PCL\Pictures")
Directory.CreateDirectory(Path & "PCL\Musics")
Try
Directory.CreateDirectory(PathTemp)
If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限")
DirectoryUtils.Create(Paths.Base & "PCL\Pictures\")
DirectoryUtils.Create(Paths.Base & "PCL\Musics\")
CheckPermissionWithException(Paths.Base & "PCL\")
Catch ex As Exception
If PathTemp = IO.Path.GetTempPath() & "PCL\" Then
MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
MsgBox($"PCL 没有对当前文件夹的权限({Paths.Base}PCL\),请尝试:" & vbCrLf &
"1. 将 PCL 移动到其他文件夹" & If(PathUtils.GetDiskName(Paths.Base) = "C"c, ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf &
"2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf &
"3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。",
MsgBoxStyle.Critical, "运行环境错误")
Environment.[Exit](ProcessReturnValues.Cancel)
End Try
RetryCacheCheck:
Try
DirectoryUtils.Create(PathTemp)
CheckPermissionWithException(PathTemp)
Catch ex As Exception
If PathUtils.IsSame(PathTemp, Path.GetTempPath() & "PCL\") Then
MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
Else
MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
Setup.Set("SystemSystemCache", "")
PathTemp = IO.Path.GetTempPath() & "PCL\"
MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
Settings.Set("SystemSystemCache", "")
PathTemp = Path.GetTempPath() & "PCL\"
GoTo RetryCacheCheck
End If
End Try
Directory.CreateDirectory(PathTemp & "Cache")
Directory.CreateDirectory(PathTemp & "Download")
Directory.CreateDirectory(PathAppdata)
'检测单例
#If Not DEBUG Then
Dim ShouldWaitForExit As Boolean = e.Args.Length > 0 AndAlso e.Args(0) = "--wait" '要求等待已有的 PCL 退出
Dim WaitRetryCount As Integer = 0
WaitRetry:
Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ")
If WindowHwnd = IntPtr.Zero Then FindWindow(Nothing, "Plain Craft Launcher 2 ")
If WindowHwnd <> IntPtr.Zero Then
If ShouldWaitForExit AndAlso WaitRetryCount < 20 Then '至多等待 10 秒
WaitRetryCount += 1
Thread.Sleep(500)
GoTo WaitRetry
End If
'将已有的 PCL 窗口拖出来
ShowWindowToTop(WindowHwnd)
'播放提示音并退出
Beep()
Environment.[Exit](ProcessReturnValues.Cancel)
End If
#End If
DirectoryUtils.Create(PathTemp & "Cache\")
DirectoryUtils.Create(Paths.AppDataThenName)
'设置 ToolTipService 默认值
ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(300))
ToolTipService.BetweenShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(400))
ToolTipService.ShowDurationProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(9999999))
ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom))
ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0))
ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0))
ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(100))
'设置网络配置默认值
ServicePointManager.Expect100Continue = False
ServicePointManager.DefaultConnectionLimit = 10000
ServicePointManager.UseNagleAlgorithm = False
ServicePointManager.EnableDnsRoundRobin = True
ServicePointManager.ReusePort = True
If Environment.OSVersion.Version < New Version(6, 2) Then 'Windows 7 回退(#8519)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
End If
'设置初始窗口
If Setup.Get("UiLauncherLogo") Then
If Settings.Get(Of Boolean)("UiLauncherLogo") Then
FrmStart = New SplashScreen("Images\icon.ico")
FrmStart.Show(False, True)
End If
'动态 DLL 调用
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve
'日志初始化
LogStart()
'添加日志
Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
Log($"[Start] 程序路径:{PathWithName}")
Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
Log($"[Start] 管理员权限:{IsAdmin()}")
'基础信息
Logger.Info($"程序版本:{VersionDisplay} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
If BuildType = BuildTypes.Snapshot Then
Logger.Info($"识别码:{Identify}{If(ThemeCheckOne(9), ",已解锁反馈主题", ",未解锁反馈主题")}")
Else
Logger.Info($"识别码:{Identify}")
End If
Logger.Info($"程序路径:{PathExe}")
Logger.Info($"系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
Logger.Info($"管理员权限:{WindowsUtils.HasAdminRole()}")
'检测异常环境
If Path.Contains(IO.Path.GetTempPath()) OrElse Path.Contains("AppData\Local\Temp\") Then
MyMsgBox("请将 PCL 从压缩包中解压之后再使用!" & vbCrLf & "在当前环境下运行可能会导致丢失游戏存档或设置,部分功能也可能无法使用!", "环境警告", "我知道了", IsWarn:=True)
If Paths.Base.Contains(Path.GetTempPath()) OrElse Paths.Base.Contains("AppData\Local\Temp\") Then
MyMsgBox("请将 PCL 从压缩包中解压后再使用!" & vbCrLf & "如果不会解压,可以在网上寻找教程。", "需要解压!", "我知道了", IsWarn:=True, ForceWait:=True)
FormMain.EndProgramForce(ProcessReturnValues.Cancel)
End If
If Is32BitSystem Then
MyMsgBox("PCL 和新版 Minecraft 均不再支持 32 位系统,部分功能将无法使用。" & vbCrLf & "非常建议重装为 64 位系统后再进行游戏!", "环境警告", "我知道了", IsWarn:=True)
If Not Environment.Is64BitOperatingSystem Then
MyMsgBox("PCL 和新版 Minecraft 均不再支持 32 位系统,请重装为 64 位系统后再进行游戏!", "环境警告", "我知道了", IsWarn:=True, ForceWait:=True)
FormMain.EndProgramForce(ProcessReturnValues.Cancel)
End If
'设置初始化
Setup.Load("SystemDebugMode")
Setup.Load("SystemDebugAnim")
Setup.Load("ToolDownloadThread")
Setup.Load("ToolDownloadCert")
'网络配置初始化
ServicePointManager.Expect100Continue = True
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
ServicePointManager.DefaultConnectionLimit = 1024
'计时
Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
ApplicationStartTick = GetTimeTick()
'执行测试
#If DEBUG Then
Test()
#End If
Logger.Info($"第一阶段加载用时:{GetTimeMs() - ApplicationStartTick} ms")
ApplicationStartTick = GetTimeMs()
AniControlEnabled += 1
Catch ex As Exception
Dim FilePath As String = Nothing
Try
FilePath = PathWithName
FilePath = PathExe
Catch
End Try
MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
MsgBox(ex.GetDisplay(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
FormMain.EndProgramForce(ProcessReturnValues.Exception)
End Try
End Sub
''' <summary>
''' 要求程序以单例运行。
''' 如果已在运行,则将其窗口拖出来并播放提示音,然后结束当前进程。
''' </summary>
Private Shared Sub WaitingMutex(e As StartupEventArgs)
If BuildType = BuildTypes.Debug Then Return
Try
Dim MutexCreatedNew As Boolean
PclMutex = New Mutex(True, "PCL_SingletonMutex", MutexCreatedNew)
If MutexCreatedNew Then Return
Logger.Warn("已有一个 PCL 实例正在运行")
'等待已有的 PCL 退出
If e.Args.Length > 0 AndAlso e.Args(0) = "--wait" Then
Try
If PclMutex.WaitOne(10000) Then Return '等待至多 10 秒
Catch Ignored As AbandonedMutexException
Return '已有实例异常退出,继续启动
End Try
End If
'将已有的 PCL 窗口拖出来
Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ")
If WindowHwnd = IntPtr.Zero Then WindowHwnd = FindWindow(Nothing, "Plain Craft Launcher 2 ")
If WindowHwnd <> IntPtr.Zero Then ShowWindowToTop(WindowHwnd)
'播放提示音并退出
Beep()
Environment.[Exit](ProcessReturnValues.Cancel)
Catch ex As Exception
Logger.Warn(ex, "单例检查失败")
End Try
End Sub
Private Shared PclMutex As Mutex
'结束
Private Sub Application_SessionEnding(sender As Object, e As SessionEndingCancelEventArgs) Handles Me.SessionEnding
FrmMain.EndProgram(False)
FrmMain?.EndProgram(False)
End Sub
'异常
Private IsCritErrored As Boolean = False
Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
On Error Resume Next
e.Handled = True
If IsProgramEnded Then Exit Sub
If IsCritErrored Then
'在汇报错误后继续引发错误,知道这次压不住了
FormMain.EndProgramForce(ProcessReturnValues.Exception)
Exit Sub
'触发页面的 Dispatcher
If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then
CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
If e.Handled Then Return
End If
IsCritErrored = True
Dim ExceptionString As String = GetExceptionDetail(e.Exception, True)
If ExceptionString.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse
ExceptionString.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse
ExceptionString.Contains(".NET Framework") OrElse ' “自动错误判断” 的结果分析
ExceptionString.Contains("未能加载文件或程序集") Then
OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer")
MsgBox("你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.6.2!", MsgBoxStyle.Information, "运行环境错误")
FormMain.EndProgramForce(ProcessReturnValues.Cancel)
If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then
CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
If e.Handled Then Return
End If
'正常处理
e.Handled = True
If IsProgramEnding Then Return
FeedbackInfo()
Dim Detail As String = e.Exception.GetDisplay(True)
If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse
Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析
OldEnvironmentAssert(e.Exception)
Else
FeedbackInfo()
Log(e.Exception, "程序出现未知错误", LogLevel.Assert, "锟斤拷烫烫烫")
Logger.Error(e.Exception, "程序出现未知错误", LogBehavior.AlertThenCrash)
End If
End Sub
Private Shared Sub OldEnvironmentAssert(Optional ex As Exception = Nothing)
OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net48-offline-installer")
If ex Is Nothing Then
Logger.Error($"你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.8!{vbCrLf}若安装失败,可以先卸载高版本的 .NET Framework 然后再试。", LogBehavior.AlertThenCrash)
Else
Logger.Error(ex, $"你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.8!{vbCrLf}若安装失败,可以先卸载高版本的 .NET Framework 然后再试。", LogBehavior.AlertThenCrash)
End If
End Sub
'动态 DLL 调用
Private Shared AssemblyNAudio As Assembly
Private Shared AssemblyJson As Assembly
Private Shared AssemblyDialog As Assembly
Private Shared AssemblyImazenWebp As Assembly
Private Shared ReadOnly AssemblyNAudioLock As New Object
Private Shared ReadOnly AssemblyJsonLock As New Object
Private Shared ReadOnly AssemblyDialogLock As New Object
Private Shared ReadOnly AssemblyImazenWebpLock As New Object
'动态 DLL 加载
Private Sub New() '这里必须尽早调用,且不能使用任何库,否则加载 MeloongCore 就会导致崩溃
Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "ThrottleDebounce", "System.Net.Http.Formatting", "Microsoft.Win32.Registry", "PCLCS", "MeloongCore.Wpf", "MeloongCore"}
Static LoadedAssemblies As New ConcurrentDictionary(Of String, Lazy(Of Assembly))(StringComparer.Ordinal) '缓存
AddHandler AppDomain.CurrentDomain.AssemblyResolve,
Function(sender As Object, Args As ResolveEventArgs) As Assembly
Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWith(p, StringComparison.Ordinal))
If Prefix Is Nothing Then Return Nothing
Dim LazyAssembly = LoadedAssemblies.GetOrAdd(Prefix, Function(p) New Lazy(Of Assembly)(
Function()
Dim LoadedAssembly = Assembly.Load(DirectCast(My.Resources.ResourceManager.GetObject(p), Byte()))
If p = "Imazen.WebP" Then ExtractLibwebp() 'WebP 特判
Return LoadedAssembly
End Function, LazyThreadSafetyMode.ExecutionAndPublication))
Return LazyAssembly.Value
End Function
End Sub
Private Shared Sub ExtractLibwebp() '这个方法会调用 ModBase,进而调用 MeloongCore,所以不能放在 New 里
SetDllDirectory(PathUtils.RemoveSlashSuffix(PathPure.Value))
Try
ExtractResources(PathPure.Value & "libwebp.dll", "libwebp64")
Catch ex As Exception
Logger.Warn(ex, "写入 libwebp.dll 失败") '防止同时加载多个图片时,同时写入文件导致文件占用,进而导致崩溃
End Try
End Sub
Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
Public Shared Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
If args.Name.StartsWithF("NAudio") Then
SyncLock AssemblyNAudioLock
If AssemblyNAudio Is Nothing Then
Log("[Start] 加载 DLL:NAudio")
AssemblyNAudio = Assembly.Load(GetResources("NAudio"))
End If
Return AssemblyNAudio
End SyncLock
ElseIf args.Name.StartsWithF("Newtonsoft.Json") Then
SyncLock AssemblyJsonLock
If AssemblyJson Is Nothing Then
Log("[Start] 加载 DLL:Json")
AssemblyJson = Assembly.Load(GetResources("Json"))
End If
Return AssemblyJson
End SyncLock
ElseIf args.Name.StartsWithF("Ookii.Dialogs.Wpf") Then
SyncLock AssemblyDialogLock
If AssemblyDialog Is Nothing Then
Log("[Start] 加载 DLL:Dialogs")
AssemblyDialog = Assembly.Load(GetResources("Dialogs"))
End If
Return AssemblyDialog
End SyncLock
ElseIf args.Name.StartsWithF("Imazen.WebP") Then
SyncLock AssemblyImazenWebpLock
If AssemblyImazenWebp Is Nothing Then
Log("[Start] 加载 DLL:Imazen.WebP")
AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP"))
SetDllDirectory(PathPure.TrimEnd("\"))
WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
End If
Return AssemblyImazenWebp
End SyncLock
Else
Return Nothing
End If
End Function
'切换窗口
'控件模板事件
Private Sub MyIconButton_Click(sender As Object, e As EventArgs)
Select Case Setup.Get("LoginType")
Select Case Settings.Get(Of McLoginType)("LoginType")
Case McLoginType.Ms
'微软
Dim MsJson As JObject = GetJson(Setup.Get("LoginMsJson"))
Dim MsJson As JObject = Settings.Get(Of String)("LoginMsJson").DeserializeJson()
MsJson.Remove(sender.Tag)
Setup.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
Settings.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
If FrmLoginMs.ComboAccounts.SelectedItem Is sender.Parent Then FrmLoginMs.ComboAccounts.SelectedIndex = 0
FrmLoginMs.ComboAccounts.Items.Remove(sender.Parent)
Case McLoginType.Legacy
'离线
Dim Names As New List(Of String)
Names.AddRange(Setup.Get("LoginLegacyName").ToString.Split("¨"))
Names.AddRange(Settings.Get(Of String)("LoginLegacyName").ToString.Split("¨"))
Names.Remove(sender.Tag)
Setup.Set("LoginLegacyName", Join(Names, "¨"))
Settings.Set("LoginLegacyName", Names.Join("¨"c))
FrmLoginLegacy.ComboName.ItemsSource = Names
FrmLoginLegacy.ComboName.Text = If(Names.Any, Names(0), "")
Case Else
'第三方
Dim Token As String = GetStringFromEnum(Setup.Get("LoginType"))
Dim Token As String = Settings.Get(Of McLoginType)("LoginType").ToString()
Dim Dict As New Dictionary(Of String, String)
Dim Names As New List(Of String)
Dim Passs As New List(Of String)
If Not Setup.Get("Login" & Token & "Email") = "" Then Names.AddRange(Setup.Get("Login" & Token & "Email").ToString.Split("¨"))
If Not Setup.Get("Login" & Token & "Pass") = "" Then Passs.AddRange(Setup.Get("Login" & Token & "Pass").ToString.Split("¨"))
If Not Settings.Get(Of String)("Login" & Token & "Email") = "" Then Names.AddRange(Settings.Get(Of String)("Login" & Token & "Email").Split("¨"))
If Not Settings.Get(Of String)("Login" & Token & "Pass") = "" Then Passs.AddRange(Settings.Get(Of String)("Login" & Token & "Pass").Split("¨"))
For i = 0 To Names.Count - 1
Dict.Add(Names(i), Passs(i))
Next
Dict.Remove(sender.Tag)
Setup.Set("Login" & Token & "Email", Join(Dict.Keys, "¨"))
Setup.Set("Login" & Token & "Pass", Join(Dict.Values, "¨"))
Settings.Set("Login" & Token & "Email", Dict.Keys.Join("¨"c))
Settings.Set("Login" & Token & "Pass", Dict.Values.Join("¨"c))
Select Case Token
Case "Nide"
FrmLoginNide.ComboName.ItemsSource = Dict.Keys
@@ -282,12 +286,4 @@ WaitRetry:
End Select
End Sub
Public Shared ShowingTooltips As New List(Of Border)
Private Sub TooltipLoaded(sender As Border, e As EventArgs)
ShowingTooltips.Add(sender)
End Sub
Private Sub TooltipUnloaded(sender As Border, e As RoutedEventArgs)
ShowingTooltips.Remove(sender)
End Sub
End Class
@@ -0,0 +1,216 @@
' Author: uye (owner of the MaaAssistantArknights team)
' Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights
' License: Apache License 2.0 (this file only)
'
' This file is originally developed for MaaAssistantArknights and licensed under AGPL v3.0.
' As the original author and copyright holder,
' I hereby re-license this file under the Apache License 2.0 for use in the Plain Craft Launcher project.
'
' This file is contributed under the spirit of good faith and open cooperation.
' It does not implement any core launcher functions of PCL.
'
' Description:
' Provides a WPF clipboard handling fix to avoid OpenClipboard exceptions
' in TextBox, RichTextBox, and DataGrid under Windows focus issues or clipboard hooks.
'
' Date: 2025-07-03
Namespace Controls.Behaviors
Public NotInheritable Class ClipboardInterceptor
Private Sub New()
End Sub
Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty =
DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor),
New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged))
Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean)
element.SetValue(EnableSafeClipboardProperty, value)
End Sub
Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean
Return element.GetValue(EnableSafeClipboardProperty)
End Function
Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then
AddCommandBindingsToTextBox(DirectCast(d, TextBox))
ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then
AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox))
ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then
AddCommandBindingsToDataGrid(DirectCast(d, DataGrid))
End If
End Sub
Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox)
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox))
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox, AddressOf CanEditTextBox))
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox, AddressOf CanEditTextBox))
End Sub
Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox)
rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox))
rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox))
rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox))
End Sub
Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid)
dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid))
End Sub
Private Shared Sub SetClipboardText(text As String)
Forms.Clipboard.SetDataObject(text, False, retryTimes:=10, retryDelay:=100) '实际的复制
Try
System.Windows.Clipboard.Flush() '将数据持久化到系统剪贴板,避免应用程序退出后数据丢失
Catch ex As Exception '持久化失败不抛出异常
Logger.Warn(ex, $"剪贴板持久化失败(错误码:0x{ex.HResult:X8})")
End Try
End Sub
Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
Try
SetClipboardText(tb.SelectedText)
Catch ex As Exception
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
End Sub
Private Shared Sub CanEditTextBox(sender As Object, e As CanExecuteRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
e.CanExecute = tb IsNot Nothing AndAlso Not tb.IsReadOnly AndAlso
(e.Command IsNot ApplicationCommands.Cut OrElse tb.SelectionLength > 0)
e.Handled = True
End Sub
Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
e.Handled = True
If tb Is Nothing OrElse tb.IsReadOnly OrElse tb.SelectionLength <= 0 Then Return
Try
SetClipboardText(tb.SelectedText)
Catch ex As Exception
Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
Return
End Try
tb.SelectedText = String.Empty
End Sub
Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim tb = TryCast(sender, TextBox)
e.Handled = True
If tb Is Nothing OrElse tb.IsReadOnly Then Return
Try
Dim pasteText = Retrier.Attempt(func:=
Function(Attempt) As String
Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing)
End Function, maxAttempts:=10, delay:=Function() TimeSpan.FromMilliseconds(100))
If pasteText IsNot Nothing Then
'将换行符替换为空格(#7825)
If Not tb.AcceptsReturn Then pasteText = pasteText.ReplaceLineEndings(" ", mergeMultiple:=True)
Dim start = tb.SelectionStart
tb.SelectedText = pasteText
tb.CaretIndex = start + pasteText.Length
tb.SelectionLength = 0
End If
Catch ex As Exception
Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
End Sub
Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim rtb = TryCast(sender, RichTextBox)
If rtb Is Nothing Then Return
Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End)
If String.IsNullOrEmpty(textRange.Text) Then Return
Try
SetClipboardText(textRange.Text)
Catch ex As Exception
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
End Sub
Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim rtb = TryCast(sender, RichTextBox)
If rtb Is Nothing Then Return
Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
If String.IsNullOrEmpty(selection.Text) Then Return
e.Handled = True
Try
SetClipboardText(selection.Text)
Catch ex As Exception
Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
Return
End Try
selection.Text = String.Empty
End Sub
Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
Dim rtb = TryCast(sender, RichTextBox)
If rtb Is Nothing Then Return
Try
Dim pasteText = Retrier.Attempt(func:=
Function(Attempt) As String
Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing)
End Function, maxAttempts:=2, delay:=Function() TimeSpan.FromMilliseconds(200))
If pasteText Is Nothing Then Return
Dim selection = rtb.Selection
selection.Text = pasteText
Dim caretPos = selection.End
rtb.CaretPosition = caretPos
rtb.Selection.Select(caretPos, caretPos)
Catch ex As Exception
Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
End Sub
Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs)
Dim dg = TryCast(sender, DataGrid)
If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return
Dim sb = New StringBuilder
Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item)
For Each row In rowGroups
Dim rowText = String.Join(vbTab, row.Select(
Function(cell)
Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock)
Return If(tb IsNot Nothing, tb.Text, "")
End Function))
sb.AppendLine(rowText)
Next
Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
Try
SetClipboardText(sbStr)
Catch ex As Exception
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
End Try
e.Handled = True
End Sub
End Class
End Namespace
@@ -0,0 +1,23 @@
Imports System.Windows.Threading
''' <summary>
''' 指示页面可以通过按下 F5 或以其他方式刷新。
''' </summary>
Public Interface IRefreshable
Sub Refresh()
End Interface
''' <summary>
''' 指示页面可以处理未捕获的异常。
''' </summary>
Public Interface IDispatcherUnhandledException
Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs)
End Interface
''' <summary>
''' 指示该控件是可勾选的。
''' </summary>
Public Interface IMyRadio
Event Check(sender As Object, e As RouteEventArgs)
Event Changed(sender As Object, e As RouteEventArgs)
End Interface
@@ -0,0 +1,46 @@
Public Class LazyLoadBehavior
''' <summary>
''' 指定首次进入 ScrollViewer 的可视范围时执行的操作。
''' </summary>
Public Shared Sub OnFirstEnterScrollViewerViewport(obj As DependencyObject, value As Action)
obj.SetValue(IsInViewportProperty, value)
End Sub
Private Shared ReadOnly IsInViewportProperty As DependencyProperty =
DependencyProperty.RegisterAttached("IsInViewport", GetType(Action), GetType(LazyLoadBehavior),
New PropertyMetadata(Nothing, AddressOf OnIsInViewportChanged))
Private Shared Sub OnIsInViewportChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim Element = TryCast(d, FrameworkElement)
If Element Is Nothing OrElse e.NewValue Is Nothing Then Return
Dim Handled As Boolean = False
Dim Handler As EventHandler =
Sub()
If Handled Then Return
'判断元素是否可见
If Element.RenderSize.Width < Double.Epsilon Then Return
If Not Element.IsVisible Then Return
'判断是否在 ScrollViewer 的可视区域内
Dim Scroll = FindParentScrollViewer(Element)
If Scroll Is Nothing Then Return
If Not New Rect(0, 0, Scroll.ViewportWidth, Scroll.ViewportHeight).IntersectsWith(
Element.TransformToAncestor(Scroll).TransformBounds(New Rect(New Point(0, 0), Element.RenderSize))) Then Return
'执行
Handled = True
CType(e.NewValue, Action).Invoke()
RemoveHandler Element.LayoutUpdated, Handler
End Sub
AddHandler Element.LayoutUpdated, Handler
End Sub
Private Shared Function FindParentScrollViewer(d As DependencyObject) As ScrollViewer
While d IsNot Nothing
If TypeOf d Is ScrollViewer Then Return CType(d, ScrollViewer)
d = VisualTreeHelper.GetParent(d)
End While
Return Nothing
End Function
End Class
@@ -1,4 +0,0 @@
Public Interface IMyRadio
Event Check(sender As Object, e As RouteEventArgs)
Event Changed(sender As Object, e As RouteEventArgs)
End Interface
@@ -1,4 +1,4 @@
<Border x:Class="MyButton"
<Border x:Class="MyButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FocusVisualStyle="{x:Null}" x:Name="PanBack"
@@ -1,4 +1,4 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyButton
@@ -136,43 +136,18 @@ Public Class MyButton
End If
Catch ex As Exception
Log(ex, "刷新按钮颜色出错")
Logger.Warn(ex, "刷新按钮颜色出错")
End Try
End Sub
'实现自定义事件
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Exit Sub
Log("[Control] 按下按钮:" & Text)
RaiseEvent Click(sender, e)
If Not String.IsNullOrEmpty(Tag) Then
If Tag.ToString.StartsWithF("链接-") OrElse Tag.ToString.StartsWithF("启动-") Then
Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!")
End If
End If
ModEvent.TryStartEvent(EventType, EventData)
End Sub
Public Property EventType As String
Get
Return GetValue(EventTypeProperty)
End Get
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing))
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
Private IsMouseDown As Boolean = False
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Return
Logger.Info($"按下按钮:{Text}")
RaiseEvent Click(sender, e)
RaiseCustomEvent() '自定义事件
End Sub
Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
IsMouseDown = True
Focus()
@@ -185,7 +160,7 @@ Public Class MyButton
AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid)
End Sub
Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Exit Sub
If Not IsMouseDown Then Return
IsMouseDown = False
AniStart({
AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle))
@@ -193,7 +168,7 @@ Public Class MyButton
End Sub
Private Sub Button_MouseLeave() Handles Me.MouseLeave
AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid)
If Not IsMouseDown Then Exit Sub
If Not IsMouseDown Then Return
IsMouseDown = False
AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid)
End Sub
+147 -159
View File
@@ -1,9 +1,9 @@
Public Class MyCard
Public Class MyCard
'控件
Inherits Grid
Private ReadOnly MainGrid As Grid
Public ReadOnly Property MainChrome As SystemDropShadowChrome
Public ReadOnly Property MainChrome As MyDropShadow
Private ReadOnly MainBorder As Border
Public Property BorderChild As UIElement
Get
@@ -63,17 +63,18 @@
'UI 建立
Public Sub New()
MainChrome = New SystemDropShadowChrome With {.Margin = New Thickness(-9.5, -9, 0.5, -0.5), .Opacity = 0.1, .CornerRadius = New CornerRadius(6)}
MainChrome.SetResourceReference(SystemDropShadowChrome.ColorProperty, "ColorObject1")
MainChrome = New MyDropShadow With {
.Margin = New Thickness(-3, -3, -3, -3 - GetWPFSize(1)), .ShadowRadius = 3, .Opacity = DropShadowIdleOpacity, .CornerRadius = New CornerRadius(5)}
MainChrome.SetResourceReference(MyDropShadow.ColorProperty, "ColorObject1")
Children.Insert(0, MainChrome)
MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(205, 255, 255, 255)), .CornerRadius = New CornerRadius(6), .IsHitTestVisible = False}
MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(245, 255, 255, 255)), .CornerRadius = New CornerRadius(5), .IsHitTestVisible = False}
Children.Insert(1, MainBorder)
MainGrid = New Grid
Children.Add(MainGrid)
End Sub
Private IsLoad As Boolean = False
Private Sub Init() Handles Me.Loaded
If IsLoad Then Exit Sub
If IsLoad Then Return
IsLoad = True
'初次加载限定
If MainTextBlock Is Nothing Then
@@ -89,8 +90,9 @@
MainGrid.Children.Add(MainSwap)
End If
'改变默认的折叠
If IsSwaped AndAlso SwapControl IsNot Nothing Then
If IsSwapped AndAlso SwapControl IsNot Nothing Then
MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0))
SwapControl.Visibility = Visibility.Collapsed
'取消由于高度变化被迫触发的高度动画
Dim RawUseAnimation As Boolean = UseAnimation
UseAnimation = False
@@ -100,115 +102,61 @@
RunInUi(Sub() UseAnimation = RawUseAnimation, True)
End If
End Sub
Public Sub StackInstall()
StackInstall(SwapControl, SwapType, Title)
'填充
Private ContentBuilder As Action(Of Panel)
Public Sub SetContentBuilder(Builder As Action(Of Panel))
ContentBuilder = Builder
End Sub
Public Sub SetContentBuilder(Of T)(Items As IEnumerable(Of T), ItemBuilder As Func(Of T, UIElement))
SetContentBuilder(
Sub(Stack)
For Each Item In Items
Stack.Children.Add(ItemBuilder(Item))
Next
Stack.Children.Add(New FrameworkElement With {.Height = 18})
End Sub)
End Sub
''' <summary>
''' 使用 ContentBuilder 生成卡片内容。
''' </summary>
Public Sub BuildContent()
If ContentBuilder Is Nothing Then Return
Dim Stack = TryCast(SwapControl, Panel)
If Stack Is Nothing Then Return
Try
ContentBuilder(Stack)
ContentBuilder = Nothing
Catch
Stack.Children.Clear()
Throw
End Try
TriggerForceResize()
End Sub
Public Shared Sub StackInstall(ByRef Stack As StackPanel, Type As Integer, Optional CardTitle As String = "")
'这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎
'但是现在……反正勉强能用……懒得改了就这样吧.jpg
'别骂了别骂了.jpg
If IsNothing(Stack.Tag) Then Exit Sub
'排序
Select Case Type
Case 3
Stack.Tag = CType(Stack.Tag, List(Of DlOptiFineListEntry)).Sort(Function(a, b) VersionSortBoolean(a.NameDisplay, b.NameDisplay))
Case 4, 10
Stack.Tag = CType(Stack.Tag, List(Of DlLiteLoaderListEntry)).Sort(Function(a, b) VersionSortBoolean(a.Inherit, b.Inherit))
Case 6
Stack.Tag = CType(Stack.Tag, List(Of DlForgeVersionEntry)).Sort(Function(a, b) a.Version > b.Version)
Case 8, 9
Stack.Tag = CType(Stack.Tag, List(Of CompFile)).Sort(Function(a, b) a.ReleaseDate > b.ReleaseDate)
End Select
'控件转换
Select Case Type
Case 5
Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)}
Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain)
LoadingPickaxe.State = Loader
Loader.Start(Stack.Tag)
AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged
AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click
Stack.Children.Add(LoadingPickaxe)
Case 6
ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
Case 8
CompFilesCardPreload(Stack, Stack.Tag)
End Select
'实现控件虚拟化
For Each Data As Object In Stack.Tag
Select Case Type
Case 0
Stack.Children.Add(PageSelectRight.McVersionListItem(Data))
Case 2
Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
Case 3
Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
Case 4
Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False))
Case 5
Case 6
Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
Case 7
'不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug
Stack.Children.Add(McDownloadListItem(Data, Sub(sender, e) FrmDownloadInstall.MinecraftSelected(sender, e), False))
Case 8
If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <>
CType(Stack.Tag, List(Of CompFile)).Count Then
'存在重复的名称(#1344)
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True))
Else
'不存在重复的名称,正常加载
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click))
End If
Case 9
If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <>
CType(Stack.Tag, List(Of CompFile)).Count Then
'存在重复的名称(#1344)
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True))
Else
'不存在重复的名称,正常加载
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click))
End If
Case 10
Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True))
Case 11
Stack.Children.Add(CType(Data, HelpEntry).ToListItem)
Case 12
Stack.Children.Add(FabricDownloadListItem(CType(Data, JObject), AddressOf FrmDownloadInstall.Fabric_Selected))
Case 13
Stack.Children.Add(NeoForgeDownloadListItem(Data, AddressOf NeoForgeSave_Click, True))
Case Else
Log("未知的虚拟化种类:" & Type, LogLevel.Feedback)
End Select
Next
Stack.Children.Add(New FrameworkElement With {.Height = 18}) '下边距,同时适应折叠
Stack.Tag = Nothing
End Sub
'事件
'动画
Private Const DropShadowIdleOpacity As Double = 0.07
Private Const DropShadowHoverOpacity As Double = 0.4
Public Property HasMouseAnimation As Boolean = True
Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
If Not HasMouseAnimation Then Exit Sub
If Not HasMouseAnimation Then Return
Dim AniList As New List(Of AniData)
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150))
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150))
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 90))
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 90))
AniList.AddRange({
AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject2", 180),
AaColor(MainBorder, Border.BackgroundProperty, New MyColor(230, 255, 255, 255) - MainBorder.Background, 180),
AaOpacity(MainChrome, 0.3 - MainChrome.Opacity, 180)
AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject4", 90),
AaOpacity(MainChrome, DropShadowHoverOpacity - MainChrome.Opacity, 90)
})
AniStart(AniList, "MyCard Mouse " & Uuid)
End Sub
Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
If Not HasMouseAnimation Then Exit Sub
If Not HasMouseAnimation Then Return
Dim AniList As New List(Of AniData)
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250))
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250))
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 90))
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 90))
AniList.AddRange({
AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject1", 300),
AaColor(MainBorder, Border.BackgroundProperty, New MyColor(205, 255, 255, 255) - MainBorder.Background, 300),
AaOpacity(MainChrome, 0.1 - MainChrome.Opacity, 300)
AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject1", 90),
AaOpacity(MainChrome, DropShadowIdleOpacity - MainChrome.Opacity, 90)
})
AniStart(AniList, "MyCard Mouse " & Uuid)
End Sub
@@ -222,46 +170,64 @@
Private IsHeightAnimating As Boolean = False
Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
If Not UseAnimation Then Exit Sub
Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
If Not UseAnimation Then Return
Dim DeltaHeight As Double = If(IsSwapped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
'卡片的进入时动画已被页面通用切换动画替代
If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Exit Sub
If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Return
StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False)
End Sub
Private Sub StartHeightAnimation(DeltaHeight As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
If IsHeightAnimating OrElse FrmMain Is Nothing Then Exit Sub '避免 XAML 设计器出错
Private Sub StartHeightAnimation(Delta As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
If IsHeightAnimating OrElse FrmMain Is Nothing Then Return '避免 XAML 设计器出错
Dim AnimList As New List(Of AniData)
If DeltaHeight > 10 OrElse (DeltaHeight < -10 AndAlso Not IsNothing(SwapControl)) Then '如果不是需要折叠的卡片,高度减小时的弹跳会吞掉按钮下边框
'高度增加较大,使用弹起动画
Dim Delta As Double = MathClamp(Math.Abs(DeltaHeight) * 0.05, 3, 10) * Math.Sign(DeltaHeight)
AnimList.AddRange({
AaHeight(Me, DeltaHeight + Delta, 300, If(IsLoadAnimation, 30, 0), If(DeltaHeight > FrmMain.Height, New AniEaseInFluent(AniEasePower.ExtraStrong), New AniEaseOutFluent(AniEasePower.ExtraStrong))),
AaHeight(Me, -Delta, 150, 260, Ease:=New AniEaseOutFluent(AniEasePower.Strong))
})
Dim AbsDelta = Math.Abs(Delta)
If AbsDelta <= 800 Then
'短距离,直接使用 150ms 的缓动动画
AnimList.Add(AaHeight(Me, Delta, 150,, New AniEaseOutFluent(AniEasePower.ExtraStrong)))
Else
'普通的改变就行啦
AnimList.AddRange({
AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent)
})
Dim EaseLength As Integer, EaseTime As Integer
Dim InitSpeed As Integer '到达缓动区前的初速度
If Delta < 0 AndAlso AbsDelta - EaseLength > 5000 * 0.1 Then
'收回距离过长 (>0.1s),强制以 100ms 完成匀速段,然后让减速段更长
EaseLength = 200
EaseTime = 150
InitSpeed = (AbsDelta - EaseLength) / 0.1
ElseIf Delta > 0 AndAlso AbsDelta - EaseLength > 5000 * 0.6 Then
'展开距离过长 (>0.6s),以 5000 速度展示 300ms 匀速段,剩下的距离全部归入减速段
InitSpeed = 5000
EaseLength = AbsDelta - InitSpeed * 0.3
EaseTime = 400
Else
'中程,匀速地快速展开(或收回)
EaseLength = 150
EaseTime = 200
InitSpeed = 4000
End If
'匀速段
AnimList.Add(AaHeight(Me, (AbsDelta - EaseLength) * Math.Sign(Delta),
(AbsDelta - EaseLength) / InitSpeed * 1000))
'减速段
AnimList.Add(AaHeight(Me, EaseLength * Math.Sign(Delta),
EaseTime,, New AniEaseOutFluentWithInitial(InitSpeed, EaseTime / 1000, EaseLength), True))
End If
AnimList.Add(AaCode(
Sub()
IsHeightAnimating = False
Height = ActualUsedHeight
If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed
If IsSwapped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed
End Sub,, True))
AniStart(AnimList, "MyCard Height " & Uuid)
IsHeightAnimating = True
ActualUsedHeight = If(IsSwaped, SwapedHeight, Height)
ActualUsedHeight = If(IsSwapped, SwapedHeight, Height)
Height = PreviousHeight
End Sub
''' <summary>
''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。
''' 通知 MyCard,控件内容已改变,需要中断动画并瞬间更新高度。
''' </summary>
Public Sub TriggerForceResize()
Height = If(IsSwaped, SwapedHeight, Double.NaN)
Height = If(IsSwapped, SwapedHeight, Double.NaN)
AniStop("MyCard Height " & Uuid)
IsHeightAnimating = False
End Sub
@@ -272,68 +238,90 @@
'若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠
'这是因为不能直接在 XAML 中设置 SwapControl
Public SwapControl As Object
Public SwapControl As FrameworkElement
Public Property CanSwap As Boolean = False
''' <summary>
''' 被折叠的种类,用于控件虚拟化。
''' </summary>
Public Property SwapType As Integer
''' <summary>
''' 是否已被折叠。
''' </summary>
Public Property IsSwaped As Boolean
Public Property IsSwapped As Boolean
Get
Return _IsSwaped
Return _IsSwapped
End Get
Set(value As Boolean)
If _IsSwaped = value Then Exit Property
_IsSwaped = value
If SwapControl Is Nothing Then Exit Property
If _IsSwapped = value Then Return
_IsSwapped = value
If SwapControl Is Nothing Then Return
'展开
If Not IsSwaped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title)
If Not IsSwapped Then BuildContent()
'若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行
If Not IsLoaded Then Exit Property
'更新高度
If Not IsLoaded Then Return
'更新样式
SwapControl.Visibility = Visibility.Visible
TriggerForceResize()
UpdateCursorStyle()
'改变箭头
AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 400,, New AniEaseOutBack(AniEasePower.Weak)), "MyCard Swap " & Uuid, True)
AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True)
End Set
End Property
Private _IsSwaped As Boolean = False
Private _IsSwapped As Boolean = False
<Obsolete("IsSwaped 存在拼写错误,请换用 IsSwapped 属性。")>
Public Property IsSwaped As Boolean
Get
Return GetValue(IsSwapedProperty)
End Get
Set(value As Boolean)
SetValue(IsSwapedProperty, value)
End Set
End Property
Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False,
Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean)))
Public Property SwapLogoRight As Boolean = False
Private IsMouseDown As Boolean = False
Private IsSwapMouseDown As Boolean = False '用于触发卡片展开/折叠的 MouseDown
Private IsCustomMouseDown As Boolean = False '用于触发自定义事件的 MouseDown
Public Event PreviewSwap(sender As Object, e As RouteEventArgs)
Public Event Swap(sender As Object, e As RouteEventArgs)
Public Const SwapedHeight As Integer = 40
Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
Dim Pos As Double = Mouse.GetPosition(Me).Y
If Not IsSwaped AndAlso
(SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
IsMouseDown = True
If Not IsSwapped AndAlso
(Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return
IsCustomMouseDown = True
If Not IsSwapped AndAlso
(SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
IsSwapMouseDown = True
End Sub
Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Return
IsMouseDown = False
Private Sub MyCard_MouseLeftButtonUp() Handles Me.MouseLeftButtonUp
If Not IsCustomMouseDown Then Return
IsCustomMouseDown = False
RaiseCustomEvent() '触发自定义事件
If Not IsSwapMouseDown Then Return
IsSwapMouseDown = False
Dim Pos As Double = Mouse.GetPosition(Me).Y
If Not IsSwaped AndAlso
(SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
If Not IsSwapped AndAlso
(SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
Dim ee = New RouteEventArgs(True)
RaiseEvent PreviewSwap(Me, ee)
If ee.Handled Then
IsMouseDown = False
Dim e = New RouteEventArgs(True)
RaiseEvent PreviewSwap(Me, e)
If e.Handled Then
IsSwapMouseDown = False
Return
End If
IsSwaped = Not IsSwaped
Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
RaiseEvent Swap(Me, ee)
IsSwapped = Not IsSwapped
Logger.Info($"{If(IsSwapped, "折叠卡片", "展开卡片")}{If(Title Is Nothing, "", ":" & Title)}")
RaiseEvent Swap(Me, e)
End Sub
Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
IsMouseDown = False
IsSwapMouseDown = False
End Sub
Private Sub UpdateCursorStyle() Handles Me.MouseMove
If _MainSwap Is Nothing Then Cursor = Nothing : Return
Dim Pos As Double = Mouse.GetPosition(Me).Y
Cursor = If(MainSwap.Visibility = Visibility.Visible AndAlso (IsSwapped OrElse (Pos <= SwapedHeight - 6 AndAlso Not (Pos = 0 AndAlso Not IsMouseDirectlyOver))),
Cursors.Hand, Nothing)
End Sub
#End Region
@@ -350,7 +338,7 @@ Partial Public Module ModAnimation
AaCode(
Sub()
If RemoveFromChildren Then
If Control.Parent Is Nothing Then Exit Sub
If Control.Parent Is Nothing Then Return
CType(Control.Parent, Object).Children.Remove(Control)
Else
Control.Visibility = Visibility.Collapsed
@@ -360,7 +348,7 @@ Partial Public Module ModAnimation
}, "MyCard Dispose " & Control.Uuid)
Else
If RemoveFromChildren Then
If Control.Parent Is Nothing Then Exit Sub
If Control.Parent Is Nothing Then Return
CType(Control.Parent, Object).Children.Remove(Control)
Else
Control.Visibility = Visibility.Collapsed
@@ -368,4 +356,4 @@ Partial Public Module ModAnimation
If CallBack IsNot Nothing Then CallBack(Control)
End If
End Sub
End Module
End Module
@@ -1,4 +1,4 @@
<Grid
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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" mc:Ignorable="d" x:Class="MyCheckBox"
@@ -1,7 +1,8 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyCheckBox
Implements ISettingControl
'基础
@@ -12,9 +13,6 @@ Public Class MyCheckBox
''' <param name="user">是否为用户手动改变的勾选状态。</param>
Public Event Change(sender As Object, user As Boolean)
Public Event PreviewChange(sender As Object, e As RouteEventArgs)
Public Sub RaiseChange()
RaiseEvent Change(Me, False)
End Sub '使外部程序引发本控件的 Change 事件
'自定义属性
Public Property Checked As Boolean
@@ -40,7 +38,7 @@ Public Class MyCheckBox
''' <param name="user">是否由用户引发。</param>
Public Sub SetChecked(value As Boolean, user As Boolean)
Try
If value = Checked Then Exit Sub
If value = Checked Then Return
'Preview 事件
If value AndAlso user Then
@@ -50,7 +48,7 @@ Public Class MyCheckBox
MouseDowned = True
Checkbox_MouseLeave()
MouseDowned = False
Exit Sub
Return
End If
End If
@@ -59,8 +57,9 @@ Public Class MyCheckBox
'更改动画
SyncUI()
RaiseCustomEvent()
Catch ex As Exception
Log(ex, "设置 Checked 失败")
Logger.Warn(ex, "设置 Checked 失败")
End Try
End Sub
Private Sub SyncUI()
@@ -74,7 +73,7 @@ Public Class MyCheckBox
AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
}, "MyCheckBox Scale " & Uuid)
AniStart({
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush2"), "ColorBrushGray4"), AnimationTimeOfCheck)
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush2"), "ColorBrushGray5"), AnimationTimeOfCheck)
}, "MyCheckBox BorderColor " & Uuid)
AniStart({
AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
@@ -87,7 +86,7 @@ Public Class MyCheckBox
AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
}, "MyCheckBox Scale " & Uuid)
AniStart({
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfCheck)
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush1"), "ColorBrushGray5"), AnimationTimeOfCheck)
}, "MyCheckBox BorderColor " & Uuid)
AniStart({
AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
@@ -101,11 +100,11 @@ Public Class MyCheckBox
If Checked Then
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 1
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 1
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray5"))
Else
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 0
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 0
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
End If
End If
End Sub
@@ -133,14 +132,14 @@ Public Class MyCheckBox
Private MouseDowned As Boolean = False
Private AllowMouseDown As Boolean = True
Private Sub Checkbox_MouseUp() Handles Me.MouseLeftButtonUp
If Not MouseDowned Then Exit Sub
Log("[Control] 按下复选框(" & (Not Checked).ToString & "):" & Text)
If Not MouseDowned Then Return
Logger.Info($"按下复选框({(Not Checked)}):{Text}")
MouseDowned = False
SetChecked(Not Checked, True)
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
End Sub
Private Sub Checkbox_MouseDown() Handles Me.MouseLeftButtonDown
If Not AllowMouseDown Then Exit Sub
If Not AllowMouseDown Then Return
MouseDowned = True
Focus()
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushBg1", 100), "MyCheckBox Background " & Uuid)
@@ -154,7 +153,7 @@ Public Class MyCheckBox
End If
End Sub
Private Sub Checkbox_MouseLeave() Handles Me.MouseLeave
If Not MouseDowned Then Exit Sub
If Not MouseDowned Then Return
MouseDowned = False
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
If Checked Then
@@ -180,18 +179,18 @@ Public Class MyCheckBox
Else
'不可用
AniStart({
AaColor(ShapeBorder, Border.BorderBrushProperty, ColorGray4 - ShapeBorder.BorderBrush, AnimationTimeOfMouseOut)
AaColor(ShapeBorder, Border.BorderBrushProperty, ColorGray5 - ShapeBorder.BorderBrush, AnimationTimeOfMouseOut)
}, "MyCheckBox BorderColor " & Uuid)
AniStart({
AaColor(LabText, TextBlock.ForegroundProperty, ColorGray4 - LabText.Foreground, AnimationTimeOfMouseOut)
AaColor(LabText, TextBlock.ForegroundProperty, ColorGray5 - LabText.Foreground, AnimationTimeOfMouseOut)
}, "MyCheckBox TextColor " & Uuid)
End If
Else
'无动画
AniStop("MyCheckBox TextColor " & Uuid)
AniStop("MyCheckBox BorderColor " & Uuid)
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"))
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray5"))
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"))
End If
End Sub
Private Sub Checkbox_MouseEnterAnimation() Handles Me.MouseEnter
@@ -203,13 +202,29 @@ Public Class MyCheckBox
}, "MyCheckBox BorderColor " & Uuid)
End Sub
Private Sub Checkbox_MouseLeaveAnimation() Handles Me.MouseLeave
If Not IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
If Not IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
AniStart({
AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut)
AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray5"), AnimationTimeOfMouseOut)
}, "MyCheckBox TextColor " & Uuid)
AniStart({
AaColor(ShapeBorder, Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut)
AaColor(ShapeBorder, Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"), AnimationTimeOfMouseOut)
}, "MyCheckBox BorderColor " & Uuid)
End Sub
#Region "设置"
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
Checked = Boolean.Parse(NewValue)
End Sub
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
Return Checked.ToString
End Function
Private Sub SaveSetting() Handles Me.Change
SettingService.SaveSetting(Me)
End Sub
#End Region
End Class
+50 -14
View File
@@ -1,5 +1,6 @@
Public Class MyComboBox
Public Class MyComboBox
Inherits ComboBox
Implements ISettingControl
Public Event TextChanged(sender As Object, e As TextChangedEventArgs)
'基础
@@ -8,7 +9,7 @@
Public Property HintText As String = ""
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
If Not IsEditable Then Exit Sub
If Not IsEditable Then Return
Try
TextBox = Template.FindName("PART_EditableTextBox", Me)
TextBox.AddHandler(LostFocusEvent, New RoutedEventHandler(AddressOf RefreshColor))
@@ -21,7 +22,7 @@
End If
If HintText.Length > 0 Then TextBox.HintText = HintText
Catch ex As Exception
Log(ex, "初始化可编辑文本框失败(" & If(Name, "") & ")", LogLevel.Feedback)
Logger.Error(ex, $"初始化可编辑文本框失败({If(Name, "")})")
End Try
End Sub
Private _Text As String = SelectedItem
@@ -103,10 +104,11 @@
Private Sub MyComboBox_DropDownOpened(sender As Object, e As EventArgs) Handles Me.DropDownOpened
RealWidth = Width
Width = ActualWidth
'将下拉菜单透明度与主窗口同步
Try
CType(Template.FindName("PanPopup", Me), Grid).Opacity = FrmMain.Opacity
Catch ex As Exception
Log(ex, "设置下拉框透明度失败", LogLevel.Feedback)
Logger.Error(ex, "设置下拉框透明度失败")
End Try
End Sub
Private Sub MyComboBox_DropDownClosed(sender As Object, e As EventArgs) Handles Me.DropDownClosed
@@ -116,16 +118,15 @@
'修复 WPF Bug:下拉框文本修改后,依然误认为还选择着此前的选项,导致再次点击该选项时内容不变
Private IsTextChanging As Boolean = False
Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
If IsTextChanging OrElse Not IsEditable Then Exit Sub
If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then
Dim RawText As String = Text
Dim RawSelectionStart As Integer = TextBox.SelectionStart
IsTextChanging = True
SelectedItem = Nothing
Text = RawText
TextBox.SelectionStart = RawSelectionStart
IsTextChanging = False
End If
If IsTextChanging OrElse Not IsEditable Then Return
If SelectedItem Is Nothing OrElse Text = SelectedItem.ToString Then Return
Dim RawText As String = Text
Dim RawSelectionStart As Integer = TextBox.SelectionStart
IsTextChanging = True
SelectedItem = Nothing
Text = RawText
TextBox.SelectionStart = RawSelectionStart
IsTextChanging = False
End Sub
Public ReadOnly Property ContentPresenter As ContentPresenter
@@ -134,4 +135,39 @@
End Get
End Property
Private Sub MyComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles Me.SelectionChanged
If IsLoaded AndAlso AniControlEnabled = 0 Then RaiseCustomEvent()
End Sub
#Region "设置"
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
Dim TargetItem = Items.OfType(Of DependencyObject)().FirstOrDefault(Function(i) Val(If(SettingService.GetValue(i), -1)) = NewValue)
If TargetItem IsNot Nothing Then
SelectedItem = TargetItem
ElseIf Items.Count > NewValue Then
SelectedIndex = NewValue
Else
Logger.Warn($"尝试显示设置 {SettingService.GetKey(Me)},但没有找到设置值 {NewValue} 的对应项")
SelectedIndex = -1
SelectedItem = Nothing
End If
End Sub
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
If SelectedItem Is Nothing Then Return Nothing
If TypeOf SelectedItem Is DependencyObject Then
Return If(SettingService.GetValue(SelectedItem), SelectedIndex)
Else
Return SelectedIndex
End If
End Function
Private Sub SaveSetting() Handles Me.SelectionChanged
If SelectedItem Is Nothing Then Return
SettingService.SaveSetting(Me)
End Sub
#End Region
End Class
@@ -1,4 +1,4 @@
Public Class MyComboBoxItem
Public Class MyComboBoxItem
Inherits ComboBoxItem
'基础
@@ -35,7 +35,7 @@
NewFontOpacity = 0.4
Time = AnimationTimeOut
End If
If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Exit Sub
If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Return
BackColorName = NewBackColorName
FontOpacity = NewFontOpacity
'触发颜色动画
@@ -61,7 +61,7 @@
End Operator
Private Sub MyComboBoxItem_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
Log("[Control] 选择下拉列表项:" & Tostring())
Logger.Info($"选择下拉列表项:{Tostring()}")
End Sub
End Class
@@ -1,65 +1,89 @@
Public NotInheritable Class SystemDropShadowChrome
Public Class MyDropShadow
Inherits Decorator
'源码来源于:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1
Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
''' <summary>
''' 阴影颜色。
''' </summary>
Public Property Color As Color
Get
Return CType(GetValue(ColorProperty), Color)
Return GetValue(ColorProperty)
End Get
Set(value As Color)
SetValue(ColorProperty, value)
End Set
End Property
Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(MyDropShadow),
New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
''' <summary>
''' 阴影模糊半径。
''' </summary>
Public Property ShadowRadius As Double
Get
Return GetValue(ShadowRadiusProperty)
End Get
Set(value As Double)
SetValue(ShadowRadiusProperty, value)
End Set
End Property
Public Shared ReadOnly ShadowRadiusProperty As DependencyProperty = DependencyProperty.Register("ShadowRadius", GetType(Double), GetType(MyDropShadow),
New FrameworkPropertyMetadata(CDbl(5), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
''' <summary>
''' 圆角大小。
''' </summary>
Public Property CornerRadius As CornerRadius
Get
Return CType(GetValue(CornerRadiusProperty), CornerRadius)
Return GetValue(CornerRadiusProperty)
End Get
Set(value As CornerRadius)
SetValue(CornerRadiusProperty, value)
End Set
End Property
Private Shared Function IsCornerRadiusValid(value As Object) As Boolean
Dim cr As CornerRadius = CType(value, CornerRadius)
Dim cr As CornerRadius = value
Return Not (cr.TopLeft < 0.0 OrElse cr.TopRight < 0.0 OrElse cr.BottomLeft < 0.0 OrElse cr.BottomRight < 0.0 OrElse Double.IsNaN(cr.TopLeft) OrElse Double.IsNaN(cr.TopRight) OrElse Double.IsNaN(cr.BottomLeft) OrElse Double.IsNaN(cr.BottomRight) OrElse Double.IsInfinity(cr.TopLeft) OrElse Double.IsInfinity(cr.TopRight) OrElse Double.IsInfinity(cr.BottomLeft) OrElse Double.IsInfinity(cr.BottomRight))
End Function
Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyDropShadow),
New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
' =======================================
' 渲染
' =======================================
Private Const ShadowDepth As Double = 5
Protected Overrides Sub OnRender(drawingContext As DrawingContext)
Dim cornerRadius As CornerRadius = Me.CornerRadius
Dim shadowBounds As New Rect(New Point(ShadowDepth, ShadowDepth), New Size(RenderSize.Width, RenderSize.Height))
Dim shadowBounds As New Rect(0, 0, RenderSize.Width, RenderSize.Height)
Dim color As Color = Me.Color
If shadowBounds.Width > 0 AndAlso shadowBounds.Height > 0 AndAlso color.A > 0 Then
Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowDepth
Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowDepth
Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowRadius
Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowRadius
Dim maxRadius As Double = Math.Min(centerWidth * 0.5, centerHeight * 0.5)
cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius)
cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius)
cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius)
cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius)
Dim brushes As Brush() = GetBrushes(color, cornerRadius)
Dim centerTop As Double = shadowBounds.Top + ShadowDepth
Dim centerLeft As Double = shadowBounds.Left + ShadowDepth
Dim centerRight As Double = shadowBounds.Right - ShadowDepth
Dim centerBottom As Double = shadowBounds.Bottom - ShadowDepth
Dim centerTop As Double = shadowBounds.Top + ShadowRadius
Dim centerLeft As Double = shadowBounds.Left + ShadowRadius
Dim centerRight As Double = shadowBounds.Right - ShadowRadius
Dim centerBottom As Double = shadowBounds.Bottom - ShadowRadius
Dim guidelineSetX As Double() = New Double() {centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight, centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight}
Dim guidelineSetY As Double() = New Double() {centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight, centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom}
drawingContext.PushGuidelineSet(New GuidelineSet(guidelineSetX, guidelineSetY))
cornerRadius.TopLeft += ShadowDepth
cornerRadius.TopRight += ShadowDepth
cornerRadius.BottomLeft += ShadowDepth
cornerRadius.BottomRight += ShadowDepth
cornerRadius.TopLeft += ShadowRadius
cornerRadius.TopRight += ShadowRadius
cornerRadius.BottomLeft += ShadowRadius
cornerRadius.BottomRight += ShadowRadius
Dim topLeft As New Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft)
drawingContext.DrawRectangle(brushes(Placement.TopLeft), Nothing, topLeft)
Dim topWidth As Double = guidelineSetX(2) - guidelineSetX(1)
If topWidth > 0 Then
Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowDepth)
Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowRadius)
drawingContext.DrawRectangle(brushes(Placement.Top), Nothing, top)
End If
@@ -68,14 +92,14 @@
Dim leftHeight As Double = guidelineSetY(3) - guidelineSetY(1)
If leftHeight > 0 Then
Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowDepth, leftHeight)
Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowRadius, leftHeight)
drawingContext.DrawRectangle(brushes(Placement.Left), Nothing, left)
End If
Dim rightHeight As Double = guidelineSetY(4) - guidelineSetY(2)
If rightHeight > 0 Then
Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowDepth, rightHeight)
Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowRadius, rightHeight)
drawingContext.DrawRectangle(brushes(Placement.Right), Nothing, right)
End If
@@ -84,20 +108,20 @@
Dim bottomWidth As Double = guidelineSetX(4) - guidelineSetX(3)
If bottomWidth > 0 Then
Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowDepth)
Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowRadius)
drawingContext.DrawRectangle(brushes(Placement.Bottom), Nothing, bottom)
End If
Dim bottomRight As New Rect(guidelineSetX(4), guidelineSetY(4), cornerRadius.BottomRight, cornerRadius.BottomRight)
drawingContext.DrawRectangle(brushes(Placement.BottomRight), Nothing, bottomRight)
If cornerRadius.TopLeft = ShadowDepth AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
If cornerRadius.TopLeft = ShadowRadius AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
Dim center As New Rect(guidelineSetX(0), guidelineSetY(0), centerWidth, centerHeight)
drawingContext.DrawRectangle(brushes(Placement.Center), Nothing, center)
Else
Dim figure As New PathFigure()
If cornerRadius.TopLeft > ShadowDepth Then
If cornerRadius.TopLeft > ShadowRadius Then
figure.StartPoint = New Point(guidelineSetX(1), guidelineSetY(0))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(1), guidelineSetY(1)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(1)), True))
@@ -105,7 +129,7 @@
figure.StartPoint = New Point(guidelineSetX(0), guidelineSetY(0))
End If
If cornerRadius.BottomLeft > ShadowDepth Then
If cornerRadius.BottomLeft > ShadowRadius Then
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(3)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(3)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(5)), True))
@@ -113,7 +137,7 @@
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(5)), True))
End If
If cornerRadius.BottomRight > ShadowDepth Then
If cornerRadius.BottomRight > ShadowRadius Then
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(5)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(4)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(4)), True))
@@ -121,7 +145,7 @@
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(5)), True))
End If
If cornerRadius.TopRight > ShadowDepth Then
If cornerRadius.TopRight > ShadowRadius Then
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(2)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(2)), True))
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(0)), True))
@@ -158,28 +182,27 @@
Private _brushes As Brush()
Private Shared Sub ClearBrushes(o As DependencyObject, e As DependencyPropertyChangedEventArgs)
CType(o, SystemDropShadowChrome)._brushes = Nothing
CType(o, MyDropShadow)._brushes = Nothing
End Sub
Private Shared Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
Dim gradientScale As Double = 1 / (cornerRadius + ShadowDepth)
Dim gsc As New GradientStopCollection From {
New GradientStop(c, (0.5 + cornerRadius) * gradientScale)
}
Private Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
Dim gradientScale As Double = 1 / (ShadowRadius + cornerRadius)
Dim gsc As New GradientStopCollection
Dim stopColor As Color = c
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.1 + cornerRadius) * gradientScale))
stopColor.A = CByte(0.74336 * c.A)
gsc.Add(New GradientStop(stopColor, (1.5 + cornerRadius) * gradientScale))
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.3 + cornerRadius) * gradientScale))
stopColor.A = CByte(0.38053 * c.A)
gsc.Add(New GradientStop(stopColor, (2.5 + cornerRadius) * gradientScale))
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.5 + cornerRadius) * gradientScale))
stopColor.A = CByte(0.12389 * c.A)
gsc.Add(New GradientStop(stopColor, (3.5 + cornerRadius) * gradientScale))
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.7 + cornerRadius) * gradientScale))
stopColor.A = CByte(0.02654 * c.A)
gsc.Add(New GradientStop(stopColor, (4.5 + cornerRadius) * gradientScale))
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.9 + cornerRadius) * gradientScale))
stopColor.A = 0
gsc.Add(New GradientStop(stopColor, (5 + cornerRadius) * gradientScale))
gsc.Add(New GradientStop(stopColor, (ShadowRadius + cornerRadius) * gradientScale))
gsc.Freeze()
Return gsc
End Function
Private Shared Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
Private Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
Dim brushes As Brush() = New Brush(8) {}
brushes(Placement.Center) = New SolidColorBrush(c)
brushes(Placement.Center).Freeze()
@@ -297,3 +320,5 @@
End Function
End Class
'参考自:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1
@@ -1,8 +1,8 @@
<Grid
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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" x:Name="PanBack" mc:Ignorable="d" x:Class="MyExtraButton"
Width="40" RenderTransformOrigin="0.5,0.5" ToolTipService.Placement="Left" ToolTipService.VerticalOffset="16" ToolTipService.HorizontalOffset="-8" Height="0">
Width="40" RenderTransformOrigin="0.5,0.5" Height="0">
<Grid.RenderTransform>
<ScaleTransform ScaleX="0" ScaleY="0" />
</Grid.RenderTransform>
@@ -1,4 +1,4 @@
Public Class MyExtraButton
Public Class MyExtraButton
'声明
Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
@@ -11,7 +11,7 @@
Return _Progress
End Get
Set(value As Double)
If _Progress = value Then Exit Property
If _Progress = value Then Return
_Progress = value
If value < 0.0001 Then
PanProgress.Visibility = Visibility.Collapsed
@@ -30,7 +30,7 @@
Return _Logo
End Get
Set(value As String)
If value = _Logo Then Exit Property
If value = _Logo Then Return
_Logo = value
Path.Data = (New GeometryConverter).ConvertFromString(value)
End Set
@@ -51,27 +51,28 @@
Return _Show
End Get
Set(value As Boolean)
If _Show = value Then Exit Property
If _Show = value Then Return
_Show = value
RunInUi(Sub()
If value Then
'有了
Visibility = Visibility.Visible
AniStart({
AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
}, "MyExtraButton MainScale " & Uuid)
Else
'没了
AniStart({
AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
AaCode(Sub() Visibility = Visibility.Collapsed,, True)
}, "MyExtraButton MainScale " & Uuid)
End If
IsHitTestVisible = value '防止缩放动画中依然可以点进去
End Sub)
RunInUi(
Sub()
If value Then
'有了
Visibility = Visibility.Visible
AniStart({
AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
}, "MyExtraButton MainScale " & Uuid)
Else
'没了
AniStart({
AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
AaCode(Sub() Visibility = Visibility.Collapsed,, True)
}, "MyExtraButton MainScale " & Uuid)
End If
IsHitTestVisible = value '防止缩放动画中依然可以点进去
End Sub)
End Set
End Property
Public Delegate Function ShowCheckDelegate() As Boolean
@@ -83,7 +84,7 @@
'触发点击事件
Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
If IsLeftMouseHeld Then
Log("[Control] 按下附加按钮" & If(ToolTip = "", "", ":" & ToolTip.ToString))
Logger.Info($"按下附加按钮{If(ToolTip = "", "", ":" & ToolTip)}")
RaiseEvent Click(sender, e)
e.Handled = True
Button_LeftMouseUp()
@@ -91,7 +92,7 @@
End Sub
Private Sub Button_RightMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonUp
If IsRightMouseHeld Then
Log("[Control] 右键按下附加按钮" & If(ToolTip = "", "", ":" & ToolTip.ToString))
Logger.Info($"右键按下附加按钮{If(ToolTip = "", "", ":" & ToolTip)}")
RaiseEvent RightClick(sender, e)
e.Handled = True
Button_RightMouseUp()
@@ -121,7 +122,7 @@
Focus()
End Sub
Private Sub Button_RightMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonDown
If Not CanRightClick Then Exit Sub
If Not CanRightClick Then Return
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
AniStart({
AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
@@ -137,11 +138,12 @@
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
}, "MyExtraButton Scale " & Uuid)
End If
If IsLeftMouseHeld Then RaiseCustomEvent()
IsLeftMouseHeld = False
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
If Not CanRightClick Then Exit Sub
If Not CanRightClick Then Return
If Not IsLeftMouseHeld Then
AniStart({
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
@@ -191,7 +193,7 @@
End If
Catch ex As Exception
Log(ex, "刷新图标按钮颜色出错")
Logger.Warn(ex, "刷新图标按钮颜色出错")
End Try
End Sub
@@ -199,16 +201,17 @@
''' 发出一圈波浪效果提示。
''' </summary>
Public Sub Ribble()
RunInUi(Sub()
Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
PanScale.Children.Insert(0, Shape)
AniStart({
AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
AaOpacity(Shape, -Shape.Opacity, 1000),
AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
}, "ExtraButton Ribble " & GetUuid())
End Sub)
RunInUi(
Sub()
Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
PanScale.Children.Insert(0, Shape)
AniStart({
AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
AaOpacity(Shape, -Shape.Opacity, 1000),
AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
}, "ExtraButton Ribble " & GetUuid())
End Sub)
End Sub
End Class
@@ -1,10 +1,10 @@
<Grid
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="PanBack" mc:Ignorable="d" x:Class="MyExtraTextButton"
RenderTransformOrigin="0.5,0.5" ToolTipService.Placement="Left" Height="52" Margin="20">
RenderTransformOrigin="0.5,0.5" Height="52" Margin="20" Cursor="Hand">
<Grid.RenderTransform>
<ScaleTransform ScaleX="0" ScaleY="0" />
</Grid.RenderTransform>
@@ -1,4 +1,4 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyExtraTextButton
@@ -14,7 +14,7 @@ Public Class MyExtraTextButton
Return _Logo
End Get
Set(value As String)
If value = _Logo Then Exit Property
If value = _Logo Then Return
_Logo = value
Path.Data = (New GeometryConverter).ConvertFromString(value)
End Set
@@ -55,7 +55,7 @@ Public Class MyExtraTextButton
Return _Show
End Get
Set(value As Boolean)
If _Show = value Then Exit Property
If _Show = value Then Return
_Show = value
RunInUi(
Sub()
@@ -81,12 +81,12 @@ Public Class MyExtraTextButton
'触发点击事件
Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
If IsLeftMouseHeld Then
Log("[Control] 按下附加图标按钮:" & Text)
RaiseEvent Click(sender, e)
e.Handled = True
Button_LeftMouseUp()
End If
If Not IsLeftMouseHeld Then Return
Logger.Info($"按下附加图标按钮:{Text}")
RaiseEvent Click(sender, e)
e.Handled = True
RaiseCustomEvent()
Button_LeftMouseUp()
End Sub
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
@@ -156,7 +156,7 @@ Public Class MyExtraTextButton
End If
Catch ex As Exception
Log(ex, "刷新附加图标按钮颜色出错")
Logger.Warn(ex, "刷新附加图标按钮颜色出错")
End Try
End Sub
+5 -13
View File
@@ -1,24 +1,16 @@
<Border x:Class="MyHint" x:Name="PanBack"
<Border x:Class="MyHint" x:Name="PanBack"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL"
FocusVisualStyle="{x:Null}"
UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="1" BorderBrush="#99FF4444" CornerRadius="3">
<Border.Background>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop x:Name="Gradient1" Color="#88FFBBBB" Offset="0.3"/>
<GradientStop x:Name="Gradient2" Color="#88FF8888" Offset="0.9"/>
</LinearGradientBrush>
</Border.Background>
UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="3,0,0,0" BorderBrush="#99FF4444" CornerRadius="2">
<Grid UseLayoutRounding="False" SnapsToDevicePixels="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Path IsHitTestVisible="False" Grid.Column="0" x:Name="Path" VerticalAlignment="Center" Fill="#BF0000" Width="14" Stretch="Uniform" Margin="10,0,2,0" Data="F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z " />
<TextBlock IsHitTestVisible="False" Grid.Column="1" x:Name="LabText" Margin="0,0.5,0,0" Padding="6,6,8,6" VerticalAlignment="Center" Foreground="#BF0000" TextWrapping="Wrap" />
<local:MyIconButton x:Name="BtnClose" SnapsToDevicePixels="True" Visibility="Collapsed" Foreground="#BF0000" Theme="Custom" Margin="0,0,6,0" Grid.Column="2" Height="20" Width="20" VerticalAlignment="Center" Logo="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
ToolTip="不再显示" ToolTipService.Placement="Center" ToolTipService.VerticalOffset="29" ToolTipService.HorizontalOffset="2" />
<TextBlock IsHitTestVisible="False" x:Name="LabText" LineHeight="16" Margin="0,0,0,-1" Padding="12,9" VerticalAlignment="Center" TextWrapping="Wrap" />
<local:MyIconButton x:Name="BtnClose" Visibility="Collapsed" Theme="Custom" Margin="0,0,8,0" Grid.Column="1" Height="20" Width="20" VerticalAlignment="Center" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z"
ToolTip="不再提示" />
</Grid>
</Border>
+80 -60
View File
@@ -1,38 +1,74 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyHint
Public Uuid As Integer = GetUuid()
Private _IsWarn As Boolean = True
Public Property IsWarn As Boolean
'边框
Public Property HasBorder As Boolean
Get
Return _IsWarn
Return BorderThickness.Top > 0
End Get
Set(value As Boolean)
If _IsWarn = value Then Exit Property
_IsWarn = value
If _IsWarn Then
BorderBrush = New MyColor("#CCFF4444")
Gradient1.Color = New MyColor("#BBFFBBBB")
Gradient2.Color = New MyColor("#BBFF8888")
Path.Fill = New MyColor("#BF0000")
LabText.Foreground = New MyColor("#BF0000")
BtnClose.Foreground = New MyColor("#BF0000")
Path.Data = (New GeometryConverter).ConvertFromString("F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z ")
If value Then
BorderThickness = New Thickness(3, GetWPFSize(1), GetWPFSize(1), GetWPFSize(1))
Else
BorderBrush = New MyColor("#CC4D76FF")
Gradient1.Color = New MyColor("#BBB0D0FF")
Gradient2.Color = New MyColor("#BB9EBAFF")
Path.Fill = New MyColor("#0062BF")
LabText.Foreground = New MyColor("#0062BF")
BtnClose.Foreground = New MyColor("#0062BF")
Path.Data = (New GeometryConverter).ConvertFromString("F1M38,19C48.4934,19 57,27.5066 57,38 57,48.4934 48.4934,57 38,57 27.5066,57 19,48.4934 19,38 19,27.5066 27.5066,19 38,19z M33.25,33.25L33.25,36.4167 36.4166,36.4167 36.4166,47.5 33.25,47.5 33.25,50.6667 44.3333,50.6667 44.3333,47.5 41.1666,47.5 41.1666,36.4167 41.1666,33.25 33.25,33.25z M38.7917,25.3333C37.48,25.3333 36.4167,26.3967 36.4167,27.7083 36.4167,29.02 37.48,30.0833 38.7917,30.0833 40.1033,30.0833 41.1667,29.02 41.1667,27.7083 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333z")
BorderThickness = New Thickness(3, 0, 0, 0)
End If
End Set
End Property
'配色
Public Enum Themes
Blue = 0
Red = 1
Yellow = 2
End Enum
Public Property Theme As Themes
Get
Return _ColorType
End Get
Set(value As Themes)
_ColorType = value
UpdateUI()
End Set
End Property
Private _ColorType As Themes = Themes.Red
Private Sub UpdateUI() Handles Me.Loaded
Select Case Theme
Case Themes.Blue
Background = New MyColor("#d9ecff")
BorderBrush = New MyColor("#1172D4")
LabText.Foreground = New MyColor("#0F64B8")
BtnClose.Foreground = New MyColor("#0F64B8")
Case Themes.Red
Background = New MyColor("#ffdddf")
BorderBrush = New MyColor("#d82929")
LabText.Foreground = New MyColor("#bf0b0b")
BtnClose.Foreground = New MyColor("#bf0b0b")
Case Themes.Yellow
Background = New MyColor("#ffebd7")
BorderBrush = New MyColor("#f57a00")
LabText.Foreground = New MyColor("#d86c00")
BtnClose.Foreground = New MyColor("#d86c00")
End Select
End Sub
<Obsolete("IsWarn 已过时。请换用 Theme 属性。")>
Public Property IsWarn As Boolean
Get
Return Theme = Themes.Red
End Get
Set(value As Boolean)
Theme = If(value, Themes.Red, Themes.Blue)
End Set
End Property
Public Shared ReadOnly IsWarnProperty As DependencyProperty =
DependencyProperty.Register("IsWarn", GetType(Boolean), GetType(MyHint), New PropertyMetadata(True,
Sub(d As MyHint, e As DependencyPropertyChangedEventArgs)
d.Theme = If(e.NewValue, Themes.Red, Themes.Blue)
End Sub))
'文本
Public ReadOnly Property Inlines As InlineCollection
Get
Return LabText.Inlines
@@ -52,6 +88,7 @@ Public Class MyHint
d.LabText.Text = e.NewValue
End Sub))
'关闭按钮
Public Property CanClose As Boolean
Get
Return BtnClose.Visibility = Visibility.Visible
@@ -60,26 +97,26 @@ Public Class MyHint
BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed)
End Set
End Property
Public Property RelativeSetup As String = ""
Private Sub MyHint_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
If CanClose AndAlso Setup.Get(RelativeSetup) Then
Visibility = Visibility.Collapsed
End If
If CanClose AndAlso Settings.Get(Of Boolean)(RelativeSetup) Then Visibility = Visibility.Collapsed
End Sub
Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click
Setup.Set(RelativeSetup, True)
Settings.SetSafe(RelativeSetup, True)
AniDispose(Me, False)
End Sub
'触发点击事件
Public Event Click(sender As Object, e As MouseButtonEventArgs)
Private IsMouseDown As Boolean = False
Private Sub MyHint_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Return
IsMouseDown = False
Log("[Control] 按下提示条" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
Logger.Info($"按下提示条{If(String.IsNullOrEmpty(Name), "", ":" & Name)}")
RaiseEvent Click(sender, e)
If e.Handled Then Return
e.Handled = True
ModEvent.TryStartEvent(EventType, EventData)
RaiseCustomEvent() '自定义事件
End Sub
Private Sub MyHint_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
IsMouseDown = True
@@ -87,42 +124,25 @@ Public Class MyHint
Private Sub MyHint_MouseLeave() Handles Me.MouseLeave
IsMouseDown = False
End Sub
Public Property EventType As String
Get
Return GetValue(EventTypeProperty)
End Get
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing))
End Class
Partial Public Module ModAnimation
Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
If Not Control.IsHitTestVisible Then Exit Sub
If Not Control.IsHitTestVisible Then Return
Control.IsHitTestVisible = False
AniStart({
AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
AaCode(Sub()
If RemoveFromChildren Then
CType(Control.Parent, Object).Children.Remove(Control)
Else
Control.Visibility = Visibility.Collapsed
End If
If CallBack IsNot Nothing Then CallBack(Control)
End Sub,, True)
}, "MyCard Dispose " & Control.Uuid)
AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
AaCode(
Sub()
If RemoveFromChildren Then
CType(Control.Parent, Object).Children.Remove(Control)
Else
Control.Visibility = Visibility.Collapsed
End If
If CallBack IsNot Nothing Then CallBack(Control)
End Sub,, True)
}, "MyCard Dispose " & Control.Uuid)
End Sub
End Module
@@ -1,4 +1,4 @@
<Border x:Class="MyIconButton"
<Border x:Class="MyIconButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
CornerRadius="1000" Background="{StaticResource ColorBrushSemiTransparent}" BorderThickness="0.001">
@@ -1,4 +1,6 @@
Public Class MyIconButton
Imports System.Windows.Shapes
Public Class MyIconButton
'自定义事件
Public Event Click(sender As Object, e As EventArgs)
@@ -48,43 +50,27 @@
End Set
End Property
'触发点击事件
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
Private IsMouseDown As Boolean = False
Public Property CanClick As Boolean = True
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
If Not CanClick Then Return
If Not IsMouseDown Then Return
Log("[Control] 按下图标按钮" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
Logger.Info($"按下图标按钮{If(String.IsNullOrEmpty(Name), "", ":" & Name)}")
RaiseEvent Click(sender, e)
e.Handled = True
Button_MouseUp()
ModEvent.TryStartEvent(EventType, EventData)
RaiseCustomEvent() '自定义事件
End Sub
Public Property EventType As String
Get
Return GetValue(EventTypeProperty)
End Get
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing))
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
Private IsMouseDown As Boolean = False
Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
If Not CanClick Then Return
IsMouseDown = True
Focus()
'指向
AniStart(AaScaleTransform(PanBack, 0.8 - CType(PanBack.RenderTransform, ScaleTransform).ScaleX, 400,, New AniEaseOutFluent(AniEasePower.Strong)), "MyIconButton Scale " & Uuid)
End Sub
Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
If Not CanClick Then Return
If IsMouseDown Then
IsMouseDown = False
AniStart({
@@ -95,6 +81,7 @@
RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
Private Sub Button_MouseLeave() Handles Me.MouseLeave
If Not CanClick Then Return
IsMouseDown = False
AniStart({
AaScaleTransform(PanBack, 1 - CType(PanBack.RenderTransform, ScaleTransform).ScaleX, 250,, New AniEaseOutFluent)
@@ -102,14 +89,12 @@
RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
'自定义事件
'务必放在 IsMouseDown 更新之后
Private Const AnimationColorIn As Integer = 120
Private Const AnimationColorOut As Integer = 150
Public Sub RefreshAnim() Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded
Public Sub RefreshAnim() Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded, Me.IsEnabledChanged
Try
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
If PanBack.Background Is Nothing Then PanBack.Background = New MyColor(0, 255, 255, 255)
If Path.Fill Is Nothing Then
Select Case Theme
@@ -121,9 +106,10 @@
Path.Fill = New MyColor(160, Foreground)
End Select
End If
If IsMouseOver Then
'指向
Dim AnimList As New List(Of AniData)
Dim AnimList As New List(Of AniData)
If Not IsEnabled Then
AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrushGray5", AnimationColorIn))
ElseIf IsMouseOver Then '指向
Select Case Theme
Case Themes.Color
AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush2", AnimationColorIn))
@@ -136,10 +122,7 @@
Case Themes.Custom
AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(255, Foreground) - Path.Fill, AnimationColorIn))
End Select
AniStart(AnimList, "MyIconButton Color " & Uuid)
Else
'普通
Dim AnimList As New List(Of AniData)
Else '普通
Select Case Theme
Case Themes.Color
AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush4", AnimationColorOut))
@@ -157,36 +140,37 @@
AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(160, Foreground) - Path.Fill, AnimationColorOut))
PanBack.Background = New MyColor(0, 255, 255, 255)
End Select
AniStart(AnimList, "MyIconButton Color " & Uuid)
End If
AniStart(AnimList, "MyIconButton Color " & Uuid)
Else
AniStop("MyIconButton Color " & Uuid)
Select Case Theme
Case Themes.Color
Path.SetResourceReference(Shape.FillProperty, "ColorBrush5")
Case Themes.White
Path.SetResourceReference(Shape.FillProperty, "ColorBrush8")
Case Themes.Red
Path.Fill = New MyColor(160, 255, 76, 76)
Case Themes.Black
Path.Fill = New MyColor(160, 0, 0, 0)
Case Themes.Custom
Path.Fill = New MyColor(160, Foreground)
End Select
If IsEnabled Then
Select Case Theme
Case Themes.Color
Path.SetResourceReference(Shape.FillProperty, "ColorBrush5")
Case Themes.White
Path.SetResourceReference(Shape.FillProperty, "ColorBrush8")
Case Themes.Red
Path.Fill = New MyColor(160, 255, 76, 76)
Case Themes.Black
Path.Fill = New MyColor(160, 0, 0, 0)
Case Themes.Custom
Path.Fill = New MyColor(160, Foreground)
End Select
Else
Path.SetResourceReference(Shape.FillProperty, "ColorBrushGray5")
End If
PanBack.Background = New MyColor(0, 255, 255, 255)
End If
Catch ex As Exception
Log(ex, "刷新图标按钮动画状态出错")
Logger.Warn(ex, "刷新图标按钮动画状态出错")
End Try
End Sub
End Class
Partial Public Module ModAnimation
Public Sub AniDispose(Control As MyIconButton, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
If Not Control.IsHitTestVisible Then Exit Sub
If Not Control.IsHitTestVisible Then Return
Control.IsHitTestVisible = False
AniStart({
AaScaleTransform(Control, -1.5, 200,, New AniEaseInFluent),
@@ -1,4 +1,4 @@
<Border x:Name="PanBack" x:Class="MyIconTextButton"
<Border x:Name="PanBack" x:Class="MyIconTextButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
@@ -1,4 +1,4 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyIconTextButton
@@ -8,9 +8,6 @@ Public Class MyIconTextButton
Public Uuid As Integer = GetUuid()
Public Event Check(sender As Object, raiseByMouse As Boolean)
Public Event Change(sender As Object, raiseByMouse As Boolean)
Public Sub RaiseChange()
RaiseEvent Change(Me, False)
End Sub '使外部程序可以引发本控件的 Change 事件
'自定义属性
@@ -72,11 +69,11 @@ Public Class MyIconTextButton
Public Event Click(sender As Object, e As RouteEventArgs)
Private IsMouseDown As Boolean = False
Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp
If Not IsMouseDown Then Exit Sub
Log("[Control] 按下带图标按钮:" & Text)
If Not IsMouseDown Then Return
Logger.Info($"按下带图标按钮:{Text}")
IsMouseDown = False
RaiseEvent Click(Me, New RouteEventArgs(True))
ModEvent.TryStartEvent(EventType, EventData)
RaiseCustomEvent() '自定义事件
RefreshColor()
End Sub
Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown
@@ -87,24 +84,6 @@ Public Class MyIconTextButton
IsMouseDown = False
RefreshColor()
End Sub
Public Property EventType As String
Get
Return GetValue(EventTypeProperty)
End Get
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
'动画
@@ -187,7 +166,7 @@ Public Class MyIconTextButton
End If
Catch ex As Exception
Log(ex, "刷新带图标按钮颜色出错")
Logger.Warn(ex, "刷新带图标按钮颜色出错")
End Try
End Sub
+37 -70
View File
@@ -1,4 +1,4 @@
Public Class MyImage
Public Class MyImage
Inherits Image
#Region "公开属性"
@@ -25,7 +25,7 @@
''' <summary>
''' 与 Image 的 Source 类似。
''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示;若 EnableCache 设为 True,图片还会保存为本地缓存。
''' 支持 WebP 格式的图片。
''' </summary>
Public Shadows Property Source As String '覆写 Image 的 Source 属性
@@ -34,9 +34,9 @@
End Get
Set(value As String)
If value = "" Then value = Nothing
If _Source = value Then Exit Property
If _Source = value Then Return
_Source = value
If Not IsInitialized Then Exit Property '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
If Not IsInitialized Then Return '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
Load()
End Set
End Property
@@ -46,7 +46,7 @@
Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
''' <summary>
''' 当 Source 首次下载失败时,会从该备用地址加载图片。
''' 若 Source 是一个网络图片,该地址将作为第二图片源。
''' </summary>
Public Property FallbackSource As String
Get
@@ -82,15 +82,15 @@
End Get
Set(value As String)
If value = "" Then value = Nothing
If _ActualSource = value Then Exit Property
If _ActualSource = value Then Return
_ActualSource = value
Try
Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
RunInUiWait(Sub() MyBase.Source = Bitmap)
Dim Bitmap As ImageSource = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
RunInUi(Sub() MyBase.Source = Bitmap)
Catch ex As Exception
Log(ex, $"加载图片失败({value})")
Logger.Warn(ex, $"加载图片失败({value})")
Try
If value.StartsWithF(PathTemp) AndAlso File.Exists(value) Then File.Delete(value)
If value.StartsWithF(PathTemp, True) Then FileUtils.Delete(value)
Catch
End Try
End Try
@@ -103,87 +103,54 @@
'空
If Source Is Nothing Then
ActualSource = Nothing
Exit Sub
Return
End If
'本地图片
If Not Source.StartsWithF("http") Then
ActualSource = Source
Exit Sub
Return
End If
'从缓存加载网络图片
Dim Url As String = Source
Dim Retried As Boolean = False
Dim TempPath As String = GetTempPath(Url)
Dim TempFile As New FileInfo(TempPath)
Dim EnableCache As Boolean = Me.EnableCache
Dim TempPath As String = $"{PathTemp}MyImage\{Source.GetStableHashCode()}.png{If(EnableCache, "", GetUuid())}" '不启用缓存时在末尾加上随机字符串,避免冲突
Dim TempFile = FileUtils.GetInfo(TempPath)
If EnableCache AndAlso TempFile.Exists Then
ActualSource = TempPath
If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
ActualSource = TempPath '显示缓存的图片
If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存
Else
ActualSource = LoadingSource '显示加载中图片
End If
RunInNewThread(
Sub()
Dim TempDownloadingPath As String = Nothing
Dim IsLocalFallback As Boolean = Not String.IsNullOrEmpty(FallbackSource) AndAlso Not FallbackSource.StartsWithF("http")
Try
RetryStart:
'下载
ActualSource = LoadingSource '显示加载中图片
TempDownloadingPath = TempPath & RandomInteger(0, 10000000)
Directory.CreateDirectory(GetPathFromFullPath(TempPath)) '重新实现下载,以避免携带 Header(#5072)
Using Client As New Net.WebClient
Client.DownloadFile(Url, TempDownloadingPath)
End Using
If Url <> Source AndAlso Url <> FallbackSource Then
'已经更换了地址
File.Delete(TempDownloadingPath)
ElseIf EnableCache Then
'保存缓存并显示
If File.Exists(TempPath) Then File.Delete(TempPath)
FileSystem.Rename(TempDownloadingPath, TempPath)
RunInUi(Sub() ActualSource = TempPath)
Else
'直接显示
RunInUiWait(Sub() ActualSource = TempDownloadingPath)
File.Delete(TempDownloadingPath)
End If
NetDownloadByLoader(
If(FallbackSource IsNot Nothing AndAlso FallbackSource.StartsWithF("http") AndAlso Source <> FallbackSource,
{Source, FallbackSource}, {Source}),
TempPath, SimulateBrowserHeaders:=True)
ActualSource = TempPath
If Not EnableCache Then FileUtils.Delete(TempPath)
Catch ex As Exception
Try
If TempPath IsNot Nothing Then File.Delete(TempPath)
If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath)
If TempPath IsNot Nothing Then FileUtils.Delete(TempPath)
Catch
End Try
If Not Retried Then
'更换备用地址
Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer)
Retried = True
Url = If(FallbackSource, Source)
'空
If Url Is Nothing Then
ActualSource = Nothing
Exit Sub
End If
'本地图片
If Not Url.StartsWithF("http") Then
ActualSource = Url
Exit Sub
End If
'从缓存加载网络图片
TempPath = GetTempPath(Url)
TempFile = New FileInfo(TempPath)
If EnableCache AndAlso TempFile.Exists() Then
ActualSource = TempPath
If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
End If
'下载
If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试
GoTo RetryStart
'加载本地备用图片
If IsLocalFallback Then
Try
ActualSource = FallbackSource
Logger.Warn(ex, $"下载图片失败,使用本地备用图片({Source})")
Return
Catch exx As Exception
Logger.Error(ex, $"下载图片失败({Source})", LogBehavior.Toast)
Logger.Error(exx, $"加载备用图片失败({FallbackSource})", LogBehavior.Toast)
End Try
Else
Log(ex, $"下载图片失败({Url})", LogLevel.Hint)
Logger.Error(ex, $"下载图片失败({Source},备用:{FallbackSource})", LogBehavior.Toast)
End If
End Try
End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
End Sub
Public Shared Function GetTempPath(Url As String) As String
Return $"{PathTemp}MyImage\{GetHash(Url)}.png"
End Function
End Class
@@ -1,4 +1,4 @@
<Grid x:Class="MyListItem"
<Grid x:Class="MyListItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="PanBack"
@@ -1,4 +1,4 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyListItem
@@ -174,7 +174,7 @@ Public Class MyListItem
Return GetValue(TitleProperty)
End Get
Set(value As String)
SetValue(TitleProperty, value.Replace(vbCr, "").Replace(vbLf, ""))
SetValue(TitleProperty, value.ReplaceLineEndings(""))
End Set
End Property
Public Shared ReadOnly TitleProperty As DependencyProperty = DependencyProperty.Register("Title", GetType(String), GetType(MyListItem))
@@ -197,8 +197,8 @@ Public Class MyListItem
Return _Info
End Get
Set(value As String)
If _Info = value Then Exit Property
value = value.Replace(vbCr, "").Replace(vbLf, "")
If _Info = value Then Return
value = value.ReplaceLineEndings("")
_Info = value
LabInfo.Text = value
LabInfo.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
@@ -212,7 +212,7 @@ Public Class MyListItem
Return _Logo
End Get
Set(value As String)
If _Logo = value Then Exit Property
If _Logo = value Then Return
_Logo = value
'删除旧 Logo
If Not IsNothing(PathLogo) Then Children.Remove(PathLogo)
@@ -297,7 +297,7 @@ Public Class MyListItem
Return _Type
End Get
Set(value As CheckType)
If _Type = value Then Exit Property
If _Type = value Then Return
_Type = value
'切换左栏大小
ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
@@ -312,7 +312,8 @@ Public Class MyListItem
Else
'添加竖条控件
If IsNothing(RectCheck) Then
RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
RectCheck = New Border With {
.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
.VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center),
.HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
.Margin = If(Checked, New Thickness(-1, 6, 0, 6), New Thickness(-1, 0, 0, 0))}
@@ -345,7 +346,7 @@ Public Class MyListItem
Return _Checked
End Get
Set(value As Boolean)
SetChecked(value, False, True)
SetChecked(value, False, value <> _Checked) '仅在值发生变化时触发动画 (#4596)
End Set
End Property
''' <summary>
@@ -367,35 +368,36 @@ Public Class MyListItem
RaiseEvent Changed(Me, ChangedEventArgs)
If ChangedEventArgs.Handled Then
_Checked = RawValue
Exit Sub
Return
End If
End If
_Checked = value
Else
If value = _Checked Then Exit Sub
If value = _Checked Then Return
_Checked = value
If IsInitialized Then
RaiseEvent Changed(Me, ChangedEventArgs)
If ChangedEventArgs.Handled Then
_Checked = RawValue
Exit Sub
Return
End If
End If
End If
If value Then
Dim CheckEventArgs As New RouteEventArgs(user)
RaiseEvent Check(Me, CheckEventArgs)
If CheckEventArgs.Handled Then Exit Sub
If CheckEventArgs.Handled Then Return
End If
'保证只有一个单选 ListItem 选中
If Type = CheckType.RadioBox Then
If IsNothing(Parent) Then Exit Sub
If IsNothing(Parent) Then Return
Dim RadioboxList As New List(Of MyListItem)
Dim CheckedCount As Integer = 0
'收集控件列表与选中个数
For Each Control In CType(Parent, Object).Children
Control = MyVirtualizingElement.TryInit(Control)
If TypeOf Control Is MyListItem AndAlso CType(Control, MyListItem).Type = CheckType.RadioBox Then
RadioboxList.Add(Control)
If Control.Checked Then CheckedCount += 1
@@ -478,7 +480,7 @@ Public Class MyListItem
End If
Catch ex As Exception
Log(ex, "设置 Checked 失败")
Logger.Warn(ex, "设置 Checked 失败")
End Try
End Sub
@@ -502,24 +504,25 @@ Public Class MyListItem
'触发点击事件
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
If Not IsMouseDown Then Exit Sub
If Not IsMouseDown Then Return
RaiseEvent Click(sender, e)
If e.Handled Then Exit Sub
If e.Handled Then Return
'触发自定义事件
If Not String.IsNullOrEmpty(EventType) Then
ModEvent.TryStartEvent(EventType, EventData)
If CustomEventService.GetEventType(sender) <> CustomEvent.EventType.None OrElse
CustomEventService.GetEvents(sender).Events.Count > 0 Then
RaiseCustomEvent()
e.Handled = True
End If
If e.Handled Then Exit Sub
If e.Handled Then Return
'实际的单击处理
Select Case Type
Case CheckType.Clickable
Log("[Control] 按下单击列表项:" & Title)
Logger.Info($"按下单击列表项:{Title}")
Case CheckType.RadioBox
Log("[Control] 按下单选列表项:" & Title)
Logger.Info($"按下单选列表项:{Title}")
If Not Checked Then SetChecked(True, True, True)
Case CheckType.CheckBox
Log("[Control] 按下复选列表项(" & (Not Checked).ToString & "):" & Title)
Logger.Info($"按下复选列表项({Not Checked}):{Title}")
SetChecked(Not Checked, True, True)
End Select
End Sub
@@ -537,34 +540,14 @@ Public Class MyListItem
If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
End Sub
'实现自定义事件
Public Property EventType As String
Get
Return GetValue(EventTypeProperty)
End Get
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing))
#End Region
Private StateLast As String
Public IsMouseOverAnimationEnabled As Boolean = True
Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.PreviewMouseLeftButtonDown, Me.PreviewMouseLeftButtonUp
'菜单虚拟化检测
If ContentHandler IsNot Nothing Then
ContentHandler(sender, e)
ContentHandler(Me, e)
ContentHandler = Nothing
End If
'判断当前颜色
@@ -581,7 +564,7 @@ Public Class MyListItem
Time = 180
End If
End If
If StateLast = StateNew Then Exit Sub
If StateLast = StateNew Then Return
StateLast = StateNew
'触发颜色动画
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
@@ -657,13 +640,14 @@ Public Class MyListItem
SetResourceReference(ForegroundProperty, "ColorBrush1")
End If
ColumnPaddingRight.Width = New GridLength(MinPaddingRight)
If EventType = "打开帮助" AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266
If CustomEventService.GetEventType(Me) = CustomEvent.EventType.打开帮助 AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266
Try
Dim Unused = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me)
Dim Entry As New HelpEntry(CustomEvent.GetAbsoluteUrls(CustomEventService.GetEventData(Me), CustomEventService.GetEventType(Me))(0))
Entry.SetToListItem(Me)
Catch ex As Exception
Log(ex, "设置帮助 MyListItem 失败", LogLevel.Msgbox)
EventType = Nothing
EventData = Nothing
Logger.Error(ex, "设置帮助 MyListItem 失败", LogBehavior.Alert)
CustomEventService.SetEventType(Me, CustomEvent.EventType.None)
CustomEventService.SetEventData(Me, "")
End Try
End If
End Sub
@@ -1,4 +1,4 @@
<Grid
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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" x:Name="PanBack" mc:Ignorable="d" x:Class="MyLoading"
MinWidth="50" MinHeight="50" Background="{StaticResource ColorBrushSemiTransparent}">
@@ -1,4 +1,4 @@
Imports PCL.MyLoading
Imports PCL.MyLoading
Public Class MyLoading
@@ -35,7 +35,7 @@ Public Class MyLoading
Return _ShowProgress
End Get
Set(value As Boolean)
If _ShowProgress = value Then Exit Property
If _ShowProgress = value Then Return
_ShowProgress = value
RefreshText()
End Set
@@ -76,14 +76,7 @@ Public Class MyLoading
If Ex Is Nothing Then
LabText.Text = "未知错误"
Else
Do While Ex.InnerException IsNot Nothing
Ex = Ex.InnerException
Loop
LabText.Text = StrTrim(Ex.Message)
If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
"操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then
LabText.Text = "网络环境不佳,请重试或尝试使用 VPN"
End If
LabText.Text = Ex.GetDisplay(False)
End If
Else
LabText.Text = TextError
@@ -142,7 +135,7 @@ Public Class MyLoading
Return _OuterState
End Get
Set(value As MyLoadingState)
If _OuterState = value Then Exit Property
If _OuterState = value Then Return
Dim OldValue = _OuterState
_OuterState = value
'引发事件
@@ -159,7 +152,7 @@ Public Class MyLoading
Return _InnerState
End Get
Set(value As MyLoadingState)
If _InnerState = value Then Exit Property
If _InnerState = value Then Return
Dim OldValue = _InnerState
_InnerState = value
'引发事件
@@ -183,34 +176,33 @@ Public Class MyLoading
Private IsLooping As Boolean = False
Private Sub AniLoop()
'这坨循环代码也是老屎坑了,救救.jpg
If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub
If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Return
IsLooping = True
ErrorAnimationWaiting = True
AniStart({
AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)),
AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True),
AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)),
AaCode(Sub()
PathLeft.Opacity = 1
PathLeft.Margin = New Thickness(7, 41, 0, 0)
PathRight.Opacity = 1
PathRight.Margin = New Thickness(14, 41, 0, 0)
ErrorAnimationWaiting = False
End Sub),
AaOpacity(PathLeft, -1, 100, 50),
AaX(PathLeft, -5, 180,, New AniEaseOutFluent),
AaY(PathLeft, -6, 180,, New AniEaseOutFluent),
AaOpacity(PathRight, -1, 100, 50),
AaX(PathRight, 5, 180,, New AniEaseOutFluent),
AaY(PathRight, -6, 180,, New AniEaseOutFluent),
AaCode(Sub()
IsLooping = False
AniLoop()
End Sub,, True)
}, "MyLoader Loop " & Uuid & "/" & GetUuid())
If ShowProgress Then
End If
AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)),
AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True),
AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)),
AaCode(
Sub()
PathLeft.Opacity = 1
PathLeft.Margin = New Thickness(7, 41, 0, 0)
PathRight.Opacity = 1
PathRight.Margin = New Thickness(14, 41, 0, 0)
ErrorAnimationWaiting = False
End Sub),
AaOpacity(PathLeft, -1, 100, 50),
AaX(PathLeft, -5, 180,, New AniEaseOutFluent),
AaY(PathLeft, -6, 180,, New AniEaseOutFluent),
AaOpacity(PathRight, -1, 100, 50),
AaX(PathRight, 5, 180,, New AniEaseOutFluent),
AaY(PathRight, -6, 180,, New AniEaseOutFluent),
AaCode(
Sub()
IsLooping = False
AniLoop()
End Sub,, True)
}, "MyLoader Loop " & Uuid & "/" & GetUuid())
End Sub
''' <summary>
@@ -271,7 +263,7 @@ Public Class MyLoadingStateSimulator
Return _LoadingState
End Get
Set(value As MyLoadingState)
If _LoadingState = value Then Exit Property
If _LoadingState = value Then Return
Dim OldState = _LoadingState
_LoadingState = value
RaiseEvent LoadingStateChanged(value, OldState)
@@ -1,4 +1,4 @@
Public Class MyMenuItem
Public Class MyMenuItem
Inherits MenuItem
Private Sub MyMenuItem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
@@ -7,7 +7,9 @@
If IconControl IsNot Nothing Then IconControl.Data = (New GeometryConverter).ConvertFromString(Icon)
End If
'对父级设置透明度
CType(Parent, ContextMenu).Opacity = Setup.Get("UiLauncherTransparent") / 1000 + 0.4
If TypeOf Parent Is ContextMenu Then
CType(Parent, ContextMenu).Opacity = Settings.Get(Of Integer)("UiLauncherTransparent") / 1000 + 0.4
End If
End Sub
'基础
@@ -37,7 +39,7 @@
Time = AnimationTimeOut
End If
'重复性验证
If ColorName = BackName Then Exit Sub
If ColorName = BackName Then Return
ColorName = BackName
'触发颜色动画
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
@@ -54,4 +56,7 @@
End If
End Sub
Private Sub MyMenuItem_Click(sender As Object, e As RoutedEventArgs) Handles Me.Click
RaiseCustomEvent()
End Sub
End Class
@@ -1,4 +1,4 @@
<Grid x:Class="MyMsgInput"
<Grid x:Class="MyMsgInput"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:PCL"
@@ -1,4 +1,4 @@
Public Class MyMsgInput
Public Class MyMsgInput
Private ReadOnly MyConverter As MyMsgBoxConverter
Private ReadOnly Uuid As Integer = GetUuid()
@@ -26,7 +26,7 @@
ShapeLine.StrokeThickness = GetWPFSize(1)
Catch ex As Exception
Log(ex, "输入弹窗初始化失败", LogLevel.Hint)
Logger.Error(ex, "输入弹窗初始化失败", LogBehavior.Toast)
End Try
End Sub
@@ -46,10 +46,10 @@
AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
}, "MyMsgBox " & Uuid)
'记录日志
Log("[Control] 输入弹窗:" & LabTitle.Text)
Logger.Info($"输入弹窗:{LabTitle.Text}")
Catch ex As Exception
Log(ex, "输入弹窗加载失败", LogLevel.Hint)
Logger.Error(ex, "输入弹窗加载失败", LogBehavior.Toast)
End Try
End Sub
Private Sub Close()
@@ -72,13 +72,14 @@
End Sub
Public Sub Btn1_Click() Handles Btn1.Click
If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Exit Sub
TextArea.Validate() '#5773
If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Return
MyConverter.IsExited = True
MyConverter.Result = TextArea.Text
Close()
End Sub
Public Sub Btn2_Click() Handles Btn2.Click
If MyConverter.IsExited Then Exit Sub
If MyConverter.IsExited Then Return
MyConverter.IsExited = True
MyConverter.Result = Nothing
Close()
@@ -1,4 +1,4 @@
<Grid x:Class="MyMsgSelect"
<Grid x:Class="MyMsgSelect"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:PCL"
@@ -1,4 +1,4 @@
Public Class MyMsgSelect
Public Class MyMsgSelect
Private ReadOnly MyConverter As MyMsgBoxConverter
Private ReadOnly Uuid As Integer = GetUuid()
@@ -21,7 +21,9 @@
ShapeLine.StrokeThickness = GetWPFSize(1)
'添加选择控件
Btn1.IsEnabled = False
For Each Selection As IMyRadio In Converter.Content
For Each Content In Converter.Content
Content = MyVirtualizingElement.TryInit(Content)
Dim Selection As IMyRadio = Content
PanSelection.Children.Add(Selection)
AddHandler Selection.Check, AddressOf OnChecked
If TypeOf Selection Is MyListItem Then
@@ -33,7 +35,7 @@
Next
Catch ex As Exception
Log(ex, "选择弹窗初始化失败", LogLevel.Hint)
Logger.Error(ex, "选择弹窗初始化失败", LogBehavior.Toast)
End Try
End Sub
@@ -51,10 +53,10 @@
AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
}, "MyMsgBox " & Uuid)
'记录日志
Log("[Control] 选择弹窗:" & LabTitle.Text)
Logger.Info($"选择弹窗:{LabTitle.Text}")
Catch ex As Exception
Log(ex, "选择弹窗加载失败", LogLevel.Hint)
Logger.Error(ex, "选择弹窗加载失败", LogBehavior.Toast)
End Try
End Sub
Private Sub Close()
@@ -77,13 +79,13 @@
End Sub
Public Sub Btn1_Click() Handles Btn1.Click
If MyConverter.IsExited OrElse SelectedIndex = -1 Then Exit Sub
If MyConverter.IsExited OrElse SelectedIndex = -1 Then Return
MyConverter.IsExited = True
MyConverter.Result = SelectedIndex
Close()
End Sub
Public Sub Btn2_Click() Handles Btn2.Click
If MyConverter.IsExited Then Exit Sub
If MyConverter.IsExited Then Return
MyConverter.IsExited = True
MyConverter.Result = Nothing
Close()
@@ -1,4 +1,4 @@
<Grid x:Class="MyMsgText"
<Grid x:Class="MyMsgText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:PCL"
@@ -1,4 +1,4 @@
Public Class MyMsgText
Public Class MyMsgText
Private ReadOnly MyConverter As MyMsgBoxConverter
Private ReadOnly Uuid As Integer = GetUuid()
@@ -25,7 +25,7 @@
ShapeLine.StrokeThickness = GetWPFSize(1)
Catch ex As Exception
Log(ex, "普通弹窗初始化失败", LogLevel.Hint)
Logger.Error(ex, "普通弹窗初始化失败", LogBehavior.Toast)
End Try
End Sub
@@ -44,10 +44,10 @@
AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
}, "MyMsgBox " & Uuid)
'记录日志
Log("[Control] 普通弹窗:" & LabTitle.Text & vbCrLf & LabCaption.Text)
Logger.Info($"普通弹窗:{LabTitle.Text}{vbCrLf}{LabCaption.Text}")
Catch ex As Exception
Log(ex, "普通弹窗加载失败", LogLevel.Hint)
Logger.Error(ex, "普通弹窗加载失败", LogBehavior.Toast)
End Try
End Sub
Private Sub Close()
@@ -70,7 +70,7 @@
End Sub
Public Sub Btn1_Click() Handles Btn1.Click
If MyConverter.IsExited Then Exit Sub
If MyConverter.IsExited Then Return
If MyConverter.Button1Action IsNot Nothing Then
MyConverter.Button1Action()
Else
@@ -80,7 +80,7 @@
End If
End Sub
Public Sub Btn2_Click() Handles Btn2.Click
If MyConverter.IsExited Then Exit Sub
If MyConverter.IsExited Then Return
If MyConverter.Button2Action IsNot Nothing Then
MyConverter.Button2Action()
Else
@@ -90,7 +90,7 @@
End If
End Sub
Public Sub Btn3_Click() Handles Btn3.Click
If MyConverter.IsExited Then Exit Sub
If MyConverter.IsExited Then Return
If MyConverter.Button3Action IsNot Nothing Then
MyConverter.Button3Action()
Else
+18 -20
View File
@@ -1,17 +1,17 @@
Public Class MyPageLeft
Public Class MyPageLeft
Inherits Grid
Private Uuid As Integer = GetUuid()
'执行逐个进入动画的控件
Public Property AnimatedControl As FrameworkElement
Public Property AnimatedControl As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
Get
Return GetValue(AnimatedControlProperty)
End Get
Set(value As FrameworkElement)
Set(value As String)
SetValue(AnimatedControlProperty, value)
End Set
End Property
Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(FrameworkElement), GetType(MyPageLeft), New PropertyMetadata(Nothing))
Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing))
Public Sub TriggerShowAnimation()
If AnimatedControl Is Nothing Then
@@ -30,6 +30,7 @@
Dim AniList As New List(Of AniData)
Dim Id As Integer = 0, Delay As Integer = 0
For Each Element As FrameworkElement In GetAllAnimControls(True)
Element = MyVirtualizingElement.TryInit(Element)
If Element.Visibility = Visibility.Collapsed Then
'还原之前的隐藏动画可能导致的改变(#2436)
Element.Opacity = 1
@@ -39,16 +40,17 @@
Element.Opacity = 0
Element.RenderTransform = New TranslateTransform(-25, 0)
If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = False
AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 200, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent))
AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak)))
If TypeOf Element Is MyListItem Then
AniList.Add(AaCode(Sub()
CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
End Sub, Delay + 280))
AniList.Add(AaCode(
Sub()
CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
End Sub, Delay + 280))
End If
Delay += Math.Max(8, 20 - Id) * 2.5
Delay += Math.Max(15 - Id, 7) * 2
Id += 1
End If
Next
@@ -63,8 +65,8 @@
RenderTransformOrigin = New Point(0.5, 0.5)
End If
AniStart({
AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 130,, New AniEaseInFluent(AniEasePower.Weak)),
AaOpacity(Me, -Opacity, 100, 30)
AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 110,, New AniEaseInFluent(AniEasePower.Weak)),
AaOpacity(Me, -Opacity, 80, 30)
}, "PageLeft PageChange " & Uuid)
Else
'逐个退出动画
@@ -72,8 +74,8 @@
Dim Id As Integer = 0
Dim Controls = GetAllAnimControls()
For Each Element As FrameworkElement In Controls
AniList.Add(AaOpacity(Element, -Element.Opacity, 60, 80 / Controls.Count * Id))
AniList.Add(AaTranslateX(Element, -6, 60, 80 / Controls.Count * Id))
AniList.Add(AaOpacity(Element, -Element.Opacity, 50, 70 / Controls.Count * Id))
AniList.Add(AaTranslateX(Element, -6, 50, 70 / Controls.Count * Id))
Id += 1
Next
AniStart(AniList, "PageLeft PageChange " & Uuid)
@@ -83,11 +85,11 @@
'遍历获取所有需要生成动画的控件
Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
Dim AllControls As New List(Of FrameworkElement)
GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility)
GetAllAnimControls(FindName(AnimatedControl), AllControls, IgnoreInvisibility)
Return AllControls
End Function
Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
If TypeOf Element Is MyTextButton Then
AllControls.Add(Element)
ElseIf TypeOf Element Is MyListItem Then
@@ -104,7 +106,3 @@
End Sub
End Class
Public Interface IRefreshable
Sub Refresh()
End Interface
+103 -52
View File
@@ -1,18 +1,18 @@
Public Class MyPageRight
Public Class MyPageRight
Inherits AdornerDecorator
Public PageUuid As Integer = GetUuid()
'“返回顶部” 按钮检测的滚动区域
Public Property PanScroll As MyScrollViewer
Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
Get
Return GetValue(PanScrollProperty)
End Get
Set(value As MyScrollViewer)
Set(value As String)
SetValue(PanScrollProperty, value)
End Set
End Property
Private Shared ReadOnly PanScrollProperty =
DependencyProperty.Register("PanScroll", GetType(MyScrollViewer), GetType(MyPageRight), New PropertyMetadata(Nothing))
DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing))
'当前状态
Public Enum PageStates
@@ -33,9 +33,9 @@
Return _PageState
End Get
Set(value As PageStates)
If _PageState = value Then Exit Property
If _PageState = value Then Return
_PageState = value
If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
Logger.Trace($"页面状态切换为 {value}")
End Set
End Property
@@ -59,9 +59,9 @@
''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
''' <param name="RealLoader">在工作线程执行的加载器。</param>
''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
Public Sub PageLoaderInit(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
RealLoader As LoaderBase,
Optional FinishedInvoke As Action(Of LoaderBase) = Nothing, Optional InputInvoke As Func(Of Object) = Nothing,
Public Sub PageLoaderInit(Of TIn, TOut)(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
RealLoader As LoaderTask(Of TIn, TOut),
Optional FinishedInvoke As Action(Of LoaderTask(Of TIn, TOut)) = Nothing, Optional InputInvoke As Func(Of TIn) = Nothing,
Optional AutoRun As Boolean = True)
'初始化参数
Me.PanLoader = PanLoader
@@ -89,13 +89,59 @@
If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
'初次运行加载器
If PageLoaderAutoRun Then
If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
PageLoader.Start(CType(PageLoader, Object).StartGetInput(Nothing, PageLoaderInputInvoke))
Else
Dim Input = Nothing
If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
PageLoader.Start(Input)
End If
'上下两个重载只有这里不一样
PageLoader.Start(RealLoader.StartGetInput(Nothing, PageLoaderInputInvoke))
End If
If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
End If
'设置加载环
PageLoaderUi.State = RealLoader
AddHandler PageLoaderUi.Click, Sub() If RealLoader.State = LoadState.Failed Then PageLoaderRestart() '点击重试事件
End Sub
''' <summary>
''' 表明页面存在需要在后台执行的加载器。
''' </summary>
''' <param name="LoaderUi">MyLoading 控件。</param>
''' <param name="PanLoader">MyLoading 控件对应的卡片。</param>
''' <param name="PanContent">加载结束后出现的内容容器。</param>
''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
''' <param name="RealLoader">在工作线程执行的加载器。</param>
''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
Public Sub PageLoaderInit(Of TIn)(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
RealLoader As LoaderCombo(Of TIn),
Optional FinishedInvoke As Action(Of LoaderCombo(Of TIn)) = Nothing, Optional InputInvoke As Func(Of TIn) = Nothing,
Optional AutoRun As Boolean = True)
'初始化参数
Me.PanLoader = PanLoader
Me.PanContent = PanContent
Me.PanAlways = PanAlways
Me.PageLoader = RealLoader
Me.PageLoaderUi = LoaderUi
Me.PageLoaderInputInvoke = InputInvoke
Me.PageLoaderAutoRun = AutoRun
'添加结束 Invoke
If FinishedInvoke IsNot Nothing Then
AddHandler RealLoader.PreviewFinish,
Sub()
Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ContentExit
Thread.Sleep(10) '不在退出动画时执行 UI 线程操作,避免退出动画被重置
Loop
RunInUiWait(Sub() FinishedInvoke(RealLoader))
Thread.Sleep(20) '由于大量初始化控件会导致掉帧,延迟触发 State 改变事件
End Sub
End If
AddHandler RealLoader.OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) RunInUi(Sub() PageLoaderState(Loader, NewState, OldState))
'隐藏 UI
PanLoader.Visibility = Visibility.Collapsed
PanContent.Visibility = Visibility.Collapsed
If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
'初次运行加载器
If PageLoaderAutoRun Then
'上下两个重载只有这里不一样
Dim Input = Nothing
If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
PageLoader.Start(Input)
End If
If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
@@ -106,8 +152,8 @@
End Sub
'重试
Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
If Not PageLoaderAutoRun Then Exit Sub
If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
If Not PageLoaderAutoRun Then Return
If GeneralUtils.IsGenericInstanceOf(PageLoader, GetType(LoaderTask(Of ,))) OrElse TypeOf PageLoader Is LoaderWorker Then
PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
Else
If Input Is Nothing AndAlso PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
@@ -125,36 +171,37 @@
''' 需要根据加载器状态,从 Empty 切换到 ContentEnter、LoaderWait、LoaderEnter。
''' </summary>
Public Sub PageOnEnter()
If ModeDebug Then Log("[UI] 已触发 PageOnEnter")
Logger.Trace("已触发 PageOnEnter")
RaiseEvent PageEnter()
Select Case PageState
Case PageStates.Empty
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
'如果加载器在进入页面时不启动(例如 HiPer 联机),那么在此时就会有 State = Waiting
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Canceled Then
'如果加载器在进入页面时不启动(例如联机),那么在此时就会有 State = Waiting
PageState = PageStates.ContentEnter
TriggerEnterAnimation(PanAlways, If(PanContent, Child))
ElseIf PageLoader.State = LoadState.Loading Then
PageState = PageStates.LoaderWait
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid)
Else 'PageLoader.State = LoadState.Failed
PageState = PageStates.LoaderEnter
TriggerEnterAnimation(PanAlways, PanLoader)
End If
Case PageStates.ContentExit
'和上面的一样,但是不管 PanAlways
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Canceled Then
PageState = PageStates.ContentEnter
TriggerEnterAnimation(If(PanContent, Child))
ElseIf PageLoader.State = LoadState.Loading Then
PageState = PageStates.LoaderWait
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid)
Else 'PageLoader.State = LoadState.Failed
PageState = PageStates.LoaderEnter
TriggerEnterAnimation(PanLoader)
End If
Case PageStates.ContentEnter '重复调用 PageOnEnter,直接忽略
Case PageStates.ContentEnter
'重复调用 PageOnEnter,直接忽略
Case Else
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnter 事件。")
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnEnter 事件。")
End Select
End Sub
Public Event PageEnter()
@@ -163,7 +210,7 @@
''' 需要立即切换至 PageExit 或 Empty。
''' </summary>
Public Sub PageOnExit()
If ModeDebug Then Log("[UI] 已触发 PageOnExit")
Logger.Trace("已触发 PageOnExit")
RaiseEvent PageExit()
Select Case PageState
Case PageStates.ContentEnter, PageStates.ContentStay
@@ -187,8 +234,8 @@
''' 需要立即切换至 Empty。
''' </summary>
Public Sub PageOnForceExit()
If PageState = PageStates.Empty Then Exit Sub
If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
If PageState = PageStates.Empty Then Return
Logger.Trace("已触发 PageOnForceExit")
PageState = PageStates.Empty
AniStop("PageRight PageChange " & PageUuid)
'由于动画会被强制中止,所以需要手动进行隐藏
@@ -205,10 +252,10 @@
''' 需要在 PageEnter 事件确认要显示的子页面有哪些。
''' </summary>
Public Sub PageOnContentExit()
If ModeDebug Then Log("[UI] 已触发 PageOnContentExit")
Logger.Trace("已触发 PageOnContentExit")
If PageLoader IsNot Nothing AndAlso PageLoader.State = LoadState.Loading Then
Throw New Exception("在调用 PageOnContentExit 时,加载器不能为 Loading 状态")
'Loading 的加载器可能触发进一步变化,难以预测会触发子页面的动画还是加载器完成的动画
Throw New Exception("在调用 PageOnContentExit 时,加载器不能为 Loading 状态")
End If
Select Case PageState
Case PageStates.ContentEnter, PageStates.ContentStay
@@ -219,8 +266,12 @@
Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
PageState = PageStates.ContentExit
TriggerExitAnimation(PanLoader)
Case PageStates.LoaderWait, PageStates.Empty
Case PageStates.LoaderWait
PageOnEnter()
Case PageStates.Empty
'可能是在离开页面后,其他后台线程调用了本函数,此时不应触发 PageOnEnter()
'例如:在加入联机房间后,切换到其他页面,然后联机房间被解散
If IsLoaded Then PageOnEnter()
End Select
End Sub
@@ -230,7 +281,7 @@
''' 需要根据目前状态,从 ContentEnter 切换到 ContentStay,或从 LoaderEnter 切换到 LoaderStayForce。
''' </summary>
Private Sub PageOnEnterAnimationFinished()
If ModeDebug Then Log("[UI] 已触发 PageOnEnterAnimationFinished")
Logger.Trace("已触发 PageOnEnterAnimationFinished")
Select Case PageState
Case PageStates.ContentEnter
PageState = PageStates.ContentStay
@@ -238,7 +289,7 @@
PageState = PageStates.LoaderStayForce
AniStart(AaCode(AddressOf PageOnLoaderStayFinished, 400), "PageRight PageChange " & PageUuid)
Case Else
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnterAnimationFinished 事件。")
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnEnterAnimationFinished 事件。")
End Select
End Sub
''' <summary>
@@ -246,7 +297,7 @@
''' 需要根据目前状态,从 AllExit 切换到 Empty,或从 LoaderExit 切换到 ContentEnter,或从 ContentExit 重新触发 PageOnEnter。
''' </summary>
Private Sub PageOnExitAnimationFinished()
If ModeDebug Then Log("[UI] 已触发 PageOnExitAnimationFinished")
Logger.Trace("已触发 PageOnExitAnimationFinished")
Select Case PageState
Case PageStates.PageExit
PageState = PageStates.Empty
@@ -256,7 +307,7 @@
PageState = PageStates.ContentEnter
TriggerEnterAnimation(PanContent)
Case Else
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnExitAnimationFinished 事件。")
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnExitAnimationFinished 事件。")
End Select
End Sub
''' <summary>
@@ -264,7 +315,7 @@
''' 需要从 LoaderWait 切换到 LoaderEnter。
''' </summary>
Private Sub PageOnLoaderWaitFinished()
If ModeDebug Then Log("[UI] 已触发 PageOnLoaderWaitFinished")
Logger.Trace("已触发 PageOnLoaderWaitFinished")
Select Case PageState
Case PageStates.LoaderWait
PageState = PageStates.LoaderEnter
@@ -274,7 +325,7 @@
TriggerEnterAnimation(PanLoader)
End If
Case Else
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnLoaderWaitFinished 事件。")
End Select
End Sub
''' <summary>
@@ -282,7 +333,7 @@
''' 需要从 LoaderStayForce 切换到 LoaderStay 或 LoaderExit。
''' </summary>
Private Sub PageOnLoaderStayFinished()
If ModeDebug Then Log("[UI] 已触发 PageOnLoaderStayFinished")
Logger.Trace("已触发 PageOnLoaderStayFinished")
Select Case PageState
Case PageStates.LoaderStayForce
If PageLoader.State = LoadState.Finished Then
@@ -292,7 +343,7 @@
PageState = PageStates.LoaderStay
End If
Case Else
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnLoaderWaitFinished 事件。")
End Select
End Sub
@@ -302,8 +353,8 @@
Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
Select Case NewState
Case LoadState.Failed, LoadState.Loading
If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Exit Sub
If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Return
Logger.Trace("已触发 PageLoaderState (Start/Refresh)")
'(重新)开始运行
'需要从部分状态切换到 ReloadExit
Select Case PageState
@@ -313,9 +364,9 @@
Case PageStates.LoaderExit
PageState = PageStates.ContentExit
End Select
Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Exit Sub
If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
Case LoadState.Finished, LoadState.Canceled, LoadState.Waiting
If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Return
Logger.Trace("已触发 PageLoaderState (Stop/Canceled)")
'运行结束
'需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
Select Case PageState
@@ -358,10 +409,10 @@
Else
Control.Opacity = 0
Control.RenderTransform = New TranslateTransform(0, -16)
AniList.Add(AaOpacity(Control, 1, 150, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
AniList.Add(AaOpacity(Control, 1, 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
Delay += 40
Delay += 25
End If
Next
Next
@@ -373,7 +424,7 @@
End If
'结束
AniList.Add(AaCode(Sub() PageOnEnterAnimationFinished(),, True))
AniStart(AniList, "PageRight PageChange " & PageUuid)
AniStart(AniList, "PageRight PageChange " & PageUuid, True)
End Sub
'逐个退出动画
@@ -387,9 +438,9 @@
CType(Control, MyExtraTextButton).Show = False
Else
Control.IsHitTestVisible = False
AniList.Add(AaOpacity(Control, -1, 90, Delay))
AniList.Add(AaTranslateY(Control, -6, 90, Delay))
Delay += 20
AniList.Add(AaOpacity(Control, -1, 70, Delay))
AniList.Add(AaTranslateY(Control, -6, 70, Delay))
Delay += 15
End If
Next
Next
@@ -423,7 +474,7 @@
Return AllControls.Except(DisabledPageAnimControls)
End Function
Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
AllControls.Add(Element)
ElseIf TypeOf Element Is ContentControl Then
@@ -1,4 +1,4 @@
<Grid x:Class="MyRadioBox"
<Grid x:Class="MyRadioBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FocusVisualStyle="{x:Null}"
@@ -1,8 +1,9 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
Imports System.Windows.Shapes
<ContentProperty("Inlines")>
Public Class MyRadioBox
Implements IMyRadio
Implements IMyRadio, ISettingControl
'基础
@@ -41,20 +42,20 @@ Public Class MyRadioBox
RaiseEvent PreviewCheck(Me, e)
If e.Handled Then
Radiobox_MouseLeave()
Exit Sub
Return
End If
End If
'自定义属性基础
Dim IsChanged As Boolean = False
If IsLoaded AndAlso Not value = Checked Then RaiseEvent PreviewChange(Me, New RouteEventArgs(user))
If Not value = Checked Then
If value <> Checked Then
SetValue(CheckedProperty, value)
IsChanged = True
End If
'保证只有一个单选框选中
If Parent Is Nothing Then Exit Sub
'保证最多只有一个单选框选中
If Parent Is Nothing Then Return
Dim RadioboxList As New List(Of MyRadioBox)
Dim CheckedCount As Integer = 0
For Each Control In CType(Parent, Object).Children '收集控件列表与选中个数
@@ -63,42 +64,38 @@ Public Class MyRadioBox
If Control.Checked Then CheckedCount += 1
End If
Next
Select Case CheckedCount '判断选中情况
Case 0
'没有任何单选框被选中,选择第一个
RadioboxList(0).Checked = True
Case Is > 1
'选中项目多于 1 个
If Checked Then
'如果本控件选中,则取消其他所有控件的选中
For Each Control As MyRadioBox In RadioboxList
If Control.Checked AndAlso Not Control.Equals(Me) Then Control.Checked = False
Next
Else
'如果本控件未选中,则只保留第一个选中的控件
Dim FirstChecked = False
For Each Control As MyRadioBox In RadioboxList
If Control.Checked Then
If FirstChecked Then
Control.Checked = False '修改 Checked 会自动触发 Change 事件,所以不用额外触发
Else
FirstChecked = True
End If
If CheckedCount > 1 Then
If Checked Then
'如果本控件选中,则取消其他所有控件的选中
For Each Control As MyRadioBox In RadioboxList
If Control.Checked AndAlso Not Control.Equals(Me) Then Control.Checked = False
Next
Else
'如果本控件未选中,则只保留第一个选中的控件
Dim FirstChecked = False
For Each Control As MyRadioBox In RadioboxList
If Control.Checked Then
If FirstChecked Then
Control.Checked = False '修改 Checked 会自动触发 Change 事件,所以不用额外触发
Else
FirstChecked = True
End If
Next
End If
End Select
End If
Next
End If
End If
'触发事件
If IsChanged Then
'触发事件
If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
RaiseEvent Changed(Me, New RouteEventArgs(user))
RaiseCustomEvent()
'更改动画
SyncUI()
End If
'更改动画
SyncUI()
Catch ex As Exception
Log(ex, "单选框勾选改变错误", LogLevel.Hint)
Logger.Error(ex, "单选框勾选改变错误", LogBehavior.Toast)
End Try
End Sub
Private Sub SyncUI()
@@ -115,7 +112,7 @@ Public Class MyRadioBox
AaOpacity(ShapeDot, 1 - ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.6)
}, "MyRadioBox Dot " & Uuid)
AniStart({
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush2", "ColorBrushGray4")), AnimationTimeOfCheck)
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush2", "ColorBrushGray5")), AnimationTimeOfCheck)
}, "MyRadioBox BorderColor " & Uuid)
Else
'由有变无
@@ -127,7 +124,7 @@ Public Class MyRadioBox
AaOpacity(ShapeDot, -ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.2)
}, "MyRadioBox Dot " & Uuid)
AniStart({
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush1", "ColorBrushGray4")), AnimationTimeOfCheck)
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush1", "ColorBrushGray5")), AnimationTimeOfCheck)
}, "MyRadioBox BorderColor " & Uuid)
End If
Else
@@ -140,13 +137,13 @@ Public Class MyRadioBox
ShapeDot.Height = 9
ShapeDot.Opacity = 1
ShapeDot.Margin = New Thickness(5.5, 0, 0, 0)
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray5"))
Else
ShapeDot.Width = 0
ShapeDot.Height = 0
ShapeDot.Opacity = 0
ShapeDot.Margin = New Thickness(10, 0, 0, 0)
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
End If
End If
End Sub
@@ -172,8 +169,8 @@ Public Class MyRadioBox
Private MouseDowned As Boolean = False
Private AllowMouseDown As Boolean = True
Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
If Not MouseDowned Then Exit Sub
Log("[Control] 按下单选框:" & Text)
If Not MouseDowned Then Return
Logger.Info($"按下单选框:{Text}")
SetChecked(True, True)
MouseDowned = False
AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
@@ -187,7 +184,7 @@ Public Class MyRadioBox
End If
End Sub
Private Sub Radiobox_MouseLeave() Handles Me.MouseLeave
If Not MouseDowned Then Exit Sub
If Not MouseDowned Then Return
MouseDowned = False
AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
If Not Checked Then
@@ -208,15 +205,15 @@ Public Class MyRadioBox
Radiobox_MouseLeaveAnimation()
Else
'不可用
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, ColorGray4 - ShapeBorder.Stroke, AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, ColorGray4 - LabText.Foreground, AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, ColorGray5 - ShapeBorder.Stroke, AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, ColorGray5 - LabText.Foreground, AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
End If
Else
'无动画
AniStop("MyRadioBox BorderColor " & Uuid)
AniStop("MyRadioBox TextColor " & Uuid)
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"))
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray5"))
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"))
End If
End Sub
Private Sub Radiobox_MouseEnterAnimation() Handles Me.MouseEnter
@@ -224,16 +221,32 @@ Public Class MyRadioBox
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn), "MyRadioBox TextColor " & Uuid)
End Sub
Private Sub Radiobox_MouseLeaveAnimation() Handles Me.MouseLeave
If Not Me.IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
If Not Me.IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
If IsLoaded AndAlso AniControlEnabled = 0 Then
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
Else
AniStop("MyRadioBox BorderColor " & Uuid)
AniStop("MyRadioBox TextColor " & Uuid)
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"))
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"))
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
End If
End Sub
#Region "设置"
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
Checked = NewValue = SettingService.GetValue(Me)
End Sub
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
Return If(Checked, SettingService.GetValue(Me), Nothing)
End Function
Private Sub SaveSetting() Handles Me.Check
SettingService.SaveSetting(Me)
End Sub
#End Region
End Class
@@ -1,4 +1,4 @@
<Border x:Name="PanBack" x:Class="MyRadioButton"
<Border x:Name="PanBack" x:Class="MyRadioButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
@@ -1,4 +1,4 @@
Imports System.Windows.Markup
Imports System.Windows.Markup
<ContentProperty("Inlines")>
Public Class MyRadioButton
@@ -7,10 +7,6 @@ Public Class MyRadioButton
Public Uuid As Integer = GetUuid()
Public Event Check(sender As Object, raiseByMouse As Boolean)
Public Event Change(sender As Object, raiseByMouse As Boolean)
Public Sub RaiseChange()
RaiseEvent Change(Me, False)
End Sub '使外部程序可以引发本控件的 Change 事件
'自定义属性
@@ -46,23 +42,22 @@ Public Class MyRadioButton
''' 手动设置 Checked 属性。
''' </summary>
''' <param name="value">新的 Checked 属性。</param>
''' <param name="user">是否由用户引发。</param>
''' <param name="raiseByMouse">是否由用户引发。</param>
''' <param name="anime">是否执行动画。</param>
Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
Public Sub SetChecked(value As Boolean, raiseByMouse As Boolean, anime As Boolean)
Try
'自定义属性基础
Dim IsChanged As Boolean = False
If IsLoaded AndAlso Not value = _Checked Then RaiseEvent Change(Me, user)
If Not value = _Checked Then
If _Checked <> value Then
_Checked = value
IsChanged = True
End If
'保证只有一个单选框选中
If IsNothing(Parent) Then Exit Sub
If Parent Is Nothing Then Return
Dim RadioboxList As New List(Of MyRadioButton)
Dim CheckedCount As Integer = 0
'收集控件列表与选中个数
@@ -101,14 +96,15 @@ Public Class MyRadioButton
'更改动画
If Not IsChanged Then Exit Sub
If Not IsChanged Then Return
RefreshColor(Nothing, anime)
'触发事件
If Checked Then RaiseEvent Check(Me, user)
If Checked Then RaiseEvent Check(Me, raiseByMouse)
RaiseCustomEvent()
Catch ex As Exception
Log(ex, "单选按钮勾选改变错误", LogLevel.Hint)
Logger.Error(ex, "单选按钮勾选改变错误", LogBehavior.Toast)
End Try
End Sub
@@ -147,17 +143,17 @@ Public Class MyRadioButton
Public Event PreviewClick(sender As Object, e As RouteEventArgs)
Private IsMouseDown As Boolean = False
Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
If Checked Then Exit Sub
If Not IsMouseDown Then Exit Sub
Log("[Control] 按下单选按钮:" & Text)
If Checked Then Return
If Not IsMouseDown Then Return
Logger.Info($"按下单选按钮:{Text}")
IsMouseDown = False
Dim e As New RouteEventArgs(True)
RaiseEvent PreviewClick(Me, e)
If e.Handled Then Exit Sub
If e.Handled Then Return
SetChecked(True, True, True)
End Sub
Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown
If Checked Then Exit Sub
If Checked Then Return
IsMouseDown = True
RefreshColor()
End Sub
@@ -259,7 +255,7 @@ Public Class MyRadioButton
End If
Catch ex As Exception
Log(ex, "刷新单选按钮颜色出错")
Logger.Warn(ex, "刷新单选按钮颜色出错")
End Try
End Sub
+3 -3
View File
@@ -1,4 +1,4 @@
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices
Imports System.Windows.Interop
Imports System.Windows.Threading
@@ -301,7 +301,7 @@ Public Class MyResizer
End Sub
Private Sub updateMouseDown()
Dim flag As Boolean = Mouse.LeftButton = MouseButtonState.Released
Dim flag = (GetAsyncKeyState(&H1) And &H8000) = 0 '调用原生API判断鼠标是否抬起,如果使用WPF的API的话鼠标不在窗口上时不会更新状态 (#5655)
If flag Then
resizeRight = False
resizeLeft = False
@@ -311,5 +311,5 @@ Public Class MyResizer
End Sub
Private Declare Function GetCursorPos Lib "user32.dll" (<Out()> ByRef lpPoint As PointAPI) As Boolean
Private Declare Function GetAsyncKeyState Lib "user32.dll" (vKey As Integer) As Short
End Class
@@ -1,4 +1,4 @@
Public Class MyScrollBar
Public Class MyScrollBar
Inherits Primitives.ScrollBar
'基础
@@ -44,7 +44,7 @@
End If
Catch ex As Exception
Log(ex, "滚动条颜色改变出错")
Logger.Warn(ex, "滚动条颜色改变出错")
End Try
End Sub
@@ -1,34 +1,29 @@
Public Class MyScrollViewer
Public Class MyScrollViewer
Inherits ScrollViewer
Public Property DeltaMult As Double = 1
Private RealOffset As Double
Private Sub MyScrollViewer_PreviewMouseWheel(sender As Object, e As MouseWheelEventArgs) Handles Me.PreviewMouseWheel
If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Exit Sub
If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Return
Dim SourceType = e.Source.GetType
If Content.TemplatedParent Is Nothing AndAlso (
(GetType(ComboBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, ComboBox).IsDropDownOpen) OrElse
(GetType(TextBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, TextBox).AcceptsReturn) OrElse
GetType(ComboBoxItem).IsAssignableFrom(SourceType) OrElse
TypeOf e.Source Is CheckBox
) Then
(TypeOf e.Source Is Visual AndAlso
PresentationSource.FromVisual(e.Source)?.RootVisual?.GetType().FullName = "System.Windows.Controls.Primitives.PopupRoot")) Then
'如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作
Exit Sub
Return
End If
e.Handled = True
PerformVerticalOffsetDelta(-e.Delta)
'关闭 Tooltip (#2552)
For Each TooltipBorder In Application.ShowingTooltips
AniStart(AaOpacity(TooltipBorder, -1, 100), $"Hide Tooltip {GetUuid()}")
Next
End Sub
Public Sub PerformVerticalOffsetDelta(Delta As Double)
AniStart(
AaDouble(
Sub(AnimDelta As Double)
RealOffset = MathClamp(RealOffset + AnimDelta, 0, ExtentHeight - ActualHeight)
RealOffset = (RealOffset + AnimDelta).Clamp(0, ExtentHeight - ActualHeight)
ScrollToVerticalOffset(RealOffset)
End Sub, Delta * DeltaMult, 300,, New AniEaseOutFluent(6)))
End Sub
@@ -1,4 +1,4 @@
<local:MyCard x:Class="MySearchBox"
<local:MyCard x:Class="MySearchBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -1,4 +1,4 @@
Class MySearchBox
Class MySearchBox
Inherits MyCard
Public Event TextChanged(sender As Object, e As EventArgs)
@@ -39,4 +39,7 @@
TextBox.Focus()
End Sub
Private Sub MySearchBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
If e.Key = Key.Enter Then RaiseCustomEvent()
End Sub
End Class
@@ -1,4 +1,4 @@
<Border x:Class="MySlider"
<Border x:Class="MySlider"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -1,4 +1,7 @@
Public Class MySlider
Imports System.Windows.Shapes
Public Class MySlider
Implements ISettingControl
'基础
@@ -14,7 +17,7 @@
Return _MaxValue
End Get
Set(value As Integer)
If value = _MaxValue Then Exit Property
If value = _MaxValue Then Return
_MaxValue = value
RefreshWidth(Nothing, Nothing)
End Set
@@ -28,8 +31,8 @@
Set(newValue As Integer)
Try
newValue = MathClamp(newValue, 0, MaxValue)
If _Value = newValue Then Exit Property
newValue = newValue.Clamp(0, MaxValue)
If _Value = newValue Then Return
'触发 Preview 事件,修改新值
Dim OldValue = _Value
@@ -40,12 +43,12 @@
If e.Handled Then
_Value = OldValue
DragStop()
Exit Property
Return
End If
End If
If IsLoaded AndAlso AniControlEnabled = 0 Then
If ActualWidth < ShapeDot.Width Then Exit Property
If ActualWidth < ShapeDot.Width Then Return
Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width)
Dim DeltaProcess As Double = Math.Abs(LineFore.Width / (ActualWidth - ShapeDot.Width) - _Value / MaxValue)
Dim Time As Double = (1 - Math.Pow(1 - DeltaProcess, 3)) * 300 + If(ChangeByKey, 100, 0)
@@ -60,7 +63,7 @@
If AniControlEnabled = 0 Then RaiseEvent Change(Me, False)
Catch ex As Exception
Log(ex, "滑动条进度改变出错", LogLevel.Hint)
Logger.Error(ex, "滑动条进度改变出错", LogBehavior.Toast)
End Try
End Set
End Property
@@ -70,7 +73,7 @@
Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width)
LineFore.Width = Math.Max(0, NewWidth + If(NewWidth < 0.5, 0, 0.5))
LineBack.Width = Math.Max(0, ActualWidth - ShapeDot.Width - NewWidth + If(ActualWidth - ShapeDot.Width - NewWidth < 0.5, 0, 0.5))
SetLeft(ShapeDot, NewWidth)
ShapeDot.Margin = New Thickness(NewWidth, ShapeDot.Margin.Top, ShapeDot.Margin.Right, ShapeDot.Margin.Bottom)
End Sub
'拖动
@@ -88,7 +91,7 @@
AniStop("MySlider KeyPopup " & Uuid)
End Sub
Public Sub DragDoing()
Dim Percent As Double = MathClamp((Mouse.GetPosition(PanMain).X - ShapeDot.Width / 2) / (ActualWidth - ShapeDot.Width), 0, 1)
Dim Percent As Double = ((Mouse.GetPosition(PanMain).X - ShapeDot.Width / 2) / (ActualWidth - ShapeDot.Width)).Clamp(0, 1)
Dim NewValue As Integer = Percent * MaxValue
If Not NewValue = Value Then
Value = NewValue
@@ -103,7 +106,7 @@
Popup.IsOpen = False
End Sub
Public Sub RefreshPopup()
If GetHintText Is Nothing Then Exit Sub
If GetHintText Is Nothing Then Return
Popup.IsOpen = True
TextHint.Text = GetHintText.DynamicInvoke(Value)
Dim typeface As New Typeface(TextHint.FontFamily, TextHint.FontStyle, TextHint.FontWeight, TextHint.FontStretch)
@@ -153,7 +156,7 @@
End If
Catch ex As Exception
Log(ex, "滑动条颜色改变出错")
Logger.Warn(ex, "滑动条颜色改变出错")
End Try
End Sub
@@ -165,7 +168,7 @@
End Sub
Private Sub MySlider_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
'拒绝一边拖动一边用按键改变
If ReferenceEquals(Me, DragControl) Then Exit Sub
If ReferenceEquals(Me, DragControl) Then Return
'改变值
If e.Key = Key.Left Then
ChangeByKey = True
@@ -178,7 +181,7 @@
ChangeByKey = False
e.Handled = True
Else
Exit Sub
Return
End If
'更新 Popup
If GetHintText IsNot Nothing Then
@@ -188,4 +191,20 @@
End If
End Sub
#Region "设置"
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
Value = Val(NewValue)
End Sub
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
Return Value
End Function
Private Sub SaveSetting() Handles Me.Change
SettingService.SaveSetting(Me)
End Sub
#End Region
End Class
@@ -1,4 +1,4 @@
<Border x:Class="MySliderDot"
<Border x:Class="MySliderDot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -1,4 +1,4 @@
Public Class MySliderDot
Public Class MySliderDot
'基础
@@ -59,7 +59,7 @@
If AniControlEnabled = 0 Then RaiseEvent Change(Me, False)
Catch ex As Exception
Log(ex, "滑动条进度改变出错", LogLevel.Hint)
Logger.Warning(ex, "滑动条进度改变出错", LogLevel.Hint)
End Try
End Set
End Property
+37 -9
View File
@@ -1,5 +1,6 @@
Public Class MyTextBox
Public Class MyTextBox
Inherits TextBox
Implements ISettingControl
'自定义属性
@@ -36,7 +37,7 @@
End Property
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
If HintText = "" OrElse labHint.Text <> "" Then Exit Sub
If HintText = "" OrElse labHint.Text <> "" Then Return
labHint.Text = If(Text = "", HintText, "")
End Sub
@@ -44,7 +45,13 @@
Public Uuid As Integer = GetUuid()
Public Shared Event ValidateChanged(sender As Object, e As EventArgs)
Public ChangedEventList As New List(Of RoutedEventHandler)
Private _ChangedEventList As List(Of RoutedEventHandler)
Public ReadOnly Property ChangedEventList As List(Of RoutedEventHandler)
Get
If _ChangedEventList Is Nothing Then _ChangedEventList = New List(Of RoutedEventHandler)
Return _ChangedEventList
End Get
End Property
Public Custom Event ValidatedTextChanged As RoutedEventHandler
AddHandler(value As RoutedEventHandler)
ChangedEventList.Add(value)
@@ -217,11 +224,11 @@
IsTextChanged = IsLoaded
'进行输入验证
Validate()
If Not IsValidated Then Exit Sub
If Not IsValidated Then Return
'改变文本
RaiseEvent ValidatedTextChanged(sender, e)
Catch ex As Exception
Log(ex, "进行输入验证时出错", LogLevel.Assert)
Logger.Error(ex, "进行输入验证时出错")
End Try
End Sub
@@ -230,7 +237,7 @@
Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus
Try
'不对 ComboBox 从属进行动画
If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Exit Sub
If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Return
'判断当前颜色
Dim ForeColorName As String, BackColorName As String
Dim AnimationTime As Integer
@@ -264,7 +271,7 @@
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
'有动画
AniStart({
AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
AaColor(Me, BackgroundProperty, BackColorName, AnimationTime)
}, "MyTextBox Color " & Uuid)
Else
@@ -275,12 +282,12 @@
End If
Catch ex As Exception
Log(ex, "文本框颜色改变出错")
Logger.Warn(ex, "文本框颜色改变出错")
End Try
End Sub
Private Sub RefreshTextColor() Handles Me.IsEnabledChanged
Dim NewColor As MyColor = If(IsEnabled, ColorGray1, ColorGray4)
If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Exit Sub
If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Return
If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not Text = "" Then
'有动画
AniStart({AaColor(Me, ForegroundProperty, If(IsEnabled, "ColorBrushGray1", "ColorBrushGray4"), 200)}, "MyTextBox TextColor " & Uuid)
@@ -291,4 +298,25 @@
End If
End Sub
'在按下回车时触发自定义事件
Private Sub MyTextBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
If e.Key = Key.Enter Then RaiseCustomEvent()
End Sub
#Region "设置"
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
Text = NewValue
End Sub
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
Return Text
End Function
Private Sub SaveSetting() Handles Me.ValidatedTextChanged
SettingService.SaveSetting(Me)
End Sub
#End Region
End Class
+18 -41
View File
@@ -1,4 +1,4 @@
Public Class MyTextButton
Public Class MyTextButton
Inherits Label
Public Event Click(sender As Object, e As EventArgs)
@@ -22,15 +22,15 @@
End Set
End Property
Public Shared ReadOnly TextProperty As DependencyProperty =
DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("", Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs)
If Not e.OldValue = e.NewValue Then
AniStart({
AaOpacity(sender, -sender.Opacity, 50),
AaCode(Sub() sender.Content = e.NewValue,, True),
AaOpacity(sender, 1, 170)
}, "MyTextButton Text " & sender.Uuid)
End If
End Sub))
DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("",
Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs)
If e.OldValue = e.NewValue Then Return
AniStart({
AaOpacity(sender, -sender.Opacity, 50),
AaCode(Sub() sender.Content = e.NewValue,, True),
AaOpacity(sender, 1, 170)
}, "MyTextButton Text " & sender.Uuid)
End Sub))
'鼠标事件
@@ -43,13 +43,12 @@
IsMouseDown = False
End Sub
Private Sub MyTextButton_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
If IsMouseDown Then
IsMouseDown = False
Log("[Control] 按下文本按钮:" & Text)
RaiseEvent Click(Me, Nothing)
ModEvent.TryStartEvent(EventType, EventData)
e.Handled = True
End If
If Not IsMouseDown Then Return
IsMouseDown = False
Logger.Info($"按下文本按钮:{Text}")
RaiseEvent Click(Me, Nothing)
RaiseCustomEvent()
e.Handled = True
End Sub
'指向动画
@@ -57,7 +56,7 @@
Private Const AnimationTimeIn As Integer = 100
Private Const AnimationTimeOut As Integer = 200
Private ColorName As String
Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
'判断当前颜色
Dim ForeName As String
Dim Time As Integer
@@ -72,7 +71,7 @@
Time = AnimationTimeOut
End If
'重复性验证
If ColorName = ForeName Then Exit Sub
If ColorName = ForeName Then Return
ColorName = ForeName
'触发颜色动画
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
@@ -85,26 +84,4 @@
End If
End Sub
'实现自定义事件
Public Property EventType As String
Get
Return GetValue(EventTypeProperty)
End Get
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register(
"EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register(
"EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
End Class
@@ -0,0 +1,70 @@
Public Class MyVirtualizingElement(Of T As FrameworkElement)
Inherits FrameworkElement
Private Initializer As Func(Of T)
Public Sub New(Initializer As Func(Of T))
Me.Initializer = Initializer
LazyLoadBehavior.OnFirstEnterScrollViewerViewport(Me, AddressOf Init)
End Sub
''' <summary>
''' 实例化此控件。
''' </summary>
Public Function Init() As T
Dim Element As T = Initializer()
If Parent IsNot Nothing Then
If TypeOf Parent IsNot Panel Then Throw New Exception("MyVirtualizingElement 的父级必须是一个 Panel")
Dim ParentPanel As Panel = Parent
Dim CurrentIndex As Integer = ParentPanel.Children.IndexOf(Me)
ParentPanel.Children.RemoveAt(CurrentIndex)
ParentPanel.Children.Insert(CurrentIndex, Element)
End If
Return Element
End Function
Public Shared Widening Operator CType(Virtualized As MyVirtualizingElement(Of T)) As T
Return Virtualized.Init()
End Operator
End Class
'非泛型形式
Public Class MyVirtualizingElement
Inherits FrameworkElement
Private Initializer As Func(Of FrameworkElement)
Public Sub New(Initializer As Func(Of FrameworkElement))
Me.Initializer = Initializer
LazyLoadBehavior.OnFirstEnterScrollViewerViewport(Me, AddressOf Init)
End Sub
''' <summary>
''' 实例化此控件。
''' </summary>
Public Function Init() As FrameworkElement
Dim Element As FrameworkElement = Initializer()
If Parent IsNot Nothing Then
If TypeOf Parent IsNot Panel Then Throw New Exception("MyVirtualizingElement 的父级必须是一个 Panel")
Dim ParentPanel As Panel = Parent
Dim CurrentIndex As Integer = ParentPanel.Children.IndexOf(Me)
ParentPanel.Children.RemoveAt(CurrentIndex)
ParentPanel.Children.Insert(CurrentIndex, Element)
End If
Return Element
End Function
''' <summary>
''' 获取实例化后的控件。
''' 如果该控件没有实例化,则会立即实例化。
''' 如果类型错误,则返回原值。
''' </summary>
Public Shared Function TryInit(Element As FrameworkElement) As FrameworkElement
If GeneralUtils.IsGenericInstanceOf(Element, GetType(MyVirtualizingElement(Of ))) Then
Return CType(Element, Object).Init()
ElseIf TypeOf Element Is MyVirtualizingElement Then
Return CType(Element, MyVirtualizingElement).Init()
Else
Return Element
End If
End Function
End Class
+13 -14
View File
@@ -1,4 +1,4 @@
<Window
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL"
@@ -95,13 +95,12 @@
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Canvas x:Name="ImgBack" IsHitTestVisible="False" Grid.Row="1" RenderOptions.BitmapScalingMode="LowQuality" />
<Canvas x:Name="ImgBack" IsHitTestVisible="False" Grid.Row="1" RenderOptions.BitmapScalingMode="LowQuality"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Grid x:Name="PanTitle" Height="48">
<Grid.Background>
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0">
<GradientStop Color="{StaticResource ColorObject3}" Offset="0"/>
<GradientStop Color="{StaticResource ColorObject2}" Offset="0.5"/>
<GradientStop Color="{StaticResource ColorObject3}" Offset="1"/>
<GradientStop Color="{StaticResource ColorObject4}" Offset="0"/>
</LinearGradientBrush>
</Grid.Background>
<local:MyImage x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
@@ -115,19 +114,19 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M61,6.5 a10,11.5 180 1 0 0,18 M68,2.5 v24.5 h12 M98,2 v27 M107,2 v27" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="1.9" Width="53" />
<Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h6 a7,7 180 0 1 0,14 h-6 M62.5,6.5 a10,11.5 180 1 0 0,18 M71,2.5 v24.5 h13.5" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="2.2" Width="39" />
<TextBlock x:Name="LabTitleLogo" Visibility="Collapsed" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="18,0,0,0" Foreground="White" FontSize="17" VerticalAlignment="Center" />
<local:MyImage x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
<StackPanel Grid.Column="1" x:Name="PanTitleSelect" HorizontalAlignment="Left" Height="27" VerticalAlignment="Center" Orientation="Horizontal" Margin="13,0">
<local:MyRadioButton x:Name="BtnTitleSelect0" Text="{DynamicResource LangTitleHome}" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
LogoScale="0.95" Logo="M985.403 552.96C985.403 812.702 773.041 1024 512 1024S38.597 812.702 38.597 552.96c0-158.208 79.163-304.955 211.771-392.625 31.074-20.52 72.94-11.973 93.46 19.101s12.012 72.94-19.062 93.46c-94.681 62.622-151.237 167.306-151.237 280.064 0 185.344 151.788 336.108 338.471 336.108S850.471 738.304 850.471 552.96c0-114.649-57.974-220.278-155.096-282.585-31.35-20.125-40.448-61.833-20.362-93.184s61.794-40.448 93.184-20.4c135.995 87.236 217.206 235.362 217.206 396.169zM512 560.128c37.258 0 67.466-30.208 67.466-67.466V67.466C579.466 30.208 549.258 0 512 0s-67.466 30.208-67.466 67.466v425.196c0 37.258 30.208 67.466 67.466 67.466z" />
<local:MyRadioButton x:Name="BtnTitleSelect1" Text="{DynamicResource LangTitleDownload}" Tag="1" Margin="5,0" Padding="2,0"
<local:MyRadioButton x:Name="BtnTitleSelect0" Text="启动" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
LogoScale="0.9" Logo="M52.1,164.5c-1.4,0-3.1-0.5-4.2-1.3c-2.6-1.7-4-4.2-4-7V43.8c0-2.9,1.6-5.8,4.1-7c1.2-0.8,2.7-1.2,4.1-1.2c1.5,0,2.9,0.4,4.2,1.2L153.1,93c0,0,0.1,0,0.1,0.1c2.6,1.7,4,4.2,4,7c0,3-1.7,5.8-4.2,7.1l-96.8,56.2C55.1,164,53.5,164.5,52.1,164.5z M60.4,142.1l72.1-42.1L60.4,58.2V142.1z" />
<local:MyRadioButton x:Name="BtnTitleSelect1" Text="下载" Tag="1" Margin="5,0" Padding="2,0"
LogoScale="0.9" Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
<local:MyRadioButton x:Name="BtnTitleSelect2" Text="联机" Tag="2" Margin="5,0" Padding="2,0"
LogoScale="1.05" Logo="M512 817c-48.601 0-88-39.399-88-88s39.399-88 88-88 88 39.399 88 88-39.399 88-88 88zM237.671 565.74C308.335 474.58 397.369 429 504.774 429c118.433 0 214.225 55.421 287.377 166.264l-53.407 30.369c-13.84 7.87-31.362 4.367-41.114-8.219-50.291-64.911-114.577-97.367-192.856-97.367-86.851 0-156.835 40.318-209.95 120.953l-47.995-28.02c-15.263-8.91-20.412-28.507-11.502-43.77a32 32 0 0 1 2.344-3.47zM107.691 419.47C205.24 278.491 337.805 208 505.379 208c178.77 0 317.694 80.224 416.772 240.672l-56.54 31.73c-13.686 7.68-30.922 4.303-40.697-7.975C735.581 360.213 629.07 303.009 505.38 300.815 373.997 298.485 261.637 362.88 168.3 494l-50.116-28.505c-15.362-8.738-20.732-28.275-11.994-43.637a32 32 0 0 1 1.5-2.387z"/>
<local:MyRadioButton x:Name="BtnTitleSelect3" Text="{DynamicResource LangTitleSetup}" Tag="3" Margin="5,0" Padding="2,0"
<local:MyRadioButton x:Name="BtnTitleSelect3" Text="设置" Tag="3" Margin="5,0" Padding="2,0"
LogoScale="1.1" Logo="M940.4 463.7L773.3 174.2c-17.3-30-49.2-48.4-83.8-48.4H340.2c-34.6 0-66.5 18.5-83.8 48.4L89.2 463.7c-17.3 30-17.3 66.9 0 96.8L256.4 850c17.3 30 49.2 48.4 83.8 48.4h349.2c34.6 0 66.5-18.5 83.8-48.4l167.2-289.5c17.3-29.9 17.3-66.8 0-96.8z m-94.6 96.8L725.9 768.1c-17.3 30-49.2 48.4-83.8 48.4H387.5c-34.6 0-66.5-18.5-83.8-48.4L183.9 560.5c-17.3-30-17.3-66.9 0-96.8l119.8-207.5c17.3-30 49.2-48.4 83.8-48.4h254.6c34.6 0 66.5 18.5 83.8 48.4l119.8 207.5c17.3 30 17.3 66.9 0.1 96.8z M522.3 321.2c-2.5-0.1-5-0.2-7.5-0.2-119.9 0-214 110.3-186.3 235 15.8 70.9 71.5 126.6 142.4 142.4 17.5 3.9 34.7 5.4 51.4 4.7 102.1-3.9 183.6-87.9 183.6-191 0.1-103-81.5-187-183.6-190.9z m68.6 269.1c-18.5 18-43 28.9-68.6 30.7l-6 0.3c-30.2 0.4-58.6-11.4-79.7-33-19.5-20.1-30.7-47-30.9-75-0.3-29.6 11.1-57.4 32-78.3 20.6-20.6 48-32 77.2-32 2.5 0 5 0.1 7.5 0.3 26.7 1.8 51.5 13.2 70.5 32.5 19.6 20 30.8 46.9 31.2 74.9 0.2 30.2-11.5 58.6-33.2 79.6z"/>
<local:MyRadioButton x:Name="BtnTitleSelect4" Text="{DynamicResource LangTitleOther}" Tag="4" Margin="5,0" Padding="2,0"
<local:MyRadioButton x:Name="BtnTitleSelect4" Text="更多" Tag="4" Margin="5,0" Padding="2,0"
LogoScale="0.93" Logo="M364 0h-273C40 0 0 40 0 91v273C0 414 40 455 91 455h273C414 455 455 414 455 364V91C455 40 414 0 364 0zM341 341H113V113h227v227zM933 0h-273C609 0 568 40 568 91v273c0 50 40 91 91 91h273C983 455 1024 414 1024 364V91c0-50-40-91-90-91zM910 341h-227V113h227v227zM364 568h-273C40 568 0 609 0 659v273c0 50 40 91 91 91h273C414 1024 455 983 455 932v-273C455 609 414 568 364 568zM341 910H113v-227h227v227zM933 568h-273c-50 0-91 40-91 91v273c0 50 40 91 91 91h273c50 0 90-40 90-91v-273c0-50-40-90-90-90zM910 910h-227v-227h227v227z"/>
</StackPanel>
</Grid>
@@ -145,7 +144,7 @@
<Rectangle x:Name="RectLeftBackground" Width="0" Fill="{DynamicResource ColorBrushBackgroundTransparentSidebar}" IsHitTestVisible="False" />
<Rectangle x:Name="RectLeftShadow" Width="4" HorizontalAlignment="Left" IsHitTestVisible="False" Grid.Column="1" >
<Rectangle.Fill>
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0" Opacity="0.085">
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0" Opacity="0.04">
<GradientStop Color="#000000" Offset="0"/>
<GradientStop Color="#00000000" Offset="1"/>
</LinearGradientBrush>
@@ -170,8 +169,8 @@
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Row="1" Margin="15">
<local:MyExtraButton x:Name="BtnExtraBack" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="返回顶部" Visibility="Collapsed"
Logo="M858.496 188.9024 173.1072 188.9024c-30.2848 0-54.8352-24.5504-54.8352-54.8352L118.272 106.6496c0-30.2848 24.5504-54.8352 54.8352-54.8352l685.3888 0c30.2848 0 54.8352 24.5504 54.8352 54.8352l0 27.4176C913.3312 164.352 888.7808 188.9024 858.496 188.9024L858.496 188.9024zM150.6048 550.8608c0 0 300.0064-240.3584 303.0272-243.328 13.9776-13.5936 31.1808-21.8624 48.8192-24.7552 1.7152-0.3072 3.4304-0.5888 5.1456-0.768 2.7392-0.3072 5.4528-0.3584 8.192-0.3328 2.7392-0.0256 5.4272 0.0256 8.1664 0.3328 1.7408 0.1792 3.4304 0.4864 5.1456 0.768 17.664 2.8928 34.8672 11.1616 48.8192 24.7552 3.0464 2.944 303.0016 243.328 303.0016 243.328 32.384 31.5136 29.6192 63.9744-2.7392 95.5136-32.3328 31.5392-75.648 2.9696-108.0064-28.544l-185.8816-147.1232 0 485.8368c0 30.3104-24.5248 54.8608-54.8352 54.8608l-27.392 0c-30.2848 0-54.8352-24.5504-54.8352-54.8608L447.232 470.7072l-185.8304 147.0976c-32.3584 31.5392-75.6992 60.1344-108.032 28.5696C121.0368 614.8352 118.272 582.3744 150.6048 550.8608L150.6048 550.8608zM150.6048 550.8608" />
<local:MyExtraButton x:Name="BtnExtraDownload" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="下载管理" Visibility="Collapsed"
Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
<local:MyExtraButton x:Name="BtnExtraDownload" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="任务管理" Visibility="Collapsed"
LogoScale="1.1" Logo="M484.693 225.28 314.027 389.12l-68.267-68.267c-13.653-13.653-40.96-13.653-47.787 0-13.653 13.654-13.653 34.134 0 47.787l95.574 95.573c13.653 13.654 34.133 13.654 47.786 0L532.48 273.067c13.653-13.654 13.653-34.134 0-47.787s-34.133-13.653-47.787 0M819.2 341.333H614.4c-20.48 0-34.133 13.654-34.133 34.134S593.92 409.6 614.4 409.6h204.8c20.48 0 34.133-13.653 34.133-34.133s-13.653-34.134-34.133-34.134m0 341.334H614.4c-20.48 0-34.133 13.653-34.133 34.133s13.653 34.133 34.133 34.133h204.8c20.48 0 34.133-13.653 34.133-34.133s-13.653-34.133-34.133-34.133m-477.867-102.4c-75.093 0-136.533 61.44-136.533 136.533s61.44 136.533 136.533 136.533 136.534-61.44 136.534-136.533-61.44-136.533-136.534-136.533m0 204.8c-40.96 0-68.266-27.307-68.266-68.267s27.306-68.267 68.266-68.267S409.6 675.84 409.6 716.8s-27.307 68.267-68.267 68.267" />
<local:MyExtraButton x:Name="BtnExtraApril" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="投降" Visibility="Collapsed"
Logo="M124.96 32c-28.64 0-60.064 23.2-60.064 51.904v856.192c0 28.576 30.688 51.904 59.296 51.904s60.064-23.328 60.064-51.904V83.904C184.256 55.2 153.536 32 124.96 32z m616.352 71.264c-168.416 0-168.416-69.024-336.8-69.024-97.216 0-172.256 45.344-172.256 45.344l-0.736 516.736s75.776-45.28 172.96-45.28c168.416 0 168.416 69.024 336.8 69.024 105.344 0 211.168-55.328 211.168-55.328V48.064c0.032 0-105.792 55.2-211.136 55.2z m0 0" />
<local:MyExtraButton x:Name="BtnExtraShutdown" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="关闭 Minecraft" Visibility="Collapsed"
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,4 +1,4 @@
' This file is used by Code Analysis to maintain SuppressMessage
' This file is used by Code Analysis to maintain SuppressMessage
' attributes that are applied to this project.
' Project-level suppressions either have no target or are given
' a specific target and scoped to a namespace, type, member, etc.
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

@@ -1,6 +1,8 @@
'动画引擎模块
'动画引擎模块
'使用 Ani 作为方法或属性的开头,使用 Aa 作为单个动画对象的开头(便于自动补全)
Imports System.Windows.Media.Effects
Public Module ModAnimation
#Region "声明"
@@ -8,7 +10,12 @@ Public Module ModAnimation
''' <summary>
''' 动画速度。最大为 200。
''' </summary>
Public AniSpeed As Double = 1
Public ReadOnly Property AniSpeed As Double
Get
Dim Setting As Integer = Settings.Get(Of Integer)("SystemDebugAnim")
Return If(Setting >= 30, 200, (Setting * 0.1 + 0.1).Clamp(0.1, 3))
End Get
End Property
''' <summary>
''' 动画组列表。
''' </summary>
@@ -72,7 +79,7 @@ Public Module ModAnimation
''' 已经执行的动画长度。如果为负数则为延迟。
''' </summary>
''' <remarks></remarks>
Public TimeFinished As Integer
Public TimeFinished As Double
''' <summary>
''' 已经完成的百分比。
''' </summary>
@@ -107,7 +114,7 @@ Public Module ModAnimation
Public ValueLast As Object
Public Overrides Function ToString() As String
Return GetStringFromEnum(TypeMain) & " | " & TimeFinished & "/" & TimeTotal & "(" & Math.Round(TimePercent * 100) & "%)" & If(Obj Is Nothing, "", " | " & Obj.ToString & "(" & Obj.GetType.Name & ")")
Return TypeMain.ToString() & " | " & TimeFinished & "/" & TimeTotal & "(" & Math.Round(TimePercent * 100) & "%)" & If(Obj Is Nothing, "", " | " & Obj.ToString & "(" & Obj.GetType.Name & ")")
End Function
End Structure
@@ -467,7 +474,7 @@ Public Module ModAnimation
''' 按照 WPF 方式旋转控件的动画。
''' </summary>
''' <param name="Obj">动画的对象。它必须已经拥有了单一的 ScaleTransform 值。</param>
''' <param name="Value">大小改变的百分比(如-0.6)。</param>
''' <param name="Value">旋转角度(度)。</param>
''' <param name="Time">动画长度(毫秒)。</param>
''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
''' <param name="Ease">插值器类型。</param>
@@ -514,13 +521,14 @@ Public Module ModAnimation
''' </summary>
''' <remarks></remarks>
Public Function AaStack(Stack As StackPanel, Optional Time As Integer = 100, Optional Delay As Integer = 25) As List(Of AniData)
AaStack = New List(Of AniData)
Dim Result = New List(Of AniData)
Dim AniDelay As Integer = 0
For Each Item In Stack.Children
Item.Opacity = 0
AaStack.Add(AaOpacity(Item, 1, Time, AniDelay))
Result.Add(AaOpacity(Item, 1, Time, AniDelay))
AniDelay += Delay
Next
Return Result
End Function
#End Region
@@ -577,10 +585,10 @@ Public Module ModAnimation
Public Class AniEaseLinear
Inherits AniEase
Public Overrides Function GetValue(t As Double) As Double
Return MathClamp(t, 0, 1)
Return t.Clamp(0, 1)
End Function
Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
Return MathClamp(t1, 0, 1) - MathClamp(t0, 0, 1)
Return t1.Clamp(0, 1) - t0.Clamp(0, 1)
End Function
End Class
@@ -595,7 +603,7 @@ Public Module ModAnimation
p = Power
End Sub
Public Overrides Function GetValue(t As Double) As Double
Return MathClamp(t, 0, 1) ^ p
Return t.Clamp(0, 1) ^ p
End Function
End Class
''' <summary>
@@ -608,7 +616,7 @@ Public Module ModAnimation
p = Power
End Sub
Public Overrides Function GetValue(t As Double) As Double
Return 1 - MathClamp(1 - t, 0, 1) ^ p
Return 1 - (1 - t).Clamp(0, 1) ^ p
End Function
End Class
''' <summary>
@@ -624,6 +632,26 @@ Public Module ModAnimation
Return Ease.GetValue(t)
End Function
End Class
''' <summary>
''' 以特定速度开始的平滑结束。
''' </summary>
Public Class AniEaseOutFluentWithInitial
Inherits AniEase
Private ReadOnly alpha As Double '(初速度 / 平均速度) – 1
''' <param name="InitialPixelPerSecond">初速度,px/s</param>
''' <param name="TotalSecond">总时长,s</param>
''' <param name="TotalDistance">总路程,px</param>
Public Sub New(InitialPixelPerSecond As Double, TotalSecond As Double, TotalDistance As Double)
Dim v0_norm As Double = InitialPixelPerSecond * TotalSecond / TotalDistance '归一化初速度
alpha = v0_norm - 1.0
If alpha < 0 Then alpha = 0 '初速度小于平均速度时,退化为线性
End Sub
Public Overrides Function GetValue(percent As Double) As Double
Dim p As Double = percent.Clamp(0, 1)
If alpha = 0 Then Return p '退化到线性
Return (alpha + 1) * p / (1 + alpha * p)
End Function
End Class
'Back / 回弹
''' <summary>
@@ -636,7 +664,7 @@ Public Module ModAnimation
p = 3 - Power * 0.5
End Sub
Public Overrides Function GetValue(t As Double) As Double
t = MathClamp(t, 0, 1)
t = t.Clamp(0, 1)
Return t ^ p * Math.Cos(1.5 * Math.PI * (1 - t))
End Function
End Class
@@ -650,7 +678,7 @@ Public Module ModAnimation
p = 3 - Power * 0.5
End Sub
Public Overrides Function GetValue(t As Double) As Double
t = MathClamp(t, 0, 1)
t = t.Clamp(0, 1)
Return 1 - (1 - t) ^ p * Math.Cos(1.5 * Math.PI * t)
End Function
End Class
@@ -694,7 +722,7 @@ Public Module ModAnimation
p = Power + 4
End Sub
Public Overrides Function GetValue(t As Double) As Double
t = MathClamp(t, 0, 1)
t = t.Clamp(0, 1)
Return t ^ ((p - 1) * 0.25) * Math.Cos((p - 3.5) * Math.PI * (1 - t) ^ 1.5)
End Function
End Class
@@ -708,7 +736,7 @@ Public Module ModAnimation
p = Power + 4
End Sub
Public Overrides Function GetValue(t As Double) As Double
t = 1 - MathClamp(t, 0, 1)
t = 1 - t.Clamp(0, 1)
Return 1 - t ^ ((p - 1) * 0.25) * Math.Cos((p - 3.5) * Math.PI * (1 - t) ^ 1.5)
End Function
End Class
@@ -723,9 +751,17 @@ Public Module ModAnimation
''' <param name="AniGroup">由 Aa 开头的函数初始化的 AniData 对象集合。</param>
''' <param name="Name">动画组的名称。如果重复会直接停止同名动画组。</param>
Public Sub AniStart(AniGroup As IList, Optional Name As String = "", Optional RefreshTime As Boolean = False)
If RefreshTime Then AniLastTick = GetTimeTick() '避免处理动画时已经造成了极大的延迟,导致动画突然结束
If RefreshTime Then AniLastTick = GetTimeMs() '避免处理动画时已经造成了极大的延迟,导致动画突然结束
'添加到正在执行的动画组
Dim NewEntry As New AniGroupEntry With {.Data = GetFullList(Of AniData)(AniGroup), .StartTick = GetTimeTick()}
Dim AllAnis = New List(Of AniData)
For Each Element In AniGroup
If TypeOf Element Is ICollection Then
AllAnis.AddRange(Element)
Else
AllAnis.Add(Element)
End If
Next
Dim NewEntry As New AniGroupEntry With {.Data = AllAnis, .StartTick = GetTimeMs()}
If Name = "" Then
Name = NewEntry.Uuid
Else
@@ -768,57 +804,58 @@ Public Module ModAnimation
''' </summary>
Public Sub AniStart()
'初始化计时器
AniLastTick = GetTimeTick()
AniLastTick = GetTimeMs()
AniFPSTimer = AniLastTick
AniRunning = True '标记动画执行开始
RunInNewThread(Sub()
Try
Log("[Animation] 动画线程开始")
Do While True
'两帧之间的间隔时间
Dim DeltaTime As Long = MathClamp(GetTimeTick() - AniLastTick, 0, 100000)
If DeltaTime < 3 Then GoTo Sleeper
AniLastTick = GetTimeTick()
'记录 FPS
If ModeDebug Then
If MathClamp(AniLastTick - AniFPSTimer, 0, 100000) >= 500 Then
AniFPS = AniFPSCounter
AniFPSCounter = 0
AniFPSTimer = AniLastTick
End If
AniFPSCounter += 2
End If
'执行动画
RunInUiWait(Sub()
AniCount = 0
AniTimer(DeltaTime * AniSpeed)
'#If DEBUG Then
' FrmMain.Title = "F " & AniFPS & ", A " & AniCount & ", R " & NetManage.FileRemain
'#Else
' If ModeDebug Then FrmMain.Title = "FPS " & AniFPS & ", 动画 " & AniCount & ", 下载中 " & NetManage.FileRemain
'#End If
If RandomInteger(0, 64 * If(ModeDebug, 5, 30)) = 0 AndAlso ((AniFPS < 62 AndAlso AniFPS > 0) OrElse AniCount > 4 OrElse NetManager.FileRemain <> 0) Then
Log("[Report] FPS " & AniFPS & ", 动画 " & AniCount & ", 下载中 " & NetManager.FileRemain & "(" & GetString(NetManager.Speed) & "/s)")
End If
End Sub)
RunInNewThread(
Sub()
Try
Logger.Info("动画线程开始")
Do While True
'两帧之间的间隔时间
Dim DeltaTime As Long = (GetTimeMs() - AniLastTick).Clamp(0, 100000)
If DeltaTime < 3 Then GoTo Sleeper
AniLastTick = GetTimeMs()
'记录 FPS
If ModeDebug Then
If (AniLastTick - AniFPSTimer).Clamp(0, 100000) >= 500 Then
AniFPS = AniFPSCounter
AniFPSCounter = 0
AniFPSTimer = AniLastTick
End If
AniFPSCounter += 2
End If
'执行动画
RunInUiWait(
Sub()
AniCount = 0
AniTimer(DeltaTime * AniSpeed)
If RandomInteger(0, 64 * If(ModeDebug, 5, 30)) = 0 AndAlso ((AniFPS < 62 AndAlso AniFPS > 0) OrElse AniCount > 4 OrElse NetManager.FileRemain <> 0) Then
Logger.Info($"FPS {AniFPS}, 动画 {AniCount}, 下载中 {NetManager.FileRemain}({StringUtils.FormatByteSize(NetManager.Speed)}/s)")
If NetManager.FileRemain < 5 Then
For Each File In NetManager.AllFiles.Values
If File.State < NetState.Finished Then Logger.Trace($"- {File.State} {StringUtils.FormatByteSize(File.Speed)}/s {File.LocalPath}")
Next
End If
End If
End Sub)
Sleeper:
'控制 FPS
Thread.Sleep(1)
Loop
Catch ex As Exception
Log(ex, "动画帧执行失败", LogLevel.Assert)
End Try
End Sub, "Animation", ThreadPriority.AboveNormal)
'控制 FPS
Thread.Sleep(1)
Loop
Catch ex As Exception
Logger.Error(ex, "动画帧执行失败", LogBehavior.AlertThenCrash)
End Try
End Sub, "Animation", ThreadPriority.AboveNormal)
End Sub
''' <summary>
''' 动画定时器事件。
''' </summary>
Public Sub AniTimer(DeltaTick As Integer)
Public Sub AniTimer(DeltaTick As Double)
Try
If DeltaTick / AniSpeed > 200 Then Log("[Animation] 两个动画帧间隔 " & DeltaTick & " ms", LogLevel.Developer)
If DeltaTick / AniSpeed > 100 Then Logger.Info($"两个动画帧间隔 {DeltaTick} ms")
Dim i As Integer = -1
'循环每个动画组
Do While i + 1 < AniGroups.Count
@@ -883,7 +920,7 @@ NextAni:
Loop
Catch ex As Exception
Log(ex, "动画刻执行失败", LogLevel.Hint)
Logger.Error(ex, "动画刻执行失败", LogBehavior.Toast)
End Try
End Sub
@@ -896,15 +933,39 @@ NextAni:
Select Case Ani.TypeMain
Case AniType.Number
Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
Dim Delta As Double = MathUtils.Lerp(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
If Delta <> 0 Then
Select Case Ani.TypeSub
Case AniTypeSub.X
DeltaLeft(Ani.Obj, Delta)
If TypeOf Ani.Obj Is Window Then
CType(Ani.Obj, Window).Left += Delta
Else
Select Case Ani.Obj.HorizontalAlignment
Case HorizontalAlignment.Left, HorizontalAlignment.Stretch
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left + Delta, Ani.Obj.Margin.Top, Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom)
Case HorizontalAlignment.Right
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left, Ani.Obj.Margin.Top, Ani.Obj.Margin.Right - Delta, Ani.Obj.Margin.Bottom)
End Select
End If
Case AniTypeSub.Y
DeltaTop(Ani.Obj, Delta)
If TypeOf Ani.Obj Is Window Then
CType(Ani.Obj, Window).Top += Delta
Else
Select Case Ani.Obj.VerticalAlignment
Case VerticalAlignment.Top
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left, Ani.Obj.Margin.Top + Delta, Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom)
Case VerticalAlignment.Bottom
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left, Ani.Obj.Margin.Top, Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom - Delta)
End Select
End If
Case AniTypeSub.Opacity
Ani.Obj.Opacity = MathClamp(Ani.Obj.Opacity + Delta, 0, 1)
If TypeOf Ani.Obj Is UIElement Then
Dim Obj As UIElement = Ani.Obj
Obj.Opacity = (Obj.Opacity + Delta).Clamp(0, 1)
ElseIf TypeOf Ani.Obj Is DropShadowEffect Then
Dim Obj As DropShadowEffect = Ani.Obj
Obj.Opacity = (Obj.Opacity + Delta).Clamp(0, 1)
End If
Case AniTypeSub.Width
Dim Obj As FrameworkElement = Ani.Obj
Obj.Width = Math.Max(If(Double.IsNaN(Obj.Width), Obj.ActualWidth, Obj.Width) + Delta, 0)
@@ -936,7 +997,7 @@ NextAni:
Case AniType.Color
'利用 Last 记录了余下的小数值
Dim Delta As MyColor = MathPercent(New MyColor(0, 0, 0, 0), Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent)) + Ani.ValueLast
Dim Delta As MyColor = MyColor.Lerp(New MyColor(0, 0, 0, 0), Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent)) + Ani.ValueLast
Dim Obj As FrameworkElement = Ani.Obj(0)
Dim Prop As DependencyProperty = Ani.Obj(1)
Dim NewColor As MyColor = New MyColor(Obj.GetValue(Prop)) + Delta
@@ -946,21 +1007,22 @@ NextAni:
Case AniType.Scale
Dim Obj As FrameworkElement = Ani.Obj
Dim Delta As Double = Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent)
Obj.Margin = New Thickness(Obj.Margin.Left + MathPercent(0, Ani.Value.Left, Delta), Obj.Margin.Top + MathPercent(0, Ani.Value.Top, Delta), Obj.Margin.Right + MathPercent(0, Ani.Value.Left, Delta), Obj.Margin.Bottom + MathPercent(0, Ani.Value.Top, Delta))
Obj.Width = Math.Max(Obj.Width + MathPercent(0, Ani.Value.Width, Delta), 0)
Obj.Height = Math.Max(Obj.Height + MathPercent(0, Ani.Value.Height, Delta), 0)
Obj.Margin = New Thickness(Obj.Margin.Left + MathUtils.Lerp(0, Ani.Value.Left, Delta), Obj.Margin.Top + MathUtils.Lerp(0, Ani.Value.Top, Delta), Obj.Margin.Right + MathUtils.Lerp(0, Ani.Value.Left, Delta), Obj.Margin.Bottom + MathUtils.Lerp(0, Ani.Value.Top, Delta))
Obj.Width = Math.Max(Obj.Width + MathUtils.Lerp(0, Ani.Value.Width, Delta), 0)
Obj.Height = Math.Max(Obj.Height + MathUtils.Lerp(0, Ani.Value.Height, Delta), 0)
Case AniType.TextAppear
Dim TextCount As Integer = If(Ani.Value(1), Ani.Value(0).ToString.Length, 0) +
Math.Round(Ani.Value(0).ToString.Length * If(Ani.Value(1), -1, 1) * Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, 0))
Dim NewText As String = Mid(Ani.Value(0), 1, TextCount)
Dim Text As String = Ani.Value(0)
Dim TextCount As Integer = If(Ani.Value(1), Text.Length, 0) +
Math.Round(Text.Length * If(Ani.Value(1), -1, 1) * Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, 0))
Dim NewText As String = Text.Substring(0, TextCount)
'添加乱码
If TextCount < Ani.Value(0).ToString.Length Then
Dim NextText As String = Mid(Ani.Value(0), TextCount + 1, 1)
If TextCount < Text.Length Then
Dim NextText As String = Text.Substring(TextCount, 1)
If Convert.ToInt32(Convert.ToChar(NextText)) >= Convert.ToInt32(Convert.ToChar(128)) Then
NewText &= Encoding.GetEncoding("GB18030").GetString({RandomInteger(16 + 160, 87 + 160), RandomInteger(1 + 160, 89 + 160)})
Else
NewText &= RandomOne("0123456789./*-+\[]{};':/?,!@#$%^&*()_+-=qwwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM".ToCharArray)
NewText &= RandomInteger(0, 85).ConvertRadix(86)
End If
End If
'设置文本
@@ -971,31 +1033,32 @@ NextAni:
End If
Case AniType.Code
Ani.TimeFinished = Ani.TimeTotal
CType(Ani.Value, ThreadStart)()
Case AniType.ScaleTransform
Dim Obj As FrameworkElement = Ani.Obj
If TypeOf Obj.RenderTransform IsNot ScaleTransform Then
Obj.RenderTransformOrigin = New Point(0.5, 0.5)
If Obj.RenderTransformOrigin = New Point(0, 0) Then Obj.RenderTransformOrigin = New Point(0.5, 0.5)
Obj.RenderTransform = New ScaleTransform(1, 1)
End If
Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
Dim Delta As Double = MathUtils.Lerp(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
CType(Obj.RenderTransform, ScaleTransform).ScaleX = Math.Max(CType(Obj.RenderTransform, ScaleTransform).ScaleX + Delta, 0)
CType(Obj.RenderTransform, ScaleTransform).ScaleY = Math.Max(CType(Obj.RenderTransform, ScaleTransform).ScaleY + Delta, 0)
Case AniType.RotateTransform
Dim Obj As FrameworkElement = Ani.Obj
If TypeOf Obj.RenderTransform IsNot RotateTransform Then
Obj.RenderTransformOrigin = New Point(0.5, 0.5)
If Obj.RenderTransformOrigin = New Point(0, 0) Then Obj.RenderTransformOrigin = New Point(0.5, 0.5)
Obj.RenderTransform = New RotateTransform(0)
End If
Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
Dim Delta As Double = MathUtils.Lerp(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
CType(Obj.RenderTransform, RotateTransform).Angle = CType(Obj.RenderTransform, RotateTransform).Angle + Delta
End Select
Ani.TimePercent = Ani.TimeFinished / Ani.TimeTotal '修改执行百分比
Catch ex As Exception
Log(ex, "执行动画失败:" & Ani.ToString, LogLevel.Hint)
Logger.Error(ex, $"执行动画失败:{Ani}", LogBehavior.Toast)
End Try
Return Ani
End Function
File diff suppressed because it is too large. Load diff
+212 -120
View File
@@ -1,4 +1,4 @@
Public Module ModLoader
Public Module ModLoader
'各类加载器
''' <summary>
@@ -37,7 +37,7 @@
RealParent = RealParent.Parent
End While
Catch ex As Exception
Log(ex, "获取父加载器失败(" & Name & ")", LogLevel.Feedback)
Logger.Error(ex, $"获取父加载器失败({Name})")
Return Nothing
End Try
End Get
@@ -85,11 +85,12 @@
Return _State
End Get
Set(value As LoadState)
If _State = value Then Exit Property
If _State = value Then Return
Dim OldState = _State
If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
If value = LoadState.Finished AndAlso Settings.Get(Of Boolean)("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
_State = value
Log("[Loader] 加载器 " & Name & " 状态改变:" & GetStringFromEnum(value))
If value = LoadState.Canceled Then CancellationTokenSource?.Cancel()
Logger.Log($"加载器 {[GetType].Name} {Name} 状态改变:{value}", If(value = LoadState.Failed, LogLevel.Error, LogLevel.Info))
'实现 ILoadingTrigger 接口与 OnStateChanged 回调
RunInUi(
Sub()
@@ -112,7 +113,7 @@
Return _LoadingState
End Get
Set(value As MyLoading.MyLoadingState)
If _LoadingState = value Then Exit Property
If _LoadingState = value Then Return
Dim OldState = _LoadingState
_LoadingState = value
RaiseEvent LoadingStateChanged(value, OldState)
@@ -148,13 +149,13 @@
Case LoadState.Waiting
Return 0
Case LoadState.Loading
Return If(_Progress = -1, 0.02, _Progress)
Return If(_Progress = -1, 0, _Progress)
Case Else
Return 1
End Select
End Get
Set(value As Double)
If _Progress = value Then Exit Property
If _Progress = value Then Return
Dim OldValue = _Progress
_Progress = value
RaiseEvent ProgressChanged(value, OldValue)
@@ -163,13 +164,42 @@
Private _Progress As Double = -1
Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
''' <summary>
''' 创建一个 ProgressProvider,在其进度改变时,并将其进度单向映射到此加载器上。
''' 这是 Loader 对新框架的兼容层。
''' </summary>
Public Function CreateSyncProgressProvider(Optional FromPercentage As Double = 0, Optional ToPercentage As Double = 1) As ProgressProvider
Dim Provider As New ProgressProvider
AddHandler Provider.ProgressChanged,
Sub(ChangedProgress)
Dim RawProgress = Provider.GetRaw()
Progress = FromPercentage + (RawProgress.actual + RawProgress.skiped) * (ToPercentage - FromPercentage)
End Sub
Return Provider
End Function
''' <summary>
''' 计算总进度时的权重。它应该为预计时间(秒)。
''' </summary>
Public Property ProgressWeight As Double = 1
'状态变化
Public MustOverride Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
Public MustOverride Sub Abort()
Public MustOverride Sub Cancel()
Public MustOverride Sub Failed(Ex As Exception)
Private CancellationTokenSource As CancellationTokenSource
''' <summary>
''' 创建一个随此加载器取消的取消令牌;令牌取消时也会调用此加载器的 Cancel。
''' 这是 Loader 对新框架的兼容层。
''' </summary>
Public Function CreateCancellationToken() As CancellationToken
SyncLock LockState
If CancellationTokenSource Is Nothing OrElse CancellationTokenSource.IsCancellationRequested Then
CancellationTokenSource = New CancellationTokenSource
CancellationTokenSource.Token.Register(AddressOf Cancel)
End If
If State = LoadState.Canceled Then CancellationTokenSource.Cancel()
Return CancellationTokenSource.Token
End SyncLock
End Function
'等待结束
Public Const WaitForExitTimeoutMessage As String = "等待加载器执行超时。"
@@ -177,16 +207,19 @@
''' <summary>
''' 无限期地等待加载器完成,直到结束或抛出异常。若加载器尚未开始,则会开始执行。
''' </summary>
Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False)
Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False,
Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
Start(Input, IsForceRestart)
Do While State = LoadState.Loading
If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
p?.Set(Progress)
c.ThrowIfCancellationRequested()
Thread.Sleep(10)
Loop
If State = LoadState.Finished Then
Exit Sub
ElseIf State = LoadState.Aborted Then
Throw New ThreadInterruptedException("加载器执行已中断。")
Return
ElseIf State = LoadState.Canceled Then
Throw New OperationCanceledException("加载器执行已中断")
ElseIf IsNothing([Error]) Then
Throw New Exception("未知错误!")
Else
@@ -207,9 +240,9 @@
If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
Loop
If State = LoadState.Finished Then
Exit Sub
ElseIf State = LoadState.Aborted Then
Throw New ThreadInterruptedException("加载器执行已中断。")
Return
ElseIf State = LoadState.Canceled Then
Throw New OperationCanceledException("加载器执行已中断")
ElseIf IsNothing([Error]) Then
Throw New Exception("未知错误!")
Else
@@ -227,28 +260,28 @@
''' <summary>
''' 用于异步执行并监控单一函数的加载器。
''' </summary>
Public Class LoaderTask(Of InputType, OutputType)
Public Class LoaderTask(Of TIn, TOut)
Inherits LoaderBase
'线程设定
Protected Friend ThreadPriority As ThreadPriority
'执行事件
Protected Friend LoadDelegate As Action(Of LoaderTask(Of InputType, OutputType))
Protected Friend LoadDelegate As Action(Of LoaderTask(Of TIn, TOut))
Protected Friend InputDelegate As Func(Of Object)
'状态指示
''' <summary>
''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
''' </summary>
Public ReadOnly Property IsAborted As Boolean
Public ReadOnly Property IsCanceled As Boolean
Get
Return IsAbortedWithThread(Thread.CurrentThread)
Return IsCanceledWithThread(Thread.CurrentThread)
End Get
End Property
''' <summary>
''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
''' </summary>
Public Function IsAbortedWithThread(Thread As Thread) As Boolean
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Aborted
Public Function IsCanceledWithThread(Thread As Thread) As Boolean
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Canceled
End Function
''' <summary>
''' 在输入相同时使用原有结果的超时,单位为毫秒。
@@ -263,25 +296,25 @@
''' </summary>
Public LastRunningThread As Thread = Nothing
'输入输出
Public Input As InputType = Nothing
Public Output As OutputType = Nothing
Public Input As TIn = Nothing
Public Output As TOut = Nothing
'获取输入
Public Function StartGetInput(Optional Input As InputType = Nothing, Optional InputDelegate As Func(Of Object) = Nothing) As InputType 'InputDelegate 参数存在匿名调用
Public Function StartGetInput(Optional Input As TIn = Nothing, Optional InputDelegate As Func(Of TIn) = Nothing) As TIn 'InputDelegate 参数存在匿名调用
If InputDelegate Is Nothing Then InputDelegate = Me.InputDelegate
Dim NewInput As InputType = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
Dim NewInput As TIn = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
If (Input Is Nothing OrElse (NewInput IsNot Nothing AndAlso Input.Equals(NewInput))) AndAlso InputDelegate IsNot Nothing Then
RunInUiWait(Sub() Input = InputDelegate())
End If
Return Input
End Function
'代码执行
Public Function ShouldStart(ByRef Input As Object, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
Public Function ShouldStart(ByRef Input As TIn, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
'获取输入
Try
Input = StartGetInput(Input)
Catch ex As Exception
Log(ex, "加载输入获取失败(" & Name & ")", LogLevel.Hint)
Logger.Error(ex, $"加载输入获取失败({Name})", LogBehavior.Toast)
[Error] = ex
SyncLock LockState
State = LoadState.Failed
@@ -290,75 +323,90 @@
'检验输入以确定情况
If IsForceRestart Then Return True '强制要求重启
If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时
Return False '则不重试
If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
LastFinishedTime = 0 OrElse GetTimeMs() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
Return False '…则不重试
Else
Return True '需要开始
Return True '否则需要重启
End If
End Function
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
'确认是否开始加载
If ShouldStart(Input, IsForceRestart) Then
'输入不同或失败,开始加载
If State = LoadState.Loading Then TriggerThreadAbort()
If State = LoadState.Loading Then TriggerThreadInterrupt()
Me.Input = Input
SyncLock LockState
State = LoadState.Loading
Progress = -1
End SyncLock
Else
Exit Sub
Return
End If
LastRunningThread = New Thread(
Sub()
Try
IsForceRestarting = IsForceRestart
If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
LoadDelegate(Me)
If IsAborted Then
Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer)
Else
RaisePreviewFinish()
State = LoadState.Finished
LastFinishedTime = GetTimeTick() '未中断,本次输出有效
End If
Catch ex As CancelledException
If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
If Not IsAborted Then State = LoadState.Aborted
Catch ex As ThreadInterruptedException
If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%")
'如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted
'如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted
If Not IsAborted Then State = LoadState.Aborted
Catch ex As Exception
If IsAborted Then Exit Sub
Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer)
[Error] = ex
State = LoadState.Failed
End Try
End Sub) With {.Name = Name, .Priority = ThreadPriority}
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
Sub()
Try
IsForceRestarting = IsForceRestart
Logger.Trace(Function() $"加载线程 {Name} 已{If(IsForceRestarting, "强制", "")}启动")
LoadDelegate(Me)
If IsCanceled Then
Logger.Warn($"加载线程 {Name} 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})")
Return
End If
Logger.Trace(Function() $"加载线程 {Name} 已完成")
RaisePreviewFinish()
State = LoadState.Finished
LastFinishedTime = GetTimeMs() '未中断,本次输出有效
Catch ex As Exception
If ex.IsCanceled Then
'如果线程是因为判断到 IsCanceled 而提前中止,则代表已有新线程被重启,此时不应当改为 Canceled
'如果线程是在没有 IsCanceled 时手动引发了取消异常,则代表没有重启线程,这通常代表用户手动取消,应当改为 Canceled
Logger.Trace(ex, $"加载线程 {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
If Not IsCanceled Then State = LoadState.Canceled
Else
Failed(ex)
End If
End Try
End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
Try
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsCanceled
Catch ex As ThreadStateException '若遇到偶发的 “线程正在运行或被终止”,则等待后重试
Thread.Sleep(500)
LastRunningThread.Start()
End Try
End Sub
Public Overrides Sub Abort()
If State <> LoadState.Loading Then Exit Sub
Public Overrides Sub Failed(ex As Exception)
[Error] = ex
SyncLock LockState
State = LoadState.Aborted
If IsCanceled OrElse State >= LoadState.Finished Then Return
State = LoadState.Failed
End SyncLock
TriggerThreadAbort()
Logger.Warn(ex, $"加载线程 {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
TriggerThreadInterrupt()
End Sub
Private Sub TriggerThreadAbort()
If LastRunningThread Is Nothing Then Exit Sub
If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断")
If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
Public Overrides Sub Cancel()
SyncLock LockState
If State <> LoadState.Loading Then Return
State = LoadState.Canceled
End SyncLock
TriggerThreadInterrupt()
End Sub
Private Sub TriggerThreadInterrupt()
If LastRunningThread Is Nothing Then Return
If LastRunningThread.IsAlive Then
LastRunningThread.Interrupt()
Logger.Trace(Function() $"加载线程 {Name} 已中断")
End If
LastRunningThread = Nothing
End Sub
Public Sub New()
'仅仅是为了避免一些智障报错(继承类必须重写 New 的情况)
End Sub
Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of InputType, OutputType)), Optional InputDelegate As Func(Of InputType) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of TIn, TOut)), Optional InputDelegate As Func(Of TIn) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
Me.Name = Name
Me.LoadDelegate = LoadDelegate
Me.InputDelegate = InputDelegate
@@ -366,6 +414,53 @@
End Sub
End Class
''' <summary>
''' 新 Worker 对老 Loader 的兼容包装类。
''' </summary>
Public Class LoaderWorker
Inherits LoaderBase
Public WithEvents Worker As IWorker
Public Sub New(Name As String, Worker As IWorker)
Me.Name = Name
Me.Worker = Worker
AddHandler Worker.Progress.ProgressChanged, Sub(Raw) Progress = Raw.actual + Raw.skiped
End Sub
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
SyncLock LockState
State = LoadState.Loading
End SyncLock
Logger.Trace(Function() $"Worker {Name} 已启动")
IsForceRestarting = IsForceRestart
Worker.Start()
End Sub
Private Sub Worker_Succeeded() Handles Worker.Succeeded
Logger.Trace(Function() $"Worker {Name} 已完成")
RaisePreviewFinish()
SyncLock LockState
State = LoadState.Finished
End SyncLock
End Sub
Public Overrides Sub Cancel() Handles Worker.Canceled
SyncLock LockState
If State <> LoadState.Loading Then Return
State = LoadState.Canceled
End SyncLock
Logger.Trace(Function() $"Worker {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
Worker.Cancel()
End Sub
Public Overrides Sub Failed(Ex As Exception) Handles Worker.Failed
SyncLock LockState
If State >= LoadState.Finished Then Return
State = LoadState.Failed
End SyncLock
Logger.Warn(Ex, $"Worker {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
[Error] = Ex
Worker.Cancel()
End Sub
End Class
''' <summary>
''' 支持多个加载器连续运作的复合加载器。
''' </summary>
@@ -419,7 +514,7 @@
'改变状态
SyncLock LockState
If State = LoadState.Loading Then
Exit Sub
Return
Else
State = LoadState.Loading
End If
@@ -433,23 +528,34 @@
End If
RunInThread(AddressOf Update)
End Sub
Public Overrides Sub Abort()
Public Overrides Sub Cancel()
'改变状态
SyncLock LockState
If State = LoadState.Loading OrElse State = LoadState.Waiting Then
State = LoadState.Aborted
State = LoadState.Canceled
Else
Exit Sub
Return
End If
End SyncLock
RunInThread(
Sub()
'中断加载器
For Each Loader In Loaders
Loader.Abort()
Loader.Cancel()
Next
End Sub)
End Sub
Public Overrides Sub Failed(Ex As Exception)
[Error] = Ex '先设置错误再调整状态,防止父加载器获取不到异常
SyncLock LockState
If State >= LoadState.Finished Then Return
State = LoadState.Failed
End SyncLock
For Each Loader In Loaders
Loader.Cancel()
Next
FrmMain.BtnExtraDownload.ShowRefresh()
End Sub
''' <summary>
''' 子任务状态变更。
@@ -464,38 +570,29 @@
Case LoadState.Finished
'正常结束,触发刷新
Update()
Case LoadState.Aborted
Case LoadState.Canceled
'被中断,这个任务也中断
Abort()
Case Else
Cancel()
Case LoadState.Failed
'完蛋,出错了
SyncLock LockState
If State >= LoadState.Finished Then Exit Sub
[Error] = New Exception(Loader.Name & "失败", Loader.Error)
State = Loader.State
End SyncLock
For Each Loader In Loaders
Loader.Abort()
Next
FrmMain.BtnExtraDownload.ShowRefresh()
Exit Sub
Failed(New Exception(Loader.Name & "失败", Loader.Error))
End Select
End Sub
''' <summary>
''' 触发一次更新,以启动新加载器或完成。
''' </summary>
Private Sub Update()
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Exit Sub
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Canceled Then Return
Dim IsFinished As Boolean = True
Dim Blocked As Boolean = False
Dim Input As Object = Me.Input
For Each Loader In Loaders
Select Case Loader.State
Case LoadState.Finished
'检查是否需要重启
If Loader.GetType.Name.StartsWithF("LoaderTask") Then '类型名后面带有泛型,必须用 StartsWith
'检查 LoaderTask 是否需要重启
If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
Logger.Info($"由于输入条件变更,重启已完成的加载器 {Loader.Name}")
GoTo Restart
End If
'更新下一个加载器的输入
@@ -504,10 +601,10 @@
'如果不让继续启动,且已有加载器正在加载中,就不继续启动
If Loader.Block AndAlso Not IsFinished Then Blocked = True
Case LoadState.Loading
'检查是否需要重启
If Loader.GetType.Name.StartsWithF("LoaderTask") Then
'检查 LoaderTask 是否需要重启
If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
Log("[Loader] 由于输入条件变更,重启进行中的加载器 " & Loader.Name, LogLevel.Developer)
Logger.Warn($"由于输入条件变更,重启进行中的加载器 {Loader.Name}")
GoTo Restart
End If
End If
@@ -521,13 +618,14 @@ Restart:
If Blocked Then Continue For
If Input IsNot Nothing Then
'若输入类型与下一个加载器相同才继续
Dim LoaderType As String = Loader.GetType.Name
If LoaderType.StartsWithF("LoaderTask") OrElse LoaderType.StartsWithF("LoaderCombo") Then
If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) OrElse GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderCombo(Of ))) Then
Loader.Start(If(Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IsForceRestarting)
ElseIf LoaderType.StartsWithF("LoaderDownload") Then
ElseIf TypeOf Loader Is LoaderWorker Then
Loader.Start(IsForceRestart:=IsForceRestarting)
ElseIf TypeOf Loader Is LoaderDownload Then
Loader.Start(If(TypeOf Input Is List(Of NetFile), Input, Nothing), IsForceRestarting)
Else
Throw New Exception("未知的加载器类型(" & LoaderType & ")")
Throw New Exception("未知的加载器类型(" & Loader.GetType.Name & ")")
End If
Else
Loader.Start(IsForceRestart:=IsForceRestarting)
@@ -550,7 +648,7 @@ Restart:
Public Shared Sub GetLoaderList(Loader As Object, ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
For Each SubLoader In Loader.Loaders
If SubLoader.Show OrElse Not RequireShow Then List.Add(SubLoader)
If SubLoader.GetType.Name.StartsWithF("LoaderCombo") Then GetLoaderList(SubLoader, List)
If GeneralUtils.IsGenericInstanceOf(SubLoader, GetType(LoaderCombo(Of ))) Then GetLoaderList(SubLoader, List)
Next
End Sub
''' <summary>
@@ -571,31 +669,26 @@ Restart:
End Class
'任务栏进度条
Public LoaderTaskbar As New SafeList(Of LoaderBase)
Public LoaderTaskbar As New ConcurrentList(Of LoaderBase)
Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
Public Sub LoaderTaskbarAdd(Of T)(Loader As LoaderCombo(Of T))
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
LoaderTaskbar.Add(Loader)
Log($"[Taskbar] {Loader.Name} 已加入任务列表")
Logger.Info($"{Loader.Name} 已加入任务列表")
End Sub
Public Sub LoaderTaskbarProgressRefresh()
Try
Dim NewState As Shell.TaskbarItemProgressState
Dim NewProgress As Double = LoaderTaskbarProgressGet()
'检查任务是否完成,若完成则移除
'外显任务是否已经全部完成
Dim IsAllDownloadTaskCompleted As Boolean = True
For Each Loader In LoaderTaskbar.ToList()
If Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
Next
'若单个任务已中止或全部任务已完成,则刷新并移除
For Each Task In LoaderTaskbar.ToList()
If IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting Then
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task)
'若单个任务已中止,或全部任务已完成,则刷新并移除
For Each Task In LoaderTaskbar
If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
(Task.State = LoadState.Waiting OrElse Task.State = LoadState.Canceled) Then
FrmSpeedLeft?.TaskRefresh(Task)
LoaderTaskbar.Remove(Task)
Log($"[Taskbar] {Task.Name} 已移出任务列表")
Logger.Info($"{Task.Name} 已移出任务列表")
End If
Next
'更新平滑后的进度
@@ -620,15 +713,15 @@ Restart:
FrmMain.BtnExtraDownload.ShowRefresh()
End If
Catch ex As Exception
Log(ex, "刷新任务栏进度显示失败", LogLevel.Feedback)
Logger.Error(ex, "刷新任务栏进度显示失败")
End Try
End Sub
Public Function LoaderTaskbarProgressGet() As Double
Try
If Not LoaderTaskbar.Any Then Return 1
Return MathClamp(LoaderTaskbar.Select(Function(e) e.Progress).Average(), 0, 1)
Return LoaderTaskbar.Select(Function(l) l.Progress).Average().Clamp(0, 1)
Catch ex As Exception
Log(ex, "获取任务栏进度出错", LogLevel.Feedback)
Logger.Error(ex, "获取任务栏进度出错")
Return 0.5
End Try
End Function
@@ -656,11 +749,10 @@ Restart:
''' </summary>
''' <param name="ExtraPath">用于检查文件夹修改的额外路径。该路径不会传入加载器。</param>
Public Function LoaderFolderRun(Loader As LoaderBase, FolderPath As String, Type As LoaderFolderRunType, Optional MaxDepth As Integer = 0, Optional ExtraPath As String = "", Optional WaitForExit As Boolean = False) As Boolean
Dim FolderInfo As DirectoryInfo
Dim Value As New LoaderFolderDictionaryEntry With {.FolderPath = FolderPath & ExtraPath, .LastCheckTime = Nothing}
Try
'获取数据
FolderInfo = New DirectoryInfo(FolderPath & ExtraPath)
Dim FolderInfo = DirectoryUtils.GetInfo(FolderPath & ExtraPath)
Value.LastCheckTime = If(FolderInfo.Exists, GetActualLastWriteTimeUtc(FolderInfo, MaxDepth), DirectCast(Nothing, Date?))
'如果已经检查过,则跳过
If Type = LoaderFolderRunType.RunOnUpdated AndAlso LoaderFolderDictionary.ContainsKey(Loader) Then
@@ -672,7 +764,7 @@ Restart:
End If
End If
Catch ex As Exception
Log(ex, "文件夹加载器启动检测出错")
Logger.Warn(ex, "文件夹加载器启动检测出错")
End Try
'写入检查数据
LoaderFolderDictionary(Loader) = Value
File diff suppressed because it is too large. Load diff
@@ -1,4 +1,4 @@
'提供不同的输入验证方法,名称以 Validate 开头
'提供不同的输入验证方法,名称以 Validate 开头
Public Module ModValidate
''' <summary>
@@ -9,6 +9,7 @@ Public Module ModValidate
Dim Result As String = ""
For Each ValidateRule As Validate In ValidateRules
Result = ValidateRule.Validate(Text)
If Result Is Nothing Then Return ""
If Result <> "" Then Return Result
Next
Return Result
@@ -21,7 +22,7 @@ End Module
''' </summary>
Public MustInherit Class Validate
''' <summary>
''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因。
''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因;若需要中断检查并直接通过,返回 Nothing。
''' </summary>
Public MustOverride Function Validate(Str As String) As String
End Class
@@ -65,6 +66,20 @@ Public Class ValidateNullOrWhiteSpace
End Function
End Class
''' <summary>
''' 自定义的 Validate。
''' </summary>
Public Class ValidateFunc
Inherits Validate
Private ReadOnly _func As Func(Of String, String)
Public Sub New(F As Func(Of String, String))
_func = F
End Sub
Public Overrides Function Validate(Str As String) As String
Return _func(Str)
End Function
End Class
''' <summary>
''' 必须满足正则表达式。
''' </summary>
@@ -79,7 +94,7 @@ Public Class ValidateRegex
Me.ErrorDescription = ErrorDescription
End Sub
Public Overrides Function Validate(Str As String) As String
If Not RegexCheck(Str, Regex) Then Return ErrorDescription
If Not Str.RegexCheck(Regex) Then Return ErrorDescription
Return ""
End Function
End Class
@@ -93,7 +108,7 @@ Public Class ValidateHttp
End Sub '用于 XAML 初始化
Public Overrides Function Validate(Str As String) As String
If Str.EndsWithF("/") Then Str = Str.Substring(0, Str.Length - 1)
If Not RegexCheck(Str, "^(http[s]?)\://") Then Return "输入的网址无效!"
If Not Str.RegexCheck("^(http[s]?)\://") Then Return "输入的网址无效!"
Return ""
End Function
End Class
@@ -113,10 +128,10 @@ Public Class ValidateInteger
End Sub
Public Overrides Function Validate(Str As String) As String
If Str.Length > 9 Then Return "请输入一个大小合理的数字!"
Dim Valed As Integer = Val(Str)
If Valed.ToString <> Str Then Return "请输入一个整数!"
If Val(Str) > Max Then Return "不可超过 " & Max & "!"
If Val(Str) < Min Then Return "不可低于 " & Min & "!"
Dim Valed As Integer
If Not Integer.TryParse(Str, Valed) Then Return "请输入一个整数!"
If Valed > Max Then Return "不可超过 " & Max & "!"
If Valed < Min Then Return "不可低于 " & Min & "!"
Return ""
End Function
End Class
@@ -135,9 +150,9 @@ Public Class ValidateLength
Me.Max = Max
End Sub
Public Overrides Function Validate(Str As String) As String
If Len(Str) <> Max AndAlso Max = Min Then Return "长度必须为 " & Max & " 个字符!"
If Len(Str) > Max Then Return "长度最长为 " & Max & " 个字符!"
If Len(Str) < Min Then Return "长度至少需 " & Min & " 个字符!"
If Len(Str) <> Max AndAlso Max = Min Then Return $"长度必须为 {Max} 个字符!"
If Len(Str) > Max Then Return $"长度最长为 {Max} 个字符!"
If Len(Str) < Min Then Return $"长度至少需 {Min} 个字符!"
Return ""
End Function
End Class
@@ -147,21 +162,24 @@ End Class
''' </summary>
Public Class ValidateExcept
Inherits Validate
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
Public Property Excepts As New ObjectModel.Collection(Of String)
Public Property ErrorMessage As String
Public Sub New()
ErrorMessage = "输入内容不能包含 %!"
ErrorMessage = "输入内容不能包含 %"
End Sub '用于 XAML 初始化
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %!")
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
Me.Excepts = Excepts
Me.ErrorMessage = ErrorMessage
End Sub
Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能包含 %!")
Me.Excepts = New ObjectModel.Collection(Of String)
Me.ErrorMessage = ErrorMessage
For Each Data As String In Excepts
Me.Excepts.Add(Data)
Next
Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
Me.New(
New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
ErrorMessage)
End Sub
Public Sub New(Excepts As IEnumerable(Of Char), Optional ErrorMessage As String = "输入内容不能包含 %")
Me.New(
New ObjectModel.Collection(Of String)(Excepts.Select(Function(c) c.ToString).ToList),
ErrorMessage)
End Sub
Public Overrides Function Validate(Str As String) As String
For Each Ch As String In Excepts
@@ -180,29 +198,31 @@ End Class
''' </summary>
Public Class ValidateExceptSame
Inherits Validate
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
Public Property Excepts As New ObjectModel.Collection(Of String)
Public Property ErrorMessage As String
Public Property IgnoreCase As Boolean = False
Public Sub New()
End Sub
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
Me.Excepts = Excepts
Me.ErrorMessage = ErrorMessage
Me.IgnoreCase = IgnoreCase
End Sub
Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
Me.Excepts = New ObjectModel.Collection(Of String)
For Each Data As String In Excepts
Me.Excepts.Add(Data)
Next
Me.ErrorMessage = ErrorMessage
Me.IgnoreCase = IgnoreCase
Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
Me.New(
New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
ErrorMessage, IgnoreCase)
End Sub
Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
Me.New(
New ObjectModel.Collection(Of String) From {Except},
ErrorMessage, IgnoreCase)
End Sub
Public Overrides Function Validate(Str As String) As String
If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
For Each Ch As String In Excepts
If IgnoreCase Then
If Str.ToLower = Ch.ToLower Then Return ErrorMessage.Replace("%", Ch)
If Str.Lower = Ch.Lower Then Return ErrorMessage.Replace("%", Ch)
Else
If Str.Equals(Ch) Then Return ErrorMessage.Replace("%", Ch) '使用 = 不确定是否会忽略大小写
End If
@@ -212,57 +232,80 @@ Public Class ValidateExceptSame
End Class
''' <summary>
''' 必须为特定字符串。
''' </summary>
Public Class ValidateSame
Inherits Validate
Public Property Required As String
Public Property ErrorMessage As String
Public Property IgnoreCase As Boolean = False
Public Sub New()
End Sub
Public Sub New(Required As String, Optional ErrorMessage As String = "输入内容有误", Optional IgnoreCase As Boolean = False)
Me.Required = Required
Me.ErrorMessage = ErrorMessage
Me.IgnoreCase = IgnoreCase
End Sub
Public Overrides Function Validate(Str As String) As String
Return If(String.Compare(Str, Required, IgnoreCase) = 0, "", ErrorMessage)
End Function
End Class
''' <summary>
''' 对文件夹名的粗略的特化检测。
''' </summary>
Public Class ValidateFolderName
Inherits Validate
Public Property Path As String
Public Property Folder As String
Public Property UseMinecraftCharCheck As Boolean = True
Public Property IgnoreCase As Boolean = True
Private ReadOnly PathIgnore As IEnumerable(Of DirectoryInfo)
Private ReadOnly PathIgnore As IEnumerable(Of String)
Private ReadOnly IgnoreList As List(Of String)
Public Sub New()
End Sub
Public Sub New(Path As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreCase As Boolean = True)
Me.Path = Path
Me.IgnoreCase = IgnoreCase
Public Sub New(Folder As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreList As List(Of String) = Nothing)
Me.Folder = Folder
Me.UseMinecraftCharCheck = UseMinecraftCharCheck
On Error Resume Next
PathIgnore = New DirectoryInfo(Path).EnumerateDirectories
PathIgnore = DirectoryUtils.EnumerateDirectories(Folder)
Me.IgnoreList = IgnoreList
End Sub
Public Overrides Function Validate(Str As String) As String
Try
'检查是否为空
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
If Not LengthCheck = "" Then Return LengthCheck
If LengthCheck <> "" Then Return LengthCheck
'检查空格
If Str.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
If Str.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!"
'检查长度
LengthCheck = New ValidateLength(1, 100).Validate(Str)
If Not LengthCheck = "" Then Return LengthCheck
If LengthCheck <> "" Then Return LengthCheck
'检查尾部小数点
If Str.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!"
'检查特殊字符
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
If Not CharactCheck = "" Then Return CharactCheck
Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
If CharactCheck <> "" Then Return CharactCheck
'检查特殊字符串
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
If InvalidStrCheck <> "" Then Return InvalidStrCheck
'检查 NTFS 8.3 文件名(#4505)
If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
If Str.RegexCheck(".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
'检查文件夹重名
Dim Arr As New List(Of String)
Dim folderName As String
If PathIgnore IsNot Nothing Then
For Each Folder As DirectoryInfo In PathIgnore
Arr.Add(Folder.Name)
For Each FolderPath In PathIgnore
folderName = PathUtils.GetLastPart(FolderPath)
If IgnoreList Is Nothing OrElse Not IgnoreList.Contains(folderName, StringComparer.OrdinalIgnoreCase) Then Arr.Add(folderName)
Next
End If
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", IgnoreCase).Validate(Str)
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", True).Validate(Str)
If Not SameNameCheck = "" Then Return SameNameCheck
Return ""
Catch ex As Exception
Log(ex, "检查文件夹名出错")
Logger.Warn(ex, "检查文件夹名出错")
Return "错误:" & ex.Message
End Try
End Function
@@ -295,20 +338,20 @@ Public Class ValidateFileName
If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!"
'检查长度
LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, ""))
If Not LengthCheck = "" Then Return LengthCheck
If LengthCheck <> "" Then Return LengthCheck
'检查尾部小数点
If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!"
'检查特殊字符
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
If Not CharactCheck = "" Then Return CharactCheck
Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
If CharactCheck <> "" Then Return CharactCheck
'检查特殊字符串
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
If InvalidStrCheck <> "" Then Return InvalidStrCheck
'检查 NTFS 8.3 文件名(#4505)
If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
If Str.RegexCheck(".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
'检查文件重名
If ParentFolder IsNot Nothing Then
Dim DirInfo As New DirectoryInfo(ParentFolder)
Dim DirInfo = DirectoryUtils.GetInfo(ParentFolder)
If DirInfo.Exists Then
Dim SameNameCheck = New ValidateExceptSame(DirInfo.EnumerateFiles("*").Select(Function(f) f.Name),
"不可与现有文件重名!", IgnoreCase).Validate(Str)
@@ -319,7 +362,7 @@ Public Class ValidateFileName
End If
Return ""
Catch ex As Exception
Log(ex, "检查文件名出错")
Logger.Warn(ex, "检查文件名出错")
Return "错误:" & ex.Message
End Try
End Function
@@ -339,18 +382,22 @@ Public Class ValidateFolderPath
Public Overrides Function Validate(Str As String) As String
'去除尾部斜线,统一为 \
Str = Str.Replace("/", "\")
If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
If Not PathUtils.RemoveSlashSuffix(Str).EndsWithF(":") Then Str = PathUtils.RemoveSlashSuffix(Str)
'检查是否为空
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
If Not LengthCheck = "" Then Return LengthCheck
If LengthCheck <> "" Then Return LengthCheck
'检查长度
LengthCheck = New ValidateLength(1, 254).Validate(Str)
If Not LengthCheck = "" Then Return LengthCheck
If LengthCheck <> "" Then Return LengthCheck
'检查 NTFS 8.3 文件名(#4505)
Dim Match = Str.RegexSeek(".{2,}~\d")
If Match IsNot Nothing Then Return $"文件夹名不能包含 {Match} 这一特殊格式!"
'检查开头
If Str.StartsWithF("\\Mac\") Then GoTo Fin
For Each Drive As DriveInfo In My.Computer.FileSystem.Drives
If Str.ToUpper = Drive.Name Then Return ""
If Str.StartsWithF(Drive.Name, True) Then GoTo Fin
For Each Drive In Environment.GetLogicalDrives().Where(Function(p) DirectoryUtils.Exists(p))
Dim DriveName As String = Drive.Upper.First & ":\"
If Str.Upper = DriveName Then Return ""
If Str.StartsWithF(DriveName, True) Then GoTo Fin
Next
Return "文件夹路径头存在错误!"
Fin:
@@ -361,7 +408,7 @@ Fin:
Dim SubLengthCheck As String = New ValidateNullOrWhiteSpace().Validate(SubStr)
If Not SubLengthCheck = "" Then Return "文件夹路径存在错误!"
'检查特殊字符
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "路径中存在无效字符!").Validate(SubStr)
Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "路径中存在无效字符!").Validate(SubStr)
If Not CharactCheck = "" Then Return CharactCheck
'检查头部空格
If SubStr.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
@@ -371,8 +418,6 @@ Fin:
'检查特殊字符串
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!").Validate(SubStr)
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
'检查 NTFS 8.3 文件名(#4505)
If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
Next
Return ""
End Function
+23 -22
View File
@@ -1,14 +1,9 @@
'一个万能的自动图片类型转换工具类
'一个万能的自动图片类型转换工具类
Imports System.Drawing.Imaging
Public Class MyBitmap
''' <summary>
''' 位图缓存。
''' </summary>
Public Shared BitmapCache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap)
''' <summary>
''' 存储的图片
''' </summary>
@@ -30,14 +25,17 @@ Public Class MyBitmap
End Operator
Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
If Image Is Nothing Then Return Nothing
Dim Bitmap = Image.Pic
Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
Dim bitmapData = Bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
Dim BitmapPic As System.Drawing.Bitmap = Image.Pic
Dim rect = New System.Drawing.Rectangle(0, 0, BitmapPic.Width, BitmapPic.Height)
Dim bitmapData = BitmapPic.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
Try
Dim size = rect.Width * rect.Height * 4
Return BitmapSource.Create(Bitmap.Width, Bitmap.Height, Bitmap.HorizontalResolution, Bitmap.VerticalResolution, PixelFormats.Bgra32, Nothing, bitmapData.Scan0, size, bitmapData.Stride)
Dim Result = BitmapSource.Create(BitmapPic.Width, BitmapPic.Height, BitmapPic.HorizontalResolution, BitmapPic.VerticalResolution,
PixelFormats.Bgra32, Nothing, bitmapData.Scan0,
rect.Width * rect.Height * 4, bitmapData.Stride)
Result.Freeze()
Return Result
Finally
Bitmap.UnlockBits(bitmapData)
BitmapPic.UnlockBits(bitmapData)
End Try
End Operator
Public Shared Widening Operator CType(Image As System.Drawing.Bitmap) As MyBitmap
@@ -65,15 +63,16 @@ Public Class MyBitmap
FilePathOrResourceName = FilePathOrResourceName.Replace("pack://application:,,,/images/", PathImage)
If FilePathOrResourceName.StartsWithF(PathImage) Then
'使用缓存
If BitmapCache.ContainsKey(FilePathOrResourceName) Then
Pic = BitmapCache(FilePathOrResourceName).Pic
Static Cache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap)
If Cache.ContainsKey(FilePathOrResourceName) Then
Pic = Cache(FilePathOrResourceName).Pic
Else
Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
BitmapCache.TryAdd(FilePathOrResourceName, Pic)
Cache.TryAdd(FilePathOrResourceName, Pic)
End If
Else
'使用这种自己接管 FileStream 的方法加载才能解除文件占用
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open)
Using InputStream = FileUtils.ReadAsStream(FilePathOrResourceName)
'判断是否为 WebP 文件头
Dim Header(1) As Byte
InputStream.Read(Header, 0, 2)
@@ -92,9 +91,13 @@ Public Class MyBitmap
Pic = My.Application.TryFindResource(FilePathOrResourceName)
If Pic Is Nothing Then
Pic = New System.Drawing.Bitmap(1, 1)
Throw New Exception($"加载 MyBitmap 失败({FilePathOrResourceName})", ex)
If TypeOf ex Is ArgumentException Then
Throw New Exception($"图片格式不支持,或图片文件损坏({FilePathOrResourceName})", ex)
Else
Throw New Exception($"加载 MyBitmap 意外失败({FilePathOrResourceName})", ex)
End If
Else
Log(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})", LogLevel.Developer)
Logger.Warn(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})")
Exit Try
End If
End Try
@@ -123,9 +126,7 @@ Public Class MyBitmap
End Sub
Private Class WebPDecoder '将代码隔离在另外一个类中,这样只要不调用这个方法就不会加载 Imazen.WebP.dll
Public Shared Function DecodeFromBytes(Bytes As Byte()) As System.Drawing.Bitmap
If Is32BitSystem Then Throw New Exception("不支持在 32 位系统下加载 WebP 图片。")
Dim Decoder As New Imazen.WebP.SimpleDecoder()
Return Decoder.DecodeFromBytes(Bytes, Bytes.Length)
Return New Imazen.WebP.SimpleDecoder().DecodeFromBytes(Bytes, Bytes.Length)
End Function
End Class
@@ -159,7 +160,7 @@ Public Class MyBitmap
Public Sub Save(FilePath As String)
Dim encoder As BitmapEncoder = New PngBitmapEncoder()
encoder.Frames.Add(BitmapFrame.Create(Me))
Using fileStream As New FileStream(FilePath, FileMode.Create)
Using fileStream = FileUtils.CreateAsStream(FilePath)
encoder.Save(fileStream)
End Using
End Sub
@@ -0,0 +1,54 @@
Public Class PclLogger
Inherits FileLogger
''' <inheritdoc/>
Public Overrides Function Format(Text As String, Level As LogLevel, FilePath As String, Ex As Exception) As String
Text = MyBase.Format(Text, Level, FilePath, Ex)
Text = FilterUserName(FilterAccessToken(Text, "*"c), "*"c)
Return Text
End Function
''' <inheritdoc/>
Public Overrides Sub HandleBehavior(RawMessage As String, FormattedMessage As String, Behavior As LogBehavior, Ex As Exception)
If IsProgramEnding Then Return
MyBase.HandleBehavior(RawMessage, FormattedMessage, Behavior, Ex)
Dim BriefText = If(Ex Is Nothing, RawMessage, If(RawMessage Is Nothing, "", $"{RawMessage}:") & Ex.GetDisplay(False))
BriefText = FilterUserName(FilterAccessToken(BriefText, "*"c), "*"c)
Dim DetailText = If(Ex Is Nothing, RawMessage, If(RawMessage Is Nothing, "", $"{RawMessage}:") & Ex.GetDisplay(True))
DetailText = FilterUserName(FilterAccessToken(DetailText, "*"c), "*"c)
Select Case Behavior
Case LogBehavior.None
'啥也不干
Case LogBehavior.ToastIfDebug
If BuildType = BuildTypes.Debug OrElse ModeDebug Then Hint("[调试模式] " & BriefText, HintType.Blue, False)
Case LogBehavior.Toast
Hint(BriefText, HintType.Red, False)
Case LogBehavior.Alert
MyMsgBox(DetailText, "错误", IsWarn:=True)
Case LogBehavior.AlertThenFeedback
If PageSetupSystem.IsLauncherNewest Then
If MyMsgBox(DetailText & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", "错误", "反馈", "取消", IsWarn:=True) = 1 Then Feedback(False, True)
Else
MyMsgBox(DetailText & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", "错误", IsWarn:=True)
End If
Case LogBehavior.AlertThenCrash
Static FirstTrigger As Boolean = True
If FirstTrigger Then
'首次触发
FirstTrigger = False
If PageSetupSystem.IsLauncherNewest Then
If MsgBox(DetailText & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, "错误") = MsgBoxResult.Yes Then Feedback(False, True)
Else
MsgBox(DetailText & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, "错误")
End If
Else
'多次触发,直接使程序崩溃(这通常代表着在其他线程循环触发严重异常)
Thread.Sleep(2000)
End If
FormMain.EndProgramForce(ProcessReturnValues.Exception)
End Select
'遥测
If Behavior >= LogBehavior.Toast Then Telemetry("错误日志", "Exception", DetailText)
End Sub
End Class
Loaded 100 of 277 files, more files were not shown because too many files have changed in this diff. Show more