mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
394 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0ecd048b6 | ||
|
|
91010d0d8b | ||
|
|
fc771eb90a | ||
|
|
80f2fed722 | ||
|
|
bdb406c451 | ||
|
|
5bc957c6a5 | ||
|
|
416c6f15a6 | ||
|
|
9eed633e05 | ||
|
|
7e30173990 | ||
|
|
2200e2e58e | ||
|
|
b8886c5cd3 | ||
|
|
43007d8fb4 | ||
|
|
88684bff00 | ||
|
|
0c7ce7a72f | ||
|
|
075d92f754 | ||
|
|
a0cba171cc | ||
|
|
f41185310b | ||
|
|
2a4c93d241 | ||
|
|
c0980fabe8 | ||
|
|
f2ba316059 | ||
|
|
e4e9dd91f1 | ||
|
|
749ef0e138 | ||
|
|
24086ee4d0 | ||
|
|
aeb6962ae4 | ||
|
|
87e5ede91f | ||
|
|
91de6d170e | ||
|
|
3057673cdb | ||
|
|
c3ace405ac | ||
|
|
0b48581e65 | ||
|
|
4ab129e4a2 | ||
|
|
13ad36f5b4 | ||
|
|
f026321aa8 | ||
|
|
d1dfdf107b | ||
|
|
59f8895675 | ||
|
|
4cb3d5f03f | ||
|
|
067c7d7c4d | ||
|
|
1cc072ba28 | ||
|
|
0e7afa8efb | ||
|
|
b753728b7e | ||
|
|
df019da891 | ||
|
|
c6435f30eb | ||
|
|
3ac0be4220 | ||
|
|
24f6a33256 | ||
|
|
dc9278eb4f | ||
|
|
4b2c82db62 | ||
|
|
70f30edd7c | ||
|
|
c8e8213df6 | ||
|
|
7cad996902 | ||
|
|
eec47b72c7 | ||
|
|
5943b1a1fb | ||
|
|
9f9a5670bc | ||
|
|
10ba927136 | ||
|
|
07d42cedd1 | ||
|
|
07c52019f4 | ||
|
|
77cb2fc603 | ||
|
|
b5c16e2dae | ||
|
|
29c954b032 | ||
|
|
8df5d5d6eb | ||
|
|
827d944987 | ||
|
|
4c47f3c08b | ||
|
|
6540cc4577 | ||
|
|
df22d30a96 | ||
|
|
5ea9dd533f | ||
|
|
744c1079e1 | ||
|
|
1539863415 | ||
|
|
3527e43118 | ||
|
|
8388def548 | ||
|
|
d0bfbfa505 | ||
|
|
fa640d27f0 | ||
|
|
389c1417f7 | ||
|
|
6dcacb1bf4 | ||
|
|
4ffd09cce8 | ||
|
|
0dcbac3ee1 | ||
|
|
b1ea5332fc | ||
|
|
b34dab0f99 | ||
|
|
791e517e39 | ||
|
|
4408e3994e | ||
|
|
32cbbefe1a | ||
|
|
d754c0d117 | ||
|
|
4c75295f2c | ||
|
|
34e5312d75 | ||
|
|
465d6b631e | ||
|
|
0d2d1b8115 | ||
|
|
981949651e | ||
|
|
874dac1119 | ||
|
|
3f0694b28e | ||
|
|
6b166b6aed | ||
|
|
b351231c84 | ||
|
|
0603b24466 | ||
|
|
f97ad4eac0 | ||
|
|
28fc4558be | ||
|
|
ea3391b112 | ||
|
|
ec95e42d7d | ||
|
|
e9f12aeb09 | ||
|
|
04850dd136 | ||
|
|
4782d61ed0 | ||
|
|
28ade90926 | ||
|
|
dde97b6489 | ||
|
|
44fe729e1a | ||
|
|
2a1d814cc5 | ||
|
|
c1ee37bd8f | ||
|
|
65b81f0ad8 | ||
|
|
91fea88623 | ||
|
|
026c68229a | ||
|
|
91b2db886f | ||
|
|
101d316525 | ||
|
|
59d62f931d | ||
|
|
f0bb19bc07 | ||
|
|
c0b05e2c2f | ||
|
|
fc02f833a0 | ||
|
|
7b8ebd86b1 | ||
|
|
47b24286b1 | ||
|
|
0e3e3b9e4a | ||
|
|
85d7b22e11 | ||
|
|
7caeb17788 | ||
|
|
b11b90e9f1 | ||
|
|
58643a60b5 | ||
|
|
a29b487c26 | ||
|
|
1bd6023e0a | ||
|
|
579173d464 | ||
|
|
9ed53e8c34 | ||
|
|
830556a043 | ||
|
|
7ba27e184f | ||
|
|
9aa6a2b57b | ||
|
|
5773902f4a | ||
|
|
06c5bcad3e | ||
|
|
c0165c57fd | ||
|
|
4e57520115 | ||
|
|
17c3480dae | ||
|
|
7d5faadbb5 | ||
|
|
b7df968ea7 | ||
|
|
5f6cc46774 | ||
|
|
a34b5a5101 | ||
|
|
457e3c4af2 | ||
|
|
26143079b3 | ||
|
|
20ad9aec60 | ||
|
|
61dd098d95 | ||
|
|
e7233fbf2a | ||
|
|
8de526274a | ||
|
|
5b1fb6e1dd | ||
|
|
1584fd1428 | ||
|
|
65179a340f | ||
|
|
9f984b4a9b | ||
|
|
8a36bfb62c | ||
|
|
c9c8f6a5cf | ||
|
|
f9e1ef7ab5 | ||
|
|
be06a91e0f | ||
|
|
174757f94d | ||
|
|
1cd3ec0cdb | ||
|
|
7b17bb5a01 | ||
|
|
c3bab3c677 | ||
|
|
17f1c873ae | ||
|
|
313a17b7eb | ||
|
|
9ad321e10d | ||
|
|
f18dfc0baf | ||
|
|
16946c58fe | ||
|
|
c7b931956c | ||
|
|
6c0c5b4669 | ||
|
|
0342805293 | ||
|
|
e0b1896336 | ||
|
|
6d1958af4c | ||
|
|
42ab2c3000 | ||
|
|
d0fb00dccd | ||
|
|
5bc484db78 | ||
|
|
c665834f77 | ||
|
|
81d73568a7 | ||
|
|
c940ec7006 | ||
|
|
71e5e6f190 | ||
|
|
3983eaec0f | ||
|
|
9392911547 | ||
|
|
f3d1fda053 | ||
|
|
14c3a14c2d | ||
|
|
31568ffba9 | ||
|
|
3d3d004441 | ||
|
|
a40e5f2a80 | ||
|
|
186da74704 | ||
|
|
c532799eb1 | ||
|
|
595e19a1df | ||
|
|
c376c4e0e8 | ||
|
|
07f0c37b2c | ||
|
|
ecdaeccc42 | ||
|
|
64bbcdfc80 | ||
|
|
59fccf0cba | ||
|
|
f860211632 | ||
|
|
a15a8cfe2d | ||
|
|
38bed7318f | ||
|
|
e513b8b00b | ||
|
|
1e711172af | ||
|
|
8beb47fd2c | ||
|
|
41ab80511e | ||
|
|
318b6190d2 | ||
|
|
6e3dbd41c7 | ||
|
|
911f667e98 | ||
|
|
db6537bbe4 | ||
|
|
6b640b5e15 | ||
|
|
bfe9837b36 | ||
|
|
b8255e77a3 | ||
|
|
500888c2d1 | ||
|
|
5141d4a567 | ||
|
|
a86f5e1b59 | ||
|
|
390b89fc82 | ||
|
|
6d6d97c59a | ||
|
|
df23fe54d6 | ||
|
|
8caef9af0c | ||
|
|
aac0e268b0 | ||
|
|
bafee4ba18 | ||
|
|
4a1dccd7ac | ||
|
|
1f8e73f255 | ||
|
|
53c1a0156d | ||
|
|
165b77aa80 | ||
|
|
47cd9807b2 | ||
|
|
cbaf1ef5ae | ||
|
|
c2d3bd1798 | ||
|
|
bfa5325a09 | ||
|
|
43aebf7d0c | ||
|
|
ffcfb8cd65 | ||
|
|
7d9612cf13 | ||
|
|
4fb997c8d0 | ||
|
|
6029acc7f1 | ||
|
|
ef352303e7 | ||
|
|
b5579aef6e | ||
|
|
9c20e06387 | ||
|
|
323e9fbcff | ||
|
|
10d947aa61 | ||
|
|
c18581fc34 | ||
|
|
b72b5ddf91 | ||
|
|
2821f7f2af | ||
|
|
427275d122 | ||
|
|
22393b612c | ||
|
|
ffc380f96d | ||
|
|
2a757dce51 | ||
|
|
0921671de4 | ||
|
|
5a99488582 | ||
|
|
a14c41d10f | ||
|
|
ce8aee2cb1 | ||
|
|
f9ffd84429 | ||
|
|
6e69064140 | ||
|
|
ff315a3b58 | ||
|
|
aaf28132a4 | ||
|
|
5730c82f84 | ||
|
|
a71fd42d6a | ||
|
|
3f818fce50 | ||
|
|
301ead44a2 | ||
|
|
ff79e81ff4 | ||
|
|
d9e942319c | ||
|
|
4787798825 | ||
|
|
fb398dbf21 | ||
|
|
68a6834d39 | ||
|
|
4285abd531 | ||
|
|
8b5f017bea | ||
|
|
add9318a8e | ||
|
|
997fcb0e51 | ||
|
|
bd0832ca98 | ||
|
|
4545017a3b | ||
|
|
d28cd36162 | ||
|
|
cf8a9581d6 | ||
|
|
b5d12f5f5f | ||
|
|
7ddf51ef91 | ||
|
|
c2c529f399 | ||
|
|
63028170b8 | ||
|
|
156740de14 | ||
|
|
db82cd70ee | ||
|
|
d04f6a9218 | ||
|
|
f2365604dc | ||
|
|
f569f24213 | ||
|
|
ca468365ab | ||
|
|
5fa8cc37e8 | ||
|
|
ab78f68df1 | ||
|
|
62fdd89943 | ||
|
|
7fa19dd830 | ||
|
|
f4100f1c20 | ||
|
|
7f595a6981 | ||
|
|
60f2020b6c | ||
|
|
d21d59efe1 | ||
|
|
c14847b330 | ||
|
|
86481b747d | ||
|
|
2765a2e744 | ||
|
|
261794927c | ||
|
|
68d8baa70c | ||
|
|
5e44dade7f | ||
|
|
529eafc4e7 | ||
|
|
be3fa7054c | ||
|
|
01cf457386 | ||
|
|
fb496497bf | ||
|
|
68a6af9341 | ||
|
|
1ec5b2fa5e | ||
|
|
aad57b67a6 | ||
|
|
9262217271 | ||
|
|
b09e8b6bdc | ||
|
|
7c51a8ea6b | ||
|
|
3db52e8184 | ||
|
|
9e660a7ae7 | ||
|
|
4127a1daa3 | ||
|
|
4e07af8395 | ||
|
|
02707c3075 | ||
|
|
98ca533706 | ||
|
|
808b643b9f | ||
|
|
d9705b94d4 | ||
|
|
511471d06c | ||
|
|
3b79d89f59 | ||
|
|
fc1546e17f | ||
|
|
b816b0103d | ||
|
|
51570038bc | ||
|
|
0b5b911709 | ||
|
|
1a22303dec | ||
|
|
51a2fe7d88 | ||
|
|
90c6c8ebc7 | ||
|
|
ed1d6240bf | ||
|
|
adf01172e0 | ||
|
|
2caf1145c9 | ||
|
|
33b2725a95 | ||
|
|
0103834e82 | ||
|
|
8273cd973d | ||
|
|
53c484c577 | ||
|
|
706f894ea5 | ||
|
|
7f31010041 | ||
|
|
70a17f325e | ||
|
|
a4d450a98e | ||
|
|
cce08ff728 | ||
|
|
669713389a | ||
|
|
be82bc5d82 | ||
|
|
aa01154301 | ||
|
|
2ff8d6d7f4 | ||
|
|
79f62bdbd2 | ||
|
|
92c010f660 | ||
|
|
18433814e8 | ||
|
|
79c8cc78c6 | ||
|
|
6e20c15c97 | ||
|
|
8b3a311fc2 | ||
|
|
01e2e21979 | ||
|
|
38b5e4f244 | ||
|
|
5fae9f6291 | ||
|
|
f23b1c012f | ||
|
|
16fb1772ab | ||
|
|
52353b7a5a | ||
|
|
8c63a38249 | ||
|
|
91d90cb3fa | ||
|
|
583717d601 | ||
|
|
dd34815b13 | ||
|
|
8f18a89727 | ||
|
|
759689e2c9 | ||
|
|
396da901a1 | ||
|
|
f2c38bc72a | ||
|
|
b5ddc20d5f | ||
|
|
f5af6e7b1a | ||
|
|
772668a266 | ||
|
|
c342efe4be | ||
|
|
9ff322cceb | ||
|
|
d2e8e6dac3 | ||
|
|
1cbf3b548f | ||
|
|
ffd7468913 | ||
|
|
1029175071 | ||
|
|
b5f3cb92b2 | ||
|
|
8d8a2af063 | ||
|
|
43bee2bd3e | ||
|
|
abce4571ea | ||
|
|
904335cfc9 | ||
|
|
e9a821e4af | ||
|
|
8f2d2773e1 | ||
|
|
4352ba64f8 | ||
|
|
670c4366c4 | ||
|
|
46c6a973c6 | ||
|
|
455505f0cb | ||
|
|
8e625e19ee | ||
|
|
1fefcacd9a | ||
|
|
2b585964f7 | ||
|
|
6e541dfeb5 | ||
|
|
2c9af48cb2 | ||
|
|
d384915c9d | ||
|
|
1d923f4b24 | ||
|
|
0997532564 | ||
|
|
dd9053a11c | ||
|
|
a8bf268fce | ||
|
|
44d1dd50e6 | ||
|
|
14e98a43f6 | ||
|
|
69239674e6 | ||
|
|
4e972f35dd | ||
|
|
7187b913b0 | ||
|
|
4904ab1335 | ||
|
|
d572bc5753 | ||
|
|
950c4568ee | ||
|
|
a7bf82d7e7 | ||
|
|
42035803c1 | ||
|
|
3161964bcf | ||
|
|
4b2d6cc27c | ||
|
|
44068c4446 | ||
|
|
21399b3781 | ||
|
|
dcea46edf4 | ||
|
|
825cc58955 | ||
|
|
743cb9683b | ||
|
|
2420916509 | ||
|
|
479b4086c2 | ||
|
|
0626cc0a49 | ||
|
|
93040e6ff8 |
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,8 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [DGP-Studio]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
open_collective: snaphutao
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
name: BUG Report 问题反馈
|
||||
description: 告诉我们你的问题
|
||||
name: 问题反馈
|
||||
description: 通过这个议题向开发团队反馈你发现的程序中的问题
|
||||
title: "[Bug]: 在这里填写一个合适的标题"
|
||||
labels: ["BUG"]
|
||||
labels: ["BUG", "priority:none"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **请在上方设置一个合适的工单标题**
|
||||
> **请在上方以一句话简短地概括你的问题作为标题**
|
||||
> 请按下方的要求填写完整的问题表单,以便我们更快的定位问题。
|
||||
|
||||
- type: checkboxes
|
||||
@@ -14,17 +14,16 @@ body:
|
||||
attributes:
|
||||
label: 检查清单
|
||||
description: |-
|
||||
请确保你已完整执行检查清单,否则你的 Issue 可能会被忽略
|
||||
请确保你已完整执行检查清单,否则你的议题可能会被忽略
|
||||
options:
|
||||
- label: 我并未完整阅读[胡桃工具箱文档](https://hut.ao/advanced/FAQ.html)
|
||||
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
|
||||
required: true
|
||||
|
||||
- label: 我不知道文档站的导航栏中有**搜索功能**,也没有搜索过相关关键词
|
||||
- label: 我知道文档站的导航栏中有**搜索功能**,且已经搜索过相关关键词
|
||||
required: true
|
||||
|
||||
- label: 我使用的操作系统是不[受支持的版本](https://hut.ao/quick-start.html#%E6%9C%80%E4%BD%8E%E7%B3%BB%E7%BB%9F%E8%A6%81%E6%B1%82)
|
||||
|
||||
- label: 我没有**通过搜索功能**确认其他人提出过相同或类似的问题
|
||||
|
||||
- label: 我不明白上述的勾选项是**一个有助于快速排查问题的检查清单**,而是随手确认的选项
|
||||
- label: 我的问题不是[已修复](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E4%BF%AE%E5%A4%8D)的问题也不是一个别人已发布的**重复的**问题
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: winver
|
||||
@@ -32,7 +31,7 @@ body:
|
||||
label: Windows 版本
|
||||
description: |
|
||||
`Win+R` 输入 `winver` 回车后在打开的窗口第二行可以找到
|
||||
placeholder: 例:22621.1105
|
||||
placeholder: 例:22000.556
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -52,6 +51,7 @@ body:
|
||||
description: |
|
||||
在胡桃工具箱的设置界面,你可以找到并复制你的设备 ID
|
||||
如果你的问题涉及程序崩溃,请填写该项,这将有助于我们定位问题
|
||||
如果你的程序已经无法启动,请下载并运行[此PowerShell脚本](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1),它将显示你的设备 ID
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -66,13 +66,14 @@ body:
|
||||
- 角色信息面板
|
||||
- 游戏启动器
|
||||
- 实时便笺
|
||||
- 养成计算器
|
||||
- 用户面板
|
||||
- 养成计算
|
||||
- 文件缓存
|
||||
- 祈愿记录
|
||||
- 玩家查询
|
||||
- 胡桃数据库
|
||||
- 用户界面
|
||||
- 胡桃云
|
||||
- 胡桃帐号
|
||||
- 签到
|
||||
- Wiki
|
||||
- 公告
|
||||
@@ -84,7 +85,7 @@ body:
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: 发生了什么?
|
||||
description: 详细的描述问题发生前后的行为,以便我们解决问题
|
||||
description: 详细的描述问题发生前后的行为,以便我们解决问题。**如果你的问题涉及程序崩溃,你应当检查 Windows 事件查看器,并将相关的 `.Net 错误`详情附上**
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -95,4 +96,13 @@ body:
|
||||
description: 详细的描述你期望发生的行为,突出与目前(可能不正确的)行为的不同
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist-final
|
||||
attributes:
|
||||
label: 最后一步
|
||||
description: 回顾你的回答
|
||||
options:
|
||||
- label: 我认为上述的描述已经足以详细,以允许开发人员能复现该问题
|
||||
required: true
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/CHS-feature-request.yml
vendored
Normal file
27
.github/ISSUE_TEMPLATE/CHS-feature-request.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: 功能请求
|
||||
description: 通过这个议题来向开发团队分享你的想法
|
||||
title: "[Feat]: 在这里填写一个合适的标题"
|
||||
labels: ["功能", "needs-triage", "priority:none"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
请按下方的要求填写完整的问题表单。
|
||||
|
||||
- type: textarea
|
||||
id: back
|
||||
attributes:
|
||||
label: 背景与动机
|
||||
description: 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的议题
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: req
|
||||
attributes:
|
||||
label: 想要实现或优化的功能
|
||||
description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,5 +1,5 @@
|
||||
name: 网络问题
|
||||
description: 当网络问题影响到你的程序使用时
|
||||
description: 通过这个议题来反馈网络问题
|
||||
title: "[Network]: 在这里填写一个合适的标题"
|
||||
labels: ["area-Network"]
|
||||
assignees:
|
||||
@@ -19,10 +19,10 @@ body:
|
||||
description: |
|
||||
停下!
|
||||
**在填写下面的问题之前请先使用我们的网络诊断工具**
|
||||
**这个工具将会生成一份报告,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
|
||||
**这个工具将会生成一份报告并加密压缩,请将这份报告拖入下面的框中,让其与你的工单一起被上传提交**
|
||||
- 你可以点击下面的链接以下载网络诊断工具:
|
||||
- [胡桃资源站](https://d.hut.ao/d/tools/network-diagnosis-hutao.exe)
|
||||
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/network-diagnosis-hutao.exe)
|
||||
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/SH-Network-Diagnosis.exe)
|
||||
- [极狐 GitLab](https://jihulab.com/DGP-Studio/network-diagnosis-tool/-/jobs/11144011/artifacts/raw/SH-Network-Diagnosis.exe?inline=false)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -60,7 +60,6 @@ body:
|
||||
- 完全无法连接服务器
|
||||
- 连接速度慢
|
||||
- 获取到了不正确的页面或数据
|
||||
- 客户端提示 429 Error
|
||||
- 客户端图片下载错误
|
||||
- 客户端图片预下载错误
|
||||
- 其它
|
||||
@@ -74,5 +73,12 @@ body:
|
||||
description: 如果你在上一项中选择了`其它`或者你有更多信息需要提供,请在这里写下来
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist-final
|
||||
attributes:
|
||||
label: 最后一步
|
||||
description: 检查你提交的议题
|
||||
options:
|
||||
- label: 我已经在该议题中上传了包含网络诊断报告的加密压缩包
|
||||
required: true
|
||||
108
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
Normal file
108
.github/ISSUE_TEMPLATE/ENG-bug-report.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
name: BUG Report [English Form]
|
||||
description: Tell us what issue you get
|
||||
title: "[ENG][Bug]: Place your Issue Title Here"
|
||||
labels: ["BUG", "priority:none"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> **Please use one sentence to briefly describe your issue as title above**
|
||||
> Please follow the instruction below to fill the form, so we can locate the issue quickly
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: |-
|
||||
Please complete the checklist, otherwise your issue may be ignored
|
||||
options:
|
||||
- label: I have read [FAQ page](https://hut.ao/advanced/FAQ.html) and [Exception page](https://hut.ao/advanced/exceptions.html) in Snap Hutao document, and my issue is not answered
|
||||
required: true
|
||||
|
||||
- label: I and tried **search feature** in Snap Hutao document site, and no associated article
|
||||
required: true
|
||||
|
||||
- label: My issue is not a [fixed issue](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aopen+is%3Aissue+label%3A%E5%B7%B2%E4%BF%AE%E5%A4%8D), and it's not a duplicated issue
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: winver
|
||||
attributes:
|
||||
label: Windows Version
|
||||
description: |
|
||||
Use `Win+R` and input `winver`, Windows build version is usually at the second line
|
||||
placeholder: e.g. 22000.556
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: shver
|
||||
attributes:
|
||||
label: Snap Hutao Version
|
||||
description: You can find the version in application's title bar
|
||||
placeholder: e.g. 1.4.15.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: deviceid
|
||||
attributes:
|
||||
label: Device ID
|
||||
description: |
|
||||
In Snap Hutao's settings page, you can find and copy your device ID
|
||||
If your issue is about program crash, please fill this so we can dump the log and locate the source easier
|
||||
If your program cannot startup, please download and run [this PowerShell script](https://github.com/DGP-Studio/ISSUE_TEMPLATES/releases/download/get_device_id/GetHutaoDeviceId.ps1), it will shows your device ID.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: user-set-category
|
||||
attributes:
|
||||
label: Issue Category
|
||||
description: Please select the most associated category of your issue
|
||||
options:
|
||||
- Installation and Environment
|
||||
- Achievement
|
||||
- My Character
|
||||
- Game Launcher
|
||||
- Realtime Note
|
||||
- Develop Plan
|
||||
- File Cache
|
||||
- Wish Export
|
||||
- Game Record
|
||||
- Hutao Database
|
||||
- User Interface
|
||||
- Snap Hutao Cloud
|
||||
- Snap Hutao Account
|
||||
- Checkin
|
||||
- Wiki
|
||||
- Announcement
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What Happened?
|
||||
description: Describe your issue in detail to help us identify the issue. **If your issue is about program crash, you should check Windows Event Viewer, and attach associated `.Net Error` details here**
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-expected
|
||||
attributes:
|
||||
label: What is expected?
|
||||
description: Describe expected outcome, highlight the difference with current outcome
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist-final
|
||||
attributes:
|
||||
label: Last Step
|
||||
description: Review your Form
|
||||
options:
|
||||
- label: I believe the description above is detail enough to allow developers to reproduce the issue
|
||||
required: true
|
||||
|
||||
27
.github/ISSUE_TEMPLATE/ENG-feature-request.yml
vendored
Normal file
27
.github/ISSUE_TEMPLATE/ENG-feature-request.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Feature Request [English Form]
|
||||
description: Tell us about your thought
|
||||
title: "[Feat]: Place your title here"
|
||||
labels: ["功能", "needs-triage", "priority:none"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please fill the form below
|
||||
|
||||
- type: textarea
|
||||
id: back
|
||||
attributes:
|
||||
label: Background & Motivation
|
||||
description: Reason why this feature is needed. If multiple features is requested, please open multiple issues for each of them.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: req
|
||||
attributes:
|
||||
label: Detail of the Feature
|
||||
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted.
|
||||
validations:
|
||||
required: true
|
||||
79
.github/ISSUE_TEMPLATE/ENG-network-issue.yml
vendored
Normal file
79
.github/ISSUE_TEMPLATE/ENG-network-issue.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: Network Issue [English Form]
|
||||
description: Submit this issue form when network issue affect your client experience
|
||||
title: "[Network]: Place your title here"
|
||||
labels: ["area-Network"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
- Masterain98
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Please use one sentence to briefly describe your issue as title above**
|
||||
**Please follow the instruction below to fill the form, so we can locate the issue quickly**
|
||||
|
||||
- type: textarea
|
||||
id: network-diagnosis-report
|
||||
attributes:
|
||||
label: Submit Your Network Diagnosis Report
|
||||
description: |
|
||||
STOP HERE!
|
||||
**Please run our network diagnosis tool before filling this form**
|
||||
**The diagnosis tool will generate a report and add it into a password-protected archive. Drag the `.zip` archive to the box below so it can be uploaded.**
|
||||
- Use the following link to download the Network Diagnosis Tool:
|
||||
- [GitHub](https://github.com/Masterain98/network-diagnosis-tool/releases/latest/download/SH-Network-Diagnosis.exe)
|
||||
- [JIHu GitLab](https://jihulab.com/DGP-Studio/network-diagnosis-tool/-/jobs/11144011/artifacts/raw/SH-Network-Diagnosis.exe?inline=false)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: user-geo-location
|
||||
attributes:
|
||||
label: Your Geographical Location
|
||||
description: |
|
||||
Description accurate to country
|
||||
placeholder: USA
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: user-isp
|
||||
attributes:
|
||||
label: Your ISP Name
|
||||
description: |
|
||||
Name of your Internet service provider
|
||||
placeholder: AT&T
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: user-issue-category
|
||||
attributes:
|
||||
label: Issue Category
|
||||
description: Select an issue category
|
||||
options:
|
||||
- Cannot connect to server completely
|
||||
- Slow spped
|
||||
- Fetched wrong page or data
|
||||
- Image download error in the client
|
||||
- Image set pre-download error (client welcome wizard process)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Your Issue (cont.)
|
||||
description: If you selected `Other` in previous dropdown, please explain your issue in detail here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist-final
|
||||
attributes:
|
||||
label: One Last Step
|
||||
description: Check your issue form
|
||||
options:
|
||||
- label: I confirm I have attached the network diagnosis report archive in the issue
|
||||
required: true
|
||||
65
.github/ISSUE_TEMPLATE/artifact-rating-rules.yml
vendored
65
.github/ISSUE_TEMPLATE/artifact-rating-rules.yml
vendored
@@ -1,65 +0,0 @@
|
||||
name: 圣遗物评分细则建议
|
||||
description: 为圣遗物评分规则提供你的想法
|
||||
title: "[Artifact Rating] 请在这里填写角色名称"
|
||||
labels: area-AvatarInfo
|
||||
assignees: Lightczx
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
请按下方的要求填写完整的问题表单
|
||||
|
||||
- type: textarea
|
||||
id: your-suggested-rule
|
||||
attributes:
|
||||
label: 评分细则
|
||||
description: |
|
||||
请修改下方表格中的**角色名称**和**各属性权重**,并在表格后添加合适的说明
|
||||
你可以点击预览按钮(preview)来查看表格最终会显示出的内容
|
||||
value: |
|
||||
|项目|评分权重(0-100)|
|
||||
|-----|-----|
|
||||
|角色名称| 旅行者 |
|
||||
|生命值| 10 |
|
||||
|攻击力| 10 |
|
||||
|防御力| 10 |
|
||||
|暴击率| 10 |
|
||||
|暴击伤害| 10 |
|
||||
|元素精通| 10 |
|
||||
|充能效率| 10 |
|
||||
|治疗加成| 10 |
|
||||
|元素伤害| 10 |
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: no-duplicated-dropdown
|
||||
attributes:
|
||||
label: 我确认当前没有其它的该角色的圣遗物评分细则建议
|
||||
description: 如果有,你应该在已有的工单内回复以提出你的建议
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: title-filled-dropdown
|
||||
attributes:
|
||||
label: 我确认已设置合适的标题
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: all-filled-dropdown
|
||||
attributes:
|
||||
label: 我确认已完整填写表格
|
||||
options:
|
||||
- 否
|
||||
- 是
|
||||
validations:
|
||||
required: true
|
||||
13
.github/ISSUE_TEMPLATE/config.yml
vendored
13
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 胡桃工具箱 - 官方文档
|
||||
- name: Snap Hutao 官方文档 / Snap Hutao Document
|
||||
url: https://hut.ao
|
||||
about: 请在提出问题前阅读文档
|
||||
about: 请在提出问题前阅读文档 / Read the document before submit the issue
|
||||
|
||||
- name: 常见问题 / FAQ
|
||||
url: https://hut.ao/advanced/FAQ.html
|
||||
about: 常见的用户提出的问题 / Common questions asked by users
|
||||
|
||||
- name: 常见程序异常 / Common Program Exceptions
|
||||
url: https://hut.ao/advanced/exceptions.html
|
||||
about: 用户通常能自行解决这些问题 / Users may solve these problems by themselves
|
||||
|
||||
28
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
28
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Feature Request 功能请求
|
||||
description: Tell us about your thought 告诉我们你的想法
|
||||
title: "[Feat]: Place your title here 在这里填写一个合适的标题"
|
||||
labels: ["功能"]
|
||||
assignees:
|
||||
- Lightczx
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please fill the form below
|
||||
请按下方的要求填写完整的问题表单。
|
||||
|
||||
- type: textarea
|
||||
id: back
|
||||
attributes:
|
||||
label: Background & Motivation 背景与动机
|
||||
description: Reason why this feature is needed. If multiple features is requested, please open multiple issues for each of them. 添加此功能的理由,如果你想要实现多个功能,请分别发起多个单独的 Issue
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: req
|
||||
attributes:
|
||||
label: Detail of the Feature 想要实现或优化的功能
|
||||
description: Descripbe the feaure in detail. The more detailed and convincing the desciprtion the more likyly feature will be accepted. 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
|
||||
validations:
|
||||
required: true
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -10,11 +10,11 @@ src/Snap.Hutao/_ReSharper.Caches
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao/bin/
|
||||
src/Snap.Hutao/Snap.Hutao/obj/
|
||||
src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.Designer.cs
|
||||
src/Snap.Hutao/Snap.Hutao/Snap.Hutao_TemporaryKey.pfx
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/obj/
|
||||
src/Snap.Hutao/Snap.Hutao.Installer/Properties/PublishProfiles/FolderProfile.pubxml.user
|
||||
src/Snap.Hutao/Snap.Hutao.Win32/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.Win32/obj/
|
||||
|
||||
src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/
|
||||
src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/
|
||||
|
||||
72
CONTRIBUTING.md
Normal file
72
CONTRIBUTING.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Contribution Guide
|
||||
|
||||
## Contribute Your Code
|
||||
|
||||
### Setup Snap.Hutao Project
|
||||
|
||||
1. Download and install [Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)
|
||||
2. Open Visual Studio Installer to complete Visual Studio installation
|
||||
- You need to install `.NET desktop development`, `Desktop development with C++` and `Universal Windows Platform development` components
|
||||
3. Install `Single-project MSIX Packaging Tools for VS 2022` provided by Microsoft in Visual Studio marketplace
|
||||
4. Use git to clone the project `https://github.com/DGP-Studio/Snap.Hutao.git` to your local device
|
||||
5. Switch git branch to `develop`
|
||||
6. Open project solution with your Visual Studio and then you are ready to go
|
||||
|
||||
### Start Pull Request
|
||||
|
||||
- All code-related changes from authors' own branches are only allowed be merged to `develop` branch
|
||||
- Please use [keywords](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests) to link your PR or commits with issues, so issues can be automatically closed once commits are merged into `main` branch.
|
||||
|
||||
### Test Binary Package
|
||||
|
||||
Once the code in updated in `develop` and `main` branches, an Azure Pipeline CI script will build the latest code to `Snap Hutao Alpha` package. Once the package is built, it will be released on [GitHub Release page](https://github.com/DGP-Studio/Snap.Hutao/releases) as a pre-released package.
|
||||
|
||||
You need to install [Snap.Hutao.CI.cer](https://github.com/DGP-Studio/Snap.Hutao/releases/download/2023.10.3.1/Snap.Hutao.CI.cer) certificate to your local machine, and then install the msix package in the release.
|
||||
|
||||
*If the latest release does not contains attached file, that means package is still in uploading process.
|
||||
|
||||
## Start New Issue
|
||||
|
||||
To help users solve problems faster and increase developers' efficiency in solving problems, Snap Hutao provides detailed documentation to explain common problems and issue templates to guide users to report program problems by submitting issues.
|
||||
|
||||
Before submitting a new issue, you should check the following pages:
|
||||
|
||||
- [FAQ](https://hut.ao/advanced/FAQ.html) Document
|
||||
- [Common Program Exceptions ](https://hut.ao/en/advanced/exceptions.html)Document
|
||||
- [Current Opened BUG Report Issues](https://github.com/DGP-Studio/Snap.Hutao/issues?q=is%3Aissue+is%3Aopen+label%3ABUG)
|
||||
|
||||
When starting a new issue, please use the issue templates:
|
||||
|
||||
- Describe your issue in details to help developers to reproduce the issue
|
||||
- Your description of reproduction should be a step-by-step story
|
||||
- If your issue is about program crash
|
||||
- Remember to provide your Device ID
|
||||
- Check Windows Event Viewer, and attach associated `.NET Error` details in the issue body
|
||||
|
||||
## Document Modification
|
||||
|
||||
Snap Hutao Document site is stored in repository [DGP-Studio/Snap.Hutao.Docs](https://github.com/DGP-Studio/Snap.Hutao.Docs), you can process the following steps to test the site in your local device:
|
||||
|
||||
1. Download and install [NodeJS 18](https://nodejs.org/en/download/)
|
||||
2. Clone the repository
|
||||
3. Run `npm install` in the root directory of the document project
|
||||
4. Run `npm run docs:dev` to start test on 8080 port
|
||||
|
||||
### Localization
|
||||
|
||||
Snap.Hutao.Docs project structure is designed as multiple languages site. Each language has its independent folder under `docs` directory.
|
||||
|
||||
**If you wish to add another language document, you can [start an issue in document repository](https://github.com/DGP-Studio/Snap.Hutao.Docs/issues) to ask developer to setup an environment for you, or you can process the following steps by yourself:**
|
||||
|
||||
1. make a copy of `zh` folder, rename the new folder as the new language's code
|
||||
2. Start your translation work in the new language folder
|
||||
3. In `docs/.vuepress/sidebar` folder, duplicate `zh.ts` file
|
||||
1. Rename the file to `{language_code}.ts`
|
||||
2. In the line 4, change `/zh/` to `/{language_code}/`
|
||||
3. Translate all `text` field
|
||||
4. In `docs/.vuepress/navbar` folder, duplicate `zh.ts` file
|
||||
1. Rename the file to `{language_code}.ts`
|
||||
2. Replace all `/zh/` to `/{language_code}/`
|
||||
3. Translate all `text` field
|
||||
5. In `docs/.vuepress/config.ts`file, add your language information in `locales` and `plugins/docsearchPlugin/locales` dictionary
|
||||
6. In `docs/.vuepress/theme.ts`file, add your language information in `locales` dictionary
|
||||
12
README.md
12
README.md
@@ -1,8 +1,8 @@
|
||||

|
||||
|
||||
胡桃工具箱是一个 Windows 平台的开源的原神工具箱,旨在帮助玩家获得更好的游戏体验; 它是对官方移动端工具的一种非破坏性功能扩展,为不习惯在移动端进行原神游戏的 PC 玩家提供一个在 Windows 平台下获得接近移动端功能权利的途径
|
||||
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
|
||||
|
||||
Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to provide a better gaming experience for players. It's an nondestructive feature extension from Genshin Impact's official mobile application, to provide similar feature on desktop, to allow PC gamers gain deserved benefits from mobile platforms.
|
||||
Snap Hutao is an open-source Genshin Impact toolkit under MIT license, designed for modern Windows platform to improve the gaming experience for desktop players. By combining existing official resources with new features designed by the development team, it provides a complete and useful set of tools without the need to rely on mobile devices. Snap Hutao does not take any destructive modification to the game client to ensure the security of the toolkit.
|
||||
|
||||
## 下载使用 / Download
|
||||
|
||||
@@ -11,7 +11,7 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
|
||||
## 贡献 / Contribute
|
||||
|
||||
* [向我们提交 PR / Make Pull Requests](https://github.com/DGP-Studio/Snap.Hutao/pulls)
|
||||
* [在 Crowdin 上进行本地化 / Translate project on Crowdin](https://translate.hut.ao/)
|
||||
* [在 Crowdin 上进行本地化 / Translate Project on Crowdin](https://translate.hut.ao/)
|
||||
* [为我们更新文档 / Enhance our Document ](https://github.com/DGP-Studio/Snap.Hutao.Docs)
|
||||
|
||||
## 特别感谢 / Special Thanks
|
||||
@@ -19,7 +19,7 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
|
||||
* [HolographicHat](https://github.com/HolographicHat)
|
||||
* [UIGF organization](https://uigf.org)
|
||||
|
||||
### 特定的原神项目 / Specific Genshin-related Project
|
||||
### 特定的原神项目 / Specific Genshin-related Projects
|
||||
|
||||
* [biuuu/genshin-wish-export](https://github.com/biuuu/genshin-wish-export)
|
||||
* [xunkong/xunkong](https://github.com/xunkong/xunkong)
|
||||
@@ -45,4 +45,6 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
|
||||
* [Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata)
|
||||
|
||||
## 开发 / Development
|
||||

|
||||

|
||||
|
||||
[](https://star-history.com/#DGP-Studio/Snap.Hutao&Date)
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.Automation;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
internal sealed class AttributeGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateAllAttributes);
|
||||
}
|
||||
|
||||
public static void GenerateAllAttributes(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
string coreAnnotations = """
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
internal sealed class CommandAttribute : Attribute
|
||||
{
|
||||
public CommandAttribute(string name)
|
||||
{
|
||||
}
|
||||
|
||||
public bool AllowConcurrentExecutions { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
internal sealed class ConstructorGeneratedAttribute : Attribute
|
||||
{
|
||||
public ConstructorGeneratedAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
public bool CallBaseConstructor { get; set; }
|
||||
public bool ResolveHttpClient { get; set; }
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class DependencyPropertyAttribute : Attribute
|
||||
{
|
||||
public DependencyPropertyAttribute(string name, Type type)
|
||||
{
|
||||
}
|
||||
|
||||
public DependencyPropertyAttribute(string name, Type type, object defaultValue)
|
||||
{
|
||||
}
|
||||
|
||||
public DependencyPropertyAttribute(string name, Type type, object defaultValue, string valueChangedCallbackName)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsAttached { get; set; }
|
||||
public Type AttachedType { get; set; } = default;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||
[Conditional("DEBUG")]
|
||||
internal sealed class HighQualityAttribute : Attribute
|
||||
{
|
||||
}
|
||||
""";
|
||||
context.AddSource("Snap.Hutao.Core.Annotation.Attributes.g.cs", coreAnnotations);
|
||||
|
||||
string coreDependencyInjectionAnnotationHttpClients = """
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
internal sealed class HttpClientAttribute : Attribute
|
||||
{
|
||||
public HttpClientAttribute(HttpClientConfiguration configuration)
|
||||
{
|
||||
}
|
||||
|
||||
public HttpClientAttribute(HttpClientConfiguration configuration, Type interfaceType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal enum HttpClientConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认配置
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// 米游社请求配置
|
||||
/// </summary>
|
||||
XRpc,
|
||||
|
||||
/// <summary>
|
||||
/// 米游社登录请求配置
|
||||
/// </summary>
|
||||
XRpc2,
|
||||
|
||||
/// <summary>
|
||||
/// Hoyolab app
|
||||
/// </summary>
|
||||
XRpc3,
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
|
||||
{
|
||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.MaxConnectionsPerServer"/>
|
||||
public int MaxConnectionsPerServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.UseCookies"/>
|
||||
/// </summary>
|
||||
public bool UseCookies { get; set; }
|
||||
}
|
||||
""";
|
||||
context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.Attributes.g.cs", coreDependencyInjectionAnnotationHttpClients);
|
||||
|
||||
string coreDependencyInjectionAnnotations = """
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
|
||||
internal enum InjectAs
|
||||
{
|
||||
Singleton,
|
||||
Transient,
|
||||
Scoped,
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class InjectionAttribute : Attribute
|
||||
{
|
||||
public InjectionAttribute(InjectAs injectAs)
|
||||
{
|
||||
}
|
||||
|
||||
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations);
|
||||
}
|
||||
}
|
||||
@@ -95,4 +95,4 @@ internal sealed class CommandGenerator : IIncrementalGenerator
|
||||
string normalizedClassName = classSymbol.ToDisplayString().Replace('<', '{').Replace('>', '}');
|
||||
production.AddSource($"{normalizedClassName}.{commandName}.g.cs", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.Automation;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
internal sealed class SaltConstantGenerator : IIncrementalGenerator
|
||||
{
|
||||
private static readonly HttpClient httpClient;
|
||||
private static readonly Lazy<Response<SaltLatest>> lazySaltInfo;
|
||||
|
||||
static SaltConstantGenerator()
|
||||
{
|
||||
httpClient = new();
|
||||
lazySaltInfo = new Lazy<Response<SaltLatest>>(() =>
|
||||
{
|
||||
string body = httpClient.GetStringAsync("https://internal.snapgenshin.cn/Archive/Salt/Latest").GetAwaiter().GetResult();
|
||||
return JsonParser.FromJson<Response<SaltLatest>>(body)!;
|
||||
});
|
||||
}
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterPostInitializationOutput(GenerateSaltContstants);
|
||||
}
|
||||
|
||||
private static void GenerateSaltContstants(IncrementalGeneratorPostInitializationContext context)
|
||||
{
|
||||
Response<SaltLatest> saltInfo = lazySaltInfo.Value;
|
||||
string code = $$"""
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
internal sealed class SaltConstants
|
||||
{
|
||||
public const string CNVersion = "{{saltInfo.Data.CNVersion}}";
|
||||
public const string CNK2 = "{{saltInfo.Data.CNK2}}";
|
||||
public const string CNLK2 = "{{saltInfo.Data.CNLK2}}";
|
||||
|
||||
public const string OSVersion = "{{saltInfo.Data.OSVersion}}";
|
||||
public const string OSK2 = "{{saltInfo.Data.OSK2}}";
|
||||
public const string OSLK2 = "{{saltInfo.Data.OSLK2}}";
|
||||
}
|
||||
""";
|
||||
context.AddSource("SaltConstants.g.cs", code);
|
||||
}
|
||||
|
||||
private sealed class Response<T>
|
||||
{
|
||||
[DataMember(Name = "data")]
|
||||
public T Data { get; set; } = default!;
|
||||
}
|
||||
|
||||
internal sealed class SaltLatest
|
||||
{
|
||||
public string CNVersion { get; set; } = default!;
|
||||
|
||||
public string CNK2 { get; set; } = default!;
|
||||
|
||||
public string CNLK2 { get; set; } = default!;
|
||||
|
||||
public string OSVersion { get; set; } = default!;
|
||||
|
||||
public string OSK2 { get; set; } = default!;
|
||||
|
||||
public string OSLK2 { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,10 @@ internal sealed class NotNullWhenAttribute : Attribute
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
public NotNullWhenAttribute(bool returnValue)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
}
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
|
||||
@@ -20,7 +20,6 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
|
||||
|
||||
private const string HttpClientConfiguration = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.HttpClientConfiguration.";
|
||||
private const string PrimaryHttpMessageHandlerAttributeName = "Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient.PrimaryHttpMessageHandlerAttribute";
|
||||
private const string UseDynamicSecretAttributeName = "Snap.Hutao.Web.Hoyolab.DynamicSecret.UseDynamicSecretAttribute";
|
||||
private const string CRLF = "\r\n";
|
||||
|
||||
private static readonly DiagnosticDescriptor injectionShouldOmitDescriptor = new("SH201", "Injection 特性可以省略", "HttpClient 特性已将 {0} 注册为 Transient 服务", "Quality", DiagnosticSeverity.Warning, true);
|
||||
@@ -134,11 +133,6 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
|
||||
lineBuilder.Append(" })");
|
||||
}
|
||||
|
||||
if (context.HasAttributeWithName(UseDynamicSecretAttributeName))
|
||||
{
|
||||
lineBuilder.Append(".AddHttpMessageHandler<DynamicSecretHandler>()");
|
||||
}
|
||||
|
||||
lineBuilder.Append(';');
|
||||
|
||||
lines.Add(lineBuilder.ToString());
|
||||
|
||||
@@ -50,6 +50,8 @@ internal class LocalizedEnumGenerator : IIncrementalGenerator
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Snap.Hutao.Resource.Localization;
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(LocalizedEnumGenerator)}}", "1.0.0.0")]
|
||||
@@ -79,7 +81,7 @@ internal class LocalizedEnumGenerator : IIncrementalGenerator
|
||||
}
|
||||
else
|
||||
{
|
||||
return SH.ResourceManager.GetString(key);
|
||||
return SH.ResourceManager.GetString(key, CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +104,7 @@ internal class LocalizedEnumGenerator : IIncrementalGenerator
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
return SH.ResourceManager.GetString(key);
|
||||
return SH.ResourceManager.GetString(key, CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
@@ -89,6 +89,12 @@ internal sealed class IdentityGenerator : IIncrementalGenerator
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
|
||||
607
src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs
Normal file
607
src/Snap.Hutao/Snap.Hutao.SourceGeneration/Resx/ResxGenerator.cs
Normal file
@@ -0,0 +1,607 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.XPath;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.Resx;
|
||||
|
||||
[Generator]
|
||||
public sealed class ResxGenerator : IIncrementalGenerator
|
||||
{
|
||||
private static readonly DiagnosticDescriptor InvalidResx = new("SH401", "Couldn't parse Resx file", "Couldn't parse Resx file '{0}'", "ResxGenerator", DiagnosticSeverity.Warning, true);
|
||||
private static readonly DiagnosticDescriptor InvalidPropertiesForNamespace = new("SH402", "Couldn't compute namespace", "Couldn't compute namespace for file '{0}'", "ResxGenerator", DiagnosticSeverity.Warning, true);
|
||||
private static readonly DiagnosticDescriptor InvalidPropertiesForResourceName = new("SH403", "Couldn't compute resource name", "Couldn't compute resource name for file '{0}'", "ResxGenerator", DiagnosticSeverity.Warning, true);
|
||||
private static readonly DiagnosticDescriptor InconsistentProperties = new("SH404", "Inconsistent properties", "Property '{0}' values for '{1}' are inconsistent", "ResxGenerator", DiagnosticSeverity.Warning, true);
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
IncrementalValueProvider<(string? AssemblyName, bool SupportNullableReferenceTypes)> compilationProvider = context.CompilationProvider
|
||||
.Select(static (compilation, cancellationToken) => (compilation.AssemblyName, SupportNullableReferenceTypes: compilation.GetTypeByMetadataName("System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute") is not null));
|
||||
|
||||
IncrementalValueProvider<ImmutableArray<AdditionalText>> resxProvider = context.AdditionalTextsProvider
|
||||
.Where(text => text.Path.EndsWith(".resx", StringComparison.OrdinalIgnoreCase))
|
||||
.Collect();
|
||||
|
||||
context.RegisterSourceOutput(
|
||||
source: context.AnalyzerConfigOptionsProvider.Combine(compilationProvider.Combine(resxProvider)),
|
||||
action: (ctx, source) => Execute(ctx, source.Left, source.Right.Left.AssemblyName, source.Right.Left.SupportNullableReferenceTypes, source.Right.Right));
|
||||
}
|
||||
|
||||
private static void Execute(SourceProductionContext context, AnalyzerConfigOptionsProvider options, string? assemblyName, bool supportNullableReferenceTypes, ImmutableArray<AdditionalText> files)
|
||||
{
|
||||
// Group additional file by resource kind ((a.resx, a.en.resx, a.en-us.resx), (b.resx, b.en-us.resx))
|
||||
List<IGrouping<string, AdditionalText>> resxGroups = files
|
||||
.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(x => x.Key, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
foreach (IGrouping<string, AdditionalText>? resxGroug in resxGroups)
|
||||
{
|
||||
string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroug);
|
||||
string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroug);
|
||||
string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroug);
|
||||
string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroug);
|
||||
string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroug);
|
||||
|
||||
string rootNamespace = rootNamespaceConfiguration ?? assemblyName ?? "";
|
||||
string projectDir = projectDirConfiguration ?? assemblyName ?? "";
|
||||
string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroug.Key);
|
||||
string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroug.Key);
|
||||
|
||||
string? ns = namespaceConfiguration ?? defaultNamespace;
|
||||
string? resourceName = resourceNameConfiguration ?? defaultResourceName;
|
||||
string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroug.Key));
|
||||
|
||||
if (ns == null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroug.First().Path));
|
||||
}
|
||||
|
||||
if (resourceName == null)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroug.First().Path));
|
||||
}
|
||||
|
||||
List<ResxEntry>? entries = LoadResourceFiles(context, resxGroug);
|
||||
|
||||
string content = $"""
|
||||
// Debug info:
|
||||
// key: {resxGroug.Key}
|
||||
// files: {string.Join(", ", resxGroug.Select(f => f.Path))}
|
||||
// RootNamespace (metadata): {rootNamespaceConfiguration}
|
||||
// ProjectDir (metadata): {projectDirConfiguration}
|
||||
// Namespace / DefaultResourcesNamespace (metadata): {namespaceConfiguration}
|
||||
// ResourceName (metadata): {resourceNameConfiguration}
|
||||
// ClassName (metadata): {classNameConfiguration}
|
||||
// AssemblyName: {assemblyName}
|
||||
// RootNamespace (computed): {rootNamespace}
|
||||
// ProjectDir (computed): {projectDir}
|
||||
// defaultNamespace: {defaultNamespace}
|
||||
// defaultResourceName: {defaultResourceName}
|
||||
// Namespace: {ns}
|
||||
// ResourceName: {resourceName}
|
||||
// ClassName: {className}
|
||||
""";
|
||||
|
||||
if (resourceName != null && entries != null)
|
||||
{
|
||||
content += GenerateCode(ns, className, resourceName, entries, supportNullableReferenceTypes);
|
||||
}
|
||||
|
||||
context.AddSource($"{Path.GetFileName(resxGroug.Key)}.resx.g.cs", SourceText.From(content, Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateCode(string? ns, string className, string resourceName, List<ResxEntry> entries, bool enableNullableAttributes)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("#nullable enable");
|
||||
|
||||
if (ns != null)
|
||||
{
|
||||
sb.AppendLine($$"""
|
||||
|
||||
namespace {{ns}};
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
sb.AppendLine($$"""
|
||||
internal partial class {{className}}
|
||||
{
|
||||
private static global::System.Resources.ResourceManager? resourceMan;
|
||||
|
||||
public {{className}}()
|
||||
{
|
||||
}
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (resourceMan is null)
|
||||
{
|
||||
resourceMan = new global::System.Resources.ResourceManager("{{resourceName}}", typeof({{className}}).Assembly);
|
||||
}
|
||||
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo? Culture { get; set; }
|
||||
|
||||
[return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static object? GetObject(global::System.Globalization.CultureInfo? culture, string name, object? defaultValue)
|
||||
{
|
||||
culture ??= Culture;
|
||||
object? obj = ResourceManager.GetObject(name, culture);
|
||||
if (obj == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static object? GetObject(global::System.Globalization.CultureInfo? culture, string name)
|
||||
{
|
||||
return GetObject(culture: culture, name: name, defaultValue: null);
|
||||
}
|
||||
|
||||
public static object? GetObject(string name)
|
||||
{
|
||||
return GetObject(culture: null, name: name, defaultValue: null);
|
||||
}
|
||||
|
||||
[return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static object? GetObject(string name, object? defaultValue)
|
||||
{
|
||||
return GetObject(culture: null, name: name, defaultValue: defaultValue);
|
||||
}
|
||||
|
||||
public static global::System.IO.Stream? GetStream(string name)
|
||||
{
|
||||
return GetStream(culture: null, name: name);
|
||||
}
|
||||
|
||||
public static global::System.IO.Stream? GetStream(global::System.Globalization.CultureInfo? culture, string name)
|
||||
{
|
||||
culture ??= Culture;
|
||||
return ResourceManager.GetStream(name, culture);
|
||||
}
|
||||
|
||||
public static string? GetString(global::System.Globalization.CultureInfo? culture, string name)
|
||||
{
|
||||
return GetString(culture: culture, name: name, args: null);
|
||||
}
|
||||
|
||||
public static string? GetString(global::System.Globalization.CultureInfo? culture, string name, params object?[]? args)
|
||||
{
|
||||
culture ??= Culture;
|
||||
string? str = ResourceManager.GetString(name, culture);
|
||||
if (str == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
return string.Format(culture, str, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
public static string? GetString(string name, params object?[]? args)
|
||||
{
|
||||
return GetString(culture: null, name: name, args: args);
|
||||
}
|
||||
|
||||
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static string? GetString(string name, string? defaultValue)
|
||||
{
|
||||
return GetStringOrDefault(culture: null, name: name, defaultValue: defaultValue, args: null);
|
||||
}
|
||||
|
||||
public static string? GetString(string name)
|
||||
{
|
||||
return GetStringOrDefault(culture: null, name: name, defaultValue: null, args: null);
|
||||
}
|
||||
|
||||
[return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static string? GetStringOrDefault(global::System.Globalization.CultureInfo? culture, string name, string? defaultValue)
|
||||
{
|
||||
return GetStringOrDefault(culture: culture, name: name, defaultValue: defaultValue, args: null);
|
||||
}
|
||||
|
||||
[return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static string? GetStringOrDefault(global::System.Globalization.CultureInfo? culture, string name, string? defaultValue, params object?[]? args)
|
||||
{
|
||||
culture ??= Culture;
|
||||
string? str = ResourceManager.GetString(name, culture);
|
||||
if (str == null)
|
||||
{
|
||||
if (defaultValue == null || args == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Format(culture, defaultValue, args);
|
||||
}
|
||||
}
|
||||
|
||||
if (args != null)
|
||||
{
|
||||
return string.Format(culture, str, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
[return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static string? GetStringOrDefault(string name, string? defaultValue, params object?[]? args)
|
||||
{
|
||||
return GetStringOrDefault(culture: null, name: name, defaultValue: defaultValue, args: args);
|
||||
}
|
||||
|
||||
[return:global::System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("defaultValue")]
|
||||
public static string? GetStringOrDefault(string name, string? defaultValue)
|
||||
{
|
||||
return GetStringOrDefault(culture: null, name: name, defaultValue: defaultValue, args: null);
|
||||
}
|
||||
""");
|
||||
|
||||
foreach (ResxEntry? entry in entries.OrderBy(e => e.Name, StringComparer.Ordinal))
|
||||
{
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.IsText)
|
||||
{
|
||||
XElement summary = new("summary", new XElement("para", $"Looks up a localized string for \"{entry.Name}\"."));
|
||||
if (!string.IsNullOrWhiteSpace(entry.Comment))
|
||||
{
|
||||
summary.Add(new XElement("para", entry.Comment));
|
||||
}
|
||||
|
||||
if (!entry.IsFileRef)
|
||||
{
|
||||
summary.Add(new XElement("para", $"Value: \"{entry.Value}\"."));
|
||||
}
|
||||
|
||||
string comment = summary.ToString().Replace("\r\n", "\r\n /// ", StringComparison.Ordinal);
|
||||
|
||||
sb.AppendLine($$"""
|
||||
/// {{comment}}
|
||||
public static string {{ToCSharpNameIdentifier(entry.Name!)}}
|
||||
{
|
||||
get => GetString("{{entry.Name}}")!;
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
if (entry.Value != null)
|
||||
{
|
||||
int args = Regex.Matches(entry.Value, "\\{(?<num>[0-9]+)(\\:[^}]*)?\\}", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant)
|
||||
.Cast<Match>()
|
||||
.Select(m => int.Parse(m.Groups["num"].Value, CultureInfo.InvariantCulture))
|
||||
.Distinct()
|
||||
.DefaultIfEmpty(-1)
|
||||
.Max();
|
||||
|
||||
if (args >= 0)
|
||||
{
|
||||
string inParams = string.Join(", ", Enumerable.Range(0, args + 1).Select(arg => "object? arg" + arg.ToString(CultureInfo.InvariantCulture)));
|
||||
string callParams = string.Join(", ", Enumerable.Range(0, args + 1).Select(arg => "arg" + arg.ToString(CultureInfo.InvariantCulture)));
|
||||
|
||||
sb.AppendLine($$"""
|
||||
/// {{comment}}
|
||||
public static string Format{{ToCSharpNameIdentifier(entry.Name!)}}(global::System.Globalization.CultureInfo? provider, {{inParams}})
|
||||
{
|
||||
return GetString(provider, "{{entry.Name}}", {{callParams}})!;
|
||||
}
|
||||
|
||||
/// {{comment}}
|
||||
public static string Format{{ToCSharpNameIdentifier(entry.Name!)}}({{inParams}})
|
||||
{
|
||||
return GetString("{{entry.Name}}", {{callParams}})!;
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($$"""
|
||||
public static global::{{entry.FullTypeName}}? {{ToCSharpNameIdentifier(entry.Name!)}}
|
||||
{
|
||||
get => (global::{{entry.FullTypeName}}?)GetObject("{{entry.Name}}");
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine($$"""
|
||||
}
|
||||
|
||||
internal partial class {{className}}Names
|
||||
{
|
||||
""");
|
||||
|
||||
foreach (ResxEntry entry in entries)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine($$"""
|
||||
public const string {{ToCSharpNameIdentifier(entry.Name!)}} = "entry.Name";
|
||||
""");
|
||||
}
|
||||
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string? ComputeResourceName(string rootNamespace, string projectDir, string resourcePath)
|
||||
{
|
||||
string fullProjectDir = EnsureEndSeparator(Path.GetFullPath(projectDir));
|
||||
string fullResourcePath = Path.GetFullPath(resourcePath);
|
||||
|
||||
if (fullProjectDir == fullResourcePath)
|
||||
{
|
||||
return rootNamespace;
|
||||
}
|
||||
|
||||
if (fullResourcePath.StartsWith(fullProjectDir, StringComparison.Ordinal))
|
||||
{
|
||||
string relativePath = fullResourcePath.Substring(fullProjectDir.Length);
|
||||
return rootNamespace + '.' + relativePath.Replace('/', '.').Replace('\\', '.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? ComputeNamespace(string rootNamespace, string projectDir, string resourcePath)
|
||||
{
|
||||
string fullProjectDir = EnsureEndSeparator(Path.GetFullPath(projectDir));
|
||||
string fullResourcePath = EnsureEndSeparator(Path.GetDirectoryName(Path.GetFullPath(resourcePath))!);
|
||||
|
||||
if (fullProjectDir == fullResourcePath)
|
||||
{
|
||||
return rootNamespace;
|
||||
}
|
||||
|
||||
if (fullResourcePath.StartsWith(fullProjectDir, StringComparison.Ordinal))
|
||||
{
|
||||
string relativePath = fullResourcePath.Substring(fullProjectDir.Length);
|
||||
return rootNamespace + '.' + relativePath.Replace('/', '.').Replace('\\', '.').TrimEnd('.');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<ResxEntry>? LoadResourceFiles(SourceProductionContext context, IGrouping<string, AdditionalText> resxGroug)
|
||||
{
|
||||
List<ResxEntry> entries = new();
|
||||
foreach (AdditionalText? entry in resxGroug.OrderBy(file => file.Path, StringComparer.Ordinal))
|
||||
{
|
||||
SourceText? content = entry.GetText(context.CancellationToken);
|
||||
if (content == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
XDocument document = XDocument.Parse(content.ToString());
|
||||
foreach (XElement? element in document.XPathSelectElements("/root/data"))
|
||||
{
|
||||
string? name = element.Attribute("name")?.Value;
|
||||
string? type = element.Attribute("type")?.Value;
|
||||
string? comment = element.Attribute("comment")?.Value;
|
||||
string? value = element.Element("value")?.Value;
|
||||
|
||||
ResxEntry existingEntry = entries.Find(e => e.Name == name);
|
||||
if (existingEntry != null)
|
||||
{
|
||||
existingEntry.Comment ??= comment;
|
||||
}
|
||||
else
|
||||
{
|
||||
entries.Add(new ResxEntry { Name = name, Value = value, Comment = comment, Type = type });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(InvalidResx, location: null, entry.Path));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static string? GetMetadataValue(SourceProductionContext context, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, string name, IEnumerable<AdditionalText> additionalFiles)
|
||||
{
|
||||
return GetMetadataValue(context, analyzerConfigOptionsProvider, name, name, additionalFiles);
|
||||
}
|
||||
|
||||
private static string? GetMetadataValue(SourceProductionContext context, AnalyzerConfigOptionsProvider analyzerConfigOptionsProvider, string name, string? globalName, IEnumerable<AdditionalText> additionalFiles)
|
||||
{
|
||||
string? result = null;
|
||||
foreach (AdditionalText file in additionalFiles)
|
||||
{
|
||||
if (analyzerConfigOptionsProvider.GetOptions(file).TryGetValue("build_metadata.AdditionalFiles." + name, out string? value))
|
||||
{
|
||||
if (result != null && value != result)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(InconsistentProperties, location: null, name, file.Path));
|
||||
return null;
|
||||
}
|
||||
|
||||
result = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (globalName != null && analyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property." + globalName, out string? globalValue) && !string.IsNullOrEmpty(globalValue))
|
||||
{
|
||||
return globalValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ToCSharpNameIdentifier(string name)
|
||||
{
|
||||
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#identifiers
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.globalization.unicodecategory?view=net-5.0
|
||||
StringBuilder sb = new();
|
||||
foreach (char c in name)
|
||||
{
|
||||
UnicodeCategory category = char.GetUnicodeCategory(c);
|
||||
switch (category)
|
||||
{
|
||||
case UnicodeCategory.UppercaseLetter:
|
||||
case UnicodeCategory.LowercaseLetter:
|
||||
case UnicodeCategory.TitlecaseLetter:
|
||||
case UnicodeCategory.ModifierLetter:
|
||||
case UnicodeCategory.OtherLetter:
|
||||
case UnicodeCategory.LetterNumber:
|
||||
sb.Append(c);
|
||||
break;
|
||||
|
||||
case UnicodeCategory.DecimalDigitNumber:
|
||||
case UnicodeCategory.ConnectorPunctuation:
|
||||
case UnicodeCategory.Format:
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
sb.Append(c);
|
||||
break;
|
||||
|
||||
default:
|
||||
sb.Append('_');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string EnsureEndSeparator(string path)
|
||||
{
|
||||
if (path[path.Length - 1] == Path.DirectorySeparatorChar)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
return path + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
private static string GetResourceName(string path)
|
||||
{
|
||||
string pathWithoutExtension = Path.Combine(Path.GetDirectoryName(path)!, Path.GetFileNameWithoutExtension(path));
|
||||
int indexOf = pathWithoutExtension.LastIndexOf('.');
|
||||
if (indexOf < 0)
|
||||
{
|
||||
return pathWithoutExtension;
|
||||
}
|
||||
|
||||
return Regex.IsMatch(pathWithoutExtension.Substring(indexOf + 1), "^[a-zA-Z]{2}(-[a-zA-Z]{2})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1))
|
||||
? pathWithoutExtension.Substring(0, indexOf)
|
||||
: pathWithoutExtension;
|
||||
}
|
||||
|
||||
private sealed class ResxEntry
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Value { get; set; }
|
||||
public string? Comment { get; set; }
|
||||
public string? Type { get; set; }
|
||||
|
||||
public bool IsText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Type == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Value != null)
|
||||
{
|
||||
string[] parts = Value.Split(';');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
string type = parts[1];
|
||||
if (type.StartsWith("System.String,", StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public string? FullTypeName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsText)
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (Value != null)
|
||||
{
|
||||
string[] parts = Value.Split(';');
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
string type = parts[1];
|
||||
return type.Split(',')[0];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFileRef
|
||||
{
|
||||
get => Type != null && Type.StartsWith("System.Resources.ResXFileRef,", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.SourceGeneration.Resx;
|
||||
|
||||
internal static class StringExtensions
|
||||
{
|
||||
public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
int previousIndex = 0;
|
||||
int index = str.IndexOf(oldValue, comparison);
|
||||
while (index is not -1)
|
||||
{
|
||||
sb.Append(str, previousIndex, index - previousIndex);
|
||||
sb.Append(newValue);
|
||||
index += oldValue.Length;
|
||||
|
||||
previousIndex = index;
|
||||
index = str.IndexOf(oldValue, index, comparison);
|
||||
}
|
||||
|
||||
sb.Append(str, previousIndex, str.Length - previousIndex);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
|
||||
@@ -49,7 +49,7 @@ public class JsonSerializeTest
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
};
|
||||
|
||||
Dictionary<int,string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
|
||||
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
|
||||
Assert.AreEqual(sample[111], "12");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
|
||||
"allowMarshaling": true,
|
||||
"useSafeHandles": false,
|
||||
"emitSingleFile": true
|
||||
"useSafeHandles": false
|
||||
}
|
||||
@@ -33,13 +33,17 @@ CoWaitForMultipleObjects
|
||||
// USER32
|
||||
AttachThreadInput
|
||||
FindWindowExW
|
||||
GetCursorPos
|
||||
GetDC
|
||||
GetDpiForWindow
|
||||
GetForegroundWindow
|
||||
GetWindowPlacement
|
||||
GetWindowThreadProcessId
|
||||
ReleaseDC
|
||||
RegisterHotKey
|
||||
SendInput
|
||||
SetForegroundWindow
|
||||
UnregisterHotKey
|
||||
|
||||
// COM
|
||||
IPersistFile
|
||||
@@ -53,6 +57,7 @@ IMemoryBufferByteAccess
|
||||
// Const value
|
||||
INFINITE
|
||||
WM_GETMINMAXINFO
|
||||
WM_HOTKEY
|
||||
WM_NCRBUTTONDOWN
|
||||
WM_NCRBUTTONUP
|
||||
WM_NULL
|
||||
25
src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj
Normal file
25
src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="NativeMethods.json" />
|
||||
<None Remove="NativeMethods.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="NativeMethods.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
36
src/Snap.Hutao/Snap.Hutao.Win32/StructMarshal.cs
Normal file
36
src/Snap.Hutao/Snap.Hutao.Win32/StructMarshal.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Win32.UI.WindowsAndMessaging;
|
||||
|
||||
[assembly: InternalsVisibleTo("Snap.Hutao")]
|
||||
|
||||
namespace Snap.Hutao.Win32;
|
||||
|
||||
/// <summary>
|
||||
/// 结构体封送
|
||||
/// </summary>
|
||||
internal static class StructMarshal
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的 <see cref="Windows.Win32.UI.WindowsAndMessaging.WINDOWPLACEMENT"/>
|
||||
/// </summary>
|
||||
/// <returns>新的实例</returns>
|
||||
public static unsafe WINDOWPLACEMENT WINDOWPLACEMENT()
|
||||
{
|
||||
return new() { length = SizeOf<WINDOWPLACEMENT>() };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取结构的大小
|
||||
/// </summary>
|
||||
/// <typeparam name="TStruct">结构类型</typeparam>
|
||||
/// <returns>结构的大小</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe uint SizeOf<TStruct>()
|
||||
where TStruct : unmanaged
|
||||
{
|
||||
return unchecked((uint)sizeof(TStruct));
|
||||
}
|
||||
}
|
||||
72
src/Snap.Hutao/Snap.Hutao.Win32/WinRTCustomMarshaler.cs
Normal file
72
src/Snap.Hutao/Snap.Hutao.Win32/WinRTCustomMarshaler.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Windows.Win32.CsWin32.InteropServices;
|
||||
|
||||
internal class WinRTCustomMarshaler : ICustomMarshaler
|
||||
{
|
||||
private static readonly string? AssemblyFullName = typeof(Windows.Foundation.IMemoryBuffer).Assembly.FullName;
|
||||
|
||||
private readonly string className;
|
||||
private bool lookedForFromAbi;
|
||||
private MethodInfo? fromAbiMethod;
|
||||
|
||||
private WinRTCustomMarshaler(string className)
|
||||
{
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
public static ICustomMarshaler GetInstance(string cookie)
|
||||
{
|
||||
return new WinRTCustomMarshaler(cookie);
|
||||
}
|
||||
|
||||
public void CleanUpManagedData(object ManagedObj)
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUpNativeData(nint pNativeData)
|
||||
{
|
||||
Marshal.Release(pNativeData);
|
||||
}
|
||||
|
||||
public int GetNativeDataSize()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public nint MarshalManagedToNative(object ManagedObj)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public object MarshalNativeToManaged(nint thisPtr)
|
||||
{
|
||||
return className switch
|
||||
{
|
||||
"Windows.System.DispatcherQueueController" => Windows.System.DispatcherQueueController.FromAbi(thisPtr),
|
||||
_ => MarshalNativeToManagedSlow(thisPtr),
|
||||
};
|
||||
}
|
||||
|
||||
private object MarshalNativeToManagedSlow(nint pNativeData)
|
||||
{
|
||||
if (!lookedForFromAbi)
|
||||
{
|
||||
Type? type = Type.GetType($"{className}, {AssemblyFullName}");
|
||||
|
||||
fromAbiMethod = type?.GetMethod("FromAbi");
|
||||
lookedForFromAbi = true;
|
||||
}
|
||||
|
||||
if (fromAbiMethod is not null)
|
||||
{
|
||||
return fromAbiMethod.Invoke(default, new object[] { pNativeData })!;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Marshal.GetObjectForIUnknown(pNativeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Test", "Snap.Hutao.Test\Snap.Hutao.Test.csproj", "{D691BA9F-904C-4229-87A5-E14F2EFF2F64}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{0F7ABEB2-5107-4037-B9DC-84D288FB0801}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug As Fake Elevated|Any CPU = Debug As Fake Elevated|Any CPU
|
||||
Debug As Fake Elevated|arm64 = Debug As Fake Elevated|arm64
|
||||
Debug As Fake Elevated|x64 = Debug As Fake Elevated|x64
|
||||
Debug As Fake Elevated|x86 = Debug As Fake Elevated|x86
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|arm64 = Debug|arm64
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -30,18 +28,6 @@ Global
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|Any CPU.Deploy.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|arm64.Deploy.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x64.Deploy.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug As Fake Elevated|x86.Deploy.0 = Debug As Fake Elevated|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Debug|Any CPU.Deploy.0 = Debug|x64
|
||||
@@ -66,14 +52,6 @@ Global
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.ActiveCfg = Release|x86
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Build.0 = Release|x86
|
||||
{AAAB7CF0-F299-49B8-BDB4-4C320B3EC2C7}.Release|x86.Deploy.0 = Release|x86
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
@@ -90,14 +68,6 @@ Global
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|Any CPU.ActiveCfg = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|Any CPU.Build.0 = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|arm64.ActiveCfg = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|arm64.Build.0 = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x64.ActiveCfg = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x64.Build.0 = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x86.ActiveCfg = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug As Fake Elevated|x86.Build.0 = Debug As Fake Elevated|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
@@ -114,6 +84,22 @@ Global
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D691BA9F-904C-4229-87A5-E14F2EFF2F64}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|arm64.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|arm64.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0F7ABEB2-5107-4037-B9DC-84D288FB0801}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,152 +1,31 @@
|
||||
<Application
|
||||
x:Class="Snap.Hutao.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwcw="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<muxc:XamlControlsResources/>
|
||||
<ResourceDictionary Source="Control/Theme/FontStyle.xaml"/>
|
||||
<XamlControlsResources/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Loading.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Image/CachedImage.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Card.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Color.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Converter.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/CornerRadius.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FlyoutStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/FontStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Glyph.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/InfoBarOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/ItemsPanelTemplate.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/NumericValue.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PageOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/PivotOverride.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/SettingsStyle.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/TransitionCollection.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/Uri.xaml"/>
|
||||
<ResourceDictionary Source="ms-appx:///Control/Theme/WindowOverride.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Color x:Key="AvatarPropertyAddValueColor">#FF74BF00</Color>
|
||||
<Color x:Key="CompatBackgroundColor">#FFF4F4F4</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="AvatarPropertyAddValueColor">#FF90E800</Color>
|
||||
<Color x:Key="CompatBackgroundColor">#FF242424</Color>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
<!-- Modify Window title bar color -->
|
||||
<StaticResource x:Key="WindowCaptionBackground" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<!-- Page Transparent Background -->
|
||||
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<!-- InfoBar Resource -->
|
||||
<Thickness x:Key="InfoBarIconMargin">19,16,19,16</Thickness>
|
||||
<Thickness x:Key="InfoBarContentRootPadding">0,0,0,0</Thickness>
|
||||
<x:Double x:Key="InfoBarIconFontSize">20</x:Double>
|
||||
<!-- Pivot Resource -->
|
||||
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
<!-- CornerRadius -->
|
||||
<CornerRadius x:Key="CompatCornerRadius">6</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusTop">6,6,0,0</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusRight">0,6,6,0</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
|
||||
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
|
||||
<!-- Length -->
|
||||
<GridLength x:Key="CompatGridLength2">288</GridLength>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength3">320</x:Double>
|
||||
<x:Double x:Key="HomeAdaptiveCardHeight">180</x:Double>
|
||||
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
|
||||
<x:Double x:Key="LargeAppBarButtonWidth">100</x:Double>
|
||||
<!-- ProgressBar -->
|
||||
<x:Double x:Key="LargeBackgroundProgressBarOpacity">0.2</x:Double>
|
||||
|
||||
<ThemeShadow x:Key="CompatShadow"/>
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="AvatarPropertyAddValueBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
|
||||
<SolidColorBrush x:Key="BlueBrush" Color="#FF5180CB"/>
|
||||
<SolidColorBrush x:Key="PurpleBrush" Color="#FFA156E0"/>
|
||||
<SolidColorBrush x:Key="OrangeBrush" Color="#FFBC6932"/>
|
||||
<SolidColorBrush x:Key="GuaranteePullBrush" Color="#FF0063FF"/>
|
||||
<SolidColorBrush x:Key="UpPullBrush" Color="#FFFFA400"/>
|
||||
<!-- Settings -->
|
||||
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
<Style
|
||||
x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="1,29,0,5"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingsContentComboBoxStyle"
|
||||
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||
TargetType="ComboBox">
|
||||
<Setter Property="MinWidth" Value="120"/>
|
||||
</Style>
|
||||
<!-- Uris -->
|
||||
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
|
||||
<x:String x:Key="DocumentLink_BugReport">https://hut.ao/statements/bug-report.html</x:String>
|
||||
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
|
||||
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
|
||||
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
|
||||
<x:String x:Key="Sponsor_Afadian">https://afdian.net/a/DismissedLight</x:String>
|
||||
|
||||
<!-- Images -->
|
||||
<x:String x:Key="UI_ItemIcon_None">https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png</x:String>
|
||||
<x:String x:Key="UI_MarkTower">https://static.snapgenshin.com/Bg/UI_MarkTower.png</x:String>
|
||||
<x:String x:Key="UI_Icon_Intee_Explore_1">https://static.snapgenshin.com/Bg/UI_Icon_Intee_Explore_1.png</x:String>
|
||||
<x:String x:Key="UI_MarkQuest_Events_Proce">https://static.snapgenshin.com/Bg/UI_MarkQuest_Events_Proce.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_201">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_201.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_204">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_204.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_210">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_210.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_220021">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_220021.png</x:String>
|
||||
<x:String x:Key="UI_ImgSign_ItemIcon">https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png</x:String>
|
||||
<x:String x:Key="UI_AvatarIcon_Costume_Card">https://static.snapgenshin.com/AvatarCard/UI_AvatarIcon_Costume_Card.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon25">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon25.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon71">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon71.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon250">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon272">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon293">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png</x:String>
|
||||
|
||||
<!-- FontIcon Content -->
|
||||
<x:String x:Key="FontIconContentAdd"></x:String>
|
||||
<x:String x:Key="FontIconContentSetting"></x:String>
|
||||
<x:String x:Key="FontIconContentRefresh"></x:String>
|
||||
<x:String x:Key="FontIconContentDelete"></x:String>
|
||||
<x:String x:Key="FontIconContentFolder"></x:String>
|
||||
<x:String x:Key="FontIconContentCheckList"></x:String>
|
||||
<x:String x:Key="FontIconContentAsteriskBadge12"></x:String>
|
||||
<x:String x:Key="FontIconContentZipFolder"></x:String>
|
||||
<!-- Converters -->
|
||||
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
|
||||
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<cwuc:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
|
||||
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
||||
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
|
||||
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
|
||||
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:DescriptionsParametersDescriptor x:Key="DescParamDescriptor"/>
|
||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||
<shmmc:EmotionIconConverter x:Key="EmotionIconConverter"/>
|
||||
<shmmc:EquipIconConverter x:Key="EquipIconConverter"/>
|
||||
<shmmc:GachaAvatarImgConverter x:Key="GachaAvatarImgConverter"/>
|
||||
<shmmc:GachaAvatarIconConverter x:Key="GachaAvatarIconConverter"/>
|
||||
<shmmc:GachaEquipIconConverter x:Key="GachaEquipIconConverter"/>
|
||||
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
|
||||
<shmmc:MonsterIconConverter x:Key="MonsterIconConverter"/>
|
||||
<shmmc:PropertiesParametersDescriptor x:Key="PropertyDescriptor"/>
|
||||
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
|
||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
<shvc:EmptyObjectToBoolConverter x:Key="EmptyObjectToBoolConverter"/>
|
||||
<shvc:EmptyObjectToBoolRevertConverter x:Key="EmptyObjectToBoolRevertConverter"/>
|
||||
<shvc:EmptyObjectToVisibilityConverter x:Key="EmptyObjectToVisibilityConverter"/>
|
||||
<shvc:EmptyObjectToVisibilityRevertConverter x:Key="EmptyObjectToVisibilityRevertConverter"/>
|
||||
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
|
||||
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
|
||||
<shvc:StringBoolConverter x:Key="StringBoolConverter"/>
|
||||
|
||||
<!-- Styles -->
|
||||
<Style
|
||||
x:Key="LargeGridViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||
@@ -154,118 +33,19 @@
|
||||
<Setter Property="Margin" Value="0,0,12,12"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardBorderBrush}"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
<Setter Property="Padding" Value="16,6,16,6"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
x:Name="NoneSelectionListViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
TargetType="ListViewItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Margin" Value="0,4,0,0"/>
|
||||
</Style>
|
||||
<Style x:Key="BorderCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
|
||||
<Style
|
||||
x:Name="NoneSelectionGridViewItemStyle"
|
||||
BasedOn="{StaticResource DefaultGridViewItemStyle}"
|
||||
TargetType="GridViewItem">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Margin" Value="0,0,2,4"/>
|
||||
</Style>
|
||||
<Style x:Key="BorderGridStyle" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
|
||||
</Style>
|
||||
<Style TargetType="shci:CachedImage">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="LazyLoadingThreshold" Value="300"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="shci:CachedImage">
|
||||
<Grid
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Image
|
||||
Name="PlaceholderImage"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Opacity="1.0"
|
||||
Source="{TemplateBinding PlaceholderSource}"
|
||||
Stretch="{TemplateBinding PlaceholderStretch}"/>
|
||||
<Image
|
||||
Name="Image"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
NineGrid="{TemplateBinding NineGrid}"
|
||||
Opacity="0.0"
|
||||
Stretch="{TemplateBinding Stretch}"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Failed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Loading">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Loaded">
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
AutoReverse="False"
|
||||
BeginTime="0"
|
||||
Storyboard.TargetName="Image"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="0"
|
||||
To="1"
|
||||
Duration="0:0:0.5"/>
|
||||
<DoubleAnimation
|
||||
AutoReverse="False"
|
||||
BeginTime="0"
|
||||
Storyboard.TargetName="PlaceholderImage"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="1"
|
||||
To="0"
|
||||
Duration="0:0:0.5"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Unloaded"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!-- ItemsPanelTemplate -->
|
||||
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="WrapPanelTemplate">
|
||||
<cwcw:WrapPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelTemplate">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
<!-- Transitions -->
|
||||
<TransitionCollection x:Key="ReorderThemeTransitions">
|
||||
<ReorderThemeTransition/>
|
||||
</TransitionCollection>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// 按给定比例自动调整高度的行为
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
|
||||
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
|
||||
internal sealed partial class AutoHeightBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
private readonly SizeChangedEventHandler sizeChangedEventHandler;
|
||||
|
||||
public AutoHeightBehavior()
|
||||
{
|
||||
sizeChangedEventHandler = OnSizeChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool Initialize()
|
||||
{
|
||||
UpdateElement();
|
||||
AssociatedObject.SizeChanged += sizeChangedEventHandler;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool Uninitialize()
|
||||
{
|
||||
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateElement();
|
||||
}
|
||||
|
||||
private void UpdateElement()
|
||||
{
|
||||
AssociatedObject.Height = AssociatedObject.ActualWidth * (TargetHeight / TargetWidth);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// 按给定比例自动调整高度的行为
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
|
||||
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
|
||||
internal sealed partial class AutoWidthBehavior : BehaviorBase<FrameworkElement>
|
||||
{
|
||||
private readonly SizeChangedEventHandler sizeChangedEventHandler;
|
||||
|
||||
public AutoWidthBehavior()
|
||||
{
|
||||
sizeChangedEventHandler = OnSizeChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool Initialize()
|
||||
{
|
||||
UpdateElement();
|
||||
AssociatedObject.SizeChanged += sizeChangedEventHandler;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool Uninitialize()
|
||||
{
|
||||
AssociatedObject.SizeChanged -= sizeChangedEventHandler;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
UpdateElement();
|
||||
}
|
||||
|
||||
private void UpdateElement()
|
||||
{
|
||||
AssociatedObject.Width = AssociatedObject.Height * (TargetWidth / TargetHeight);
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,36 @@ namespace Snap.Hutao.Control.Behavior;
|
||||
[DependencyProperty("CommandParameter", typeof(object))]
|
||||
internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIElement>
|
||||
{
|
||||
private bool executed;
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
// FrameworkElement in a ItemsRepeater gets attached twice
|
||||
if (AssociatedObject is FrameworkElement { IsLoaded: true })
|
||||
{
|
||||
TryExecuteCommand();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
TryExecuteCommand();
|
||||
}
|
||||
|
||||
private void TryExecuteCommand()
|
||||
{
|
||||
if (executed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Command is not null && Command.CanExecute(CommandParameter))
|
||||
{
|
||||
Command.Execute(CommandParameter);
|
||||
executed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Labs.WinUI.MarqueeTextRns;
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
internal sealed class MarqueeTextBehavior : BehaviorBase<MarqueeText>
|
||||
{
|
||||
private readonly PointerEventHandler pointerEnteredEventHandler;
|
||||
private readonly PointerEventHandler pointerExitedEventHandler;
|
||||
|
||||
public MarqueeTextBehavior()
|
||||
{
|
||||
pointerEnteredEventHandler = OnPointerEntered;
|
||||
pointerExitedEventHandler = OnPointerExited;
|
||||
}
|
||||
|
||||
protected override bool Initialize()
|
||||
{
|
||||
AssociatedObject.PointerEntered += pointerEnteredEventHandler;
|
||||
AssociatedObject.PointerExited += pointerExitedEventHandler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool Uninitialize()
|
||||
{
|
||||
AssociatedObject.PointerEntered -= pointerEnteredEventHandler;
|
||||
AssociatedObject.PointerExited -= pointerExitedEventHandler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnPointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
AssociatedObject.StartMarquee();
|
||||
}
|
||||
|
||||
private void OnPointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
AssociatedObject.StopMarquee();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI;
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
@@ -12,7 +13,7 @@ internal sealed class SelectedItemInViewBehavior : BehaviorBase<ListViewBase>
|
||||
{
|
||||
if (AssociatedObject.SelectedItem is { } item)
|
||||
{
|
||||
AssociatedObject.ScrollIntoView(item);
|
||||
AssociatedObject.SmoothScrollIntoViewWithItemAsync(item, ScrollItemPlacement.Center).SafeForget();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Animations;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
[DependencyProperty("Animation", typeof(AnimationSet))]
|
||||
[DependencyProperty("TargetObject", typeof(UIElement))]
|
||||
internal sealed partial class StartAnimationActionNoThrow : DependencyObject, IAction
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public object Execute(object sender, object parameter)
|
||||
{
|
||||
if (Animation is not null)
|
||||
{
|
||||
if (TargetObject is not null)
|
||||
{
|
||||
Animation.Start(TargetObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Animation.Start(sender as UIElement);
|
||||
}
|
||||
}
|
||||
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
internal sealed class ColorSegment : IColorSegment
|
||||
{
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
internal interface IColorSegment
|
||||
{
|
||||
@@ -7,7 +7,7 @@ using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
namespace Snap.Hutao.Control.Brush;
|
||||
|
||||
[DependencyProperty("Source", typeof(List<IColorSegment>), default!, nameof(OnSourceChanged))]
|
||||
internal sealed partial class SegmentedBar : ContentControl
|
||||
@@ -2,12 +2,11 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Snap.Hutao.Control.Alternating;
|
||||
namespace Snap.Hutao.Control.Collection.Alternating;
|
||||
|
||||
[DependencyProperty("ItemAlternateBackground", typeof(Brush))]
|
||||
[DependencyProperty("ItemAlternateBackground", typeof(Microsoft.UI.Xaml.Media.Brush))]
|
||||
internal sealed partial class AlternatingItemsControl : ItemsControl
|
||||
{
|
||||
private readonly VectorChangedEventHandler<object> itemsVectorChangedEventHandler;
|
||||
@@ -1,11 +1,9 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Snap.Hutao.Control.Alternating;
|
||||
namespace Snap.Hutao.Control.Collection.Alternating;
|
||||
|
||||
internal interface IAlternatingItem
|
||||
{
|
||||
public Brush? Background { get; set; }
|
||||
public Microsoft.UI.Xaml.Media.Brush? Background { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Snap.Hutao.Control.Collection.Selector;
|
||||
|
||||
[DependencyProperty("EnableMemberPath", typeof(string))]
|
||||
internal sealed partial class ComboBox2 : ComboBox
|
||||
{
|
||||
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
|
||||
{
|
||||
if (element is ComboBoxItem comboBoxItem)
|
||||
{
|
||||
Binding binding = new() { Path = new(EnableMemberPath) };
|
||||
comboBoxItem.SetBinding(IsEnabledProperty, binding);
|
||||
}
|
||||
|
||||
base.PrepareContainerForItemOverride(element, item);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable
|
||||
private readonly ContentDialog contentDialog;
|
||||
private readonly ITaskContext taskContext;
|
||||
|
||||
private bool disposed = false;
|
||||
private bool disposing = false;
|
||||
private bool disposed = false;
|
||||
|
||||
public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,8 @@ public sealed partial class FrameworkElementHelper
|
||||
{
|
||||
private static void OnSquareLengthChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
Microsoft.UI.Xaml.Controls.Control control = (Microsoft.UI.Xaml.Controls.Control)dp;
|
||||
control.Width = (double)e.NewValue;
|
||||
control.Height = (double)e.NewValue;
|
||||
FrameworkElement element = (FrameworkElement)dp;
|
||||
element.Width = (double)e.NewValue;
|
||||
element.Height = (double)e.NewValue;
|
||||
}
|
||||
}
|
||||
13
src/Snap.Hutao/Snap.Hutao/Control/Helper/InfoBarHelper.cs
Normal file
13
src/Snap.Hutao/Snap.Hutao/Control/Helper/InfoBarHelper.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Snap.Hutao.Control.Helper;
|
||||
|
||||
[SuppressMessage("", "SH001")]
|
||||
[DependencyProperty("IsTextSelectionEnabled", typeof(bool), false, IsAttached = true, AttachedType = typeof(InfoBar))]
|
||||
public sealed partial class InfoBarHelper
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Control.Helper;
|
||||
|
||||
[SuppressMessage("", "SH001")]
|
||||
[DependencyProperty("IsItemsEnabled", typeof(bool), true, nameof(OnIsItemsEnabledChanged), IsAttached = true, AttachedType = typeof(SettingsExpander))]
|
||||
public sealed partial class SettingsExpanderHelper
|
||||
{
|
||||
private static void OnIsItemsEnabledChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
foreach (object item in ((SettingsExpander)dp).Items)
|
||||
{
|
||||
if (item is Microsoft.UI.Xaml.Controls.Control control)
|
||||
{
|
||||
control.IsEnabled = (bool)e.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Snap.Hutao.Core.Caching;
|
||||
@@ -13,7 +12,7 @@ namespace Snap.Hutao.Control.Image;
|
||||
/// 缓存图像
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class CachedImage : ImageEx
|
||||
internal sealed class CachedImage : Implementation.ImageEx
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的缓存图像
|
||||
|
||||
82
src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.xaml
Normal file
82
src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.xaml
Normal file
@@ -0,0 +1,82 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image">
|
||||
<Style TargetType="shci:CachedImage">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource ApplicationForegroundThemeBrush}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="LazyLoadingThreshold" Value="256"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="shci:CachedImage">
|
||||
<Grid
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Image
|
||||
Name="PlaceholderImage"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Opacity="1.0"
|
||||
Source="{TemplateBinding PlaceholderSource}"
|
||||
Stretch="{TemplateBinding PlaceholderStretch}"/>
|
||||
<Image
|
||||
Name="Image"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
NineGrid="{TemplateBinding NineGrid}"
|
||||
Opacity="0.0"
|
||||
Stretch="{TemplateBinding Stretch}"/>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Failed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Loading">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Image" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderImage" Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Loaded">
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
AutoReverse="False"
|
||||
BeginTime="0"
|
||||
Storyboard.TargetName="Image"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="0"
|
||||
To="1"
|
||||
Duration="0:0:0.5"/>
|
||||
<DoubleAnimation
|
||||
AutoReverse="False"
|
||||
BeginTime="0"
|
||||
Storyboard.TargetName="PlaceholderImage"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="1"
|
||||
To="0"
|
||||
Duration="0:0:0.5"/>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Unloaded"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -164,11 +164,26 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
private async ValueTask<LoadedImageSurface> LoadImageSurfaceAsync(string file, CancellationToken token)
|
||||
{
|
||||
surfaceLoadTaskCompletionSource = new();
|
||||
LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
|
||||
surface.LoadCompleted += loadedImageSourceLoadCompletedEventHandler;
|
||||
await surfaceLoadTaskCompletionSource.Task.ConfigureAwait(true);
|
||||
LoadImageSurfaceCompleted(surface);
|
||||
return surface;
|
||||
LoadedImageSurface? surface = default;
|
||||
try
|
||||
{
|
||||
surface = LoadedImageSurface.StartLoadFromUri(file.ToUri());
|
||||
surface.LoadCompleted += loadedImageSourceLoadCompletedEventHandler;
|
||||
if (surface.DecodedPhysicalSize.Size() <= 0D)
|
||||
{
|
||||
await surfaceLoadTaskCompletionSource.Task.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
LoadImageSurfaceCompleted(surface);
|
||||
return surface;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (surface is not null)
|
||||
{
|
||||
surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask ShowAsync(CancellationToken token)
|
||||
@@ -216,7 +231,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
|
||||
private void OnLoadImageSurfaceLoadCompleted(LoadedImageSurface surface, LoadedImageSourceLoadCompletedEventArgs e)
|
||||
{
|
||||
surfaceLoadTaskCompletionSource?.TrySetResult();
|
||||
surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Win32;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Media.Casting;
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal class ImageEx : ImageExBase
|
||||
{
|
||||
private static readonly DependencyProperty NineGridProperty = DependencyProperty.Register(nameof(NineGrid), typeof(Thickness), typeof(ImageEx), new PropertyMetadata(default(Thickness)));
|
||||
|
||||
public ImageEx()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public Thickness NineGrid
|
||||
{
|
||||
get => (Thickness)GetValue(NineGridProperty);
|
||||
set => SetValue(NineGridProperty, value);
|
||||
}
|
||||
|
||||
public override CompositionBrush GetAlphaMask()
|
||||
{
|
||||
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
return image.GetAlphaMask();
|
||||
}
|
||||
|
||||
return default!;
|
||||
}
|
||||
|
||||
public CastingSource GetAsCastingSource()
|
||||
{
|
||||
if (IsInitialized && Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
return image.GetAsCastingSource();
|
||||
}
|
||||
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System.IO;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal delegate void ImageExFailedEventHandler(object sender, ImageExFailedEventArgs e);
|
||||
|
||||
internal delegate void ImageExOpenedEventHandler(object sender, ImageExOpenedEventArgs e);
|
||||
|
||||
[SuppressMessage("", "CA1001")]
|
||||
[SuppressMessage("", "SH003")]
|
||||
[TemplateVisualState(Name = LoadingState, GroupName = CommonGroup)]
|
||||
[TemplateVisualState(Name = LoadedState, GroupName = CommonGroup)]
|
||||
[TemplateVisualState(Name = UnloadedState, GroupName = CommonGroup)]
|
||||
[TemplateVisualState(Name = FailedState, GroupName = CommonGroup)]
|
||||
[TemplatePart(Name = PartImage, Type = typeof(object))]
|
||||
internal abstract class ImageExBase : Microsoft.UI.Xaml.Controls.Control, IAlphaMaskProvider
|
||||
{
|
||||
protected const string PartImage = "Image";
|
||||
protected const string CommonGroup = "CommonStates";
|
||||
protected const string LoadingState = "Loading";
|
||||
protected const string LoadedState = "Loaded";
|
||||
protected const string UnloadedState = "Unloaded";
|
||||
protected const string FailedState = "Failed";
|
||||
|
||||
private static readonly DependencyProperty StretchProperty = DependencyProperty.Register(nameof(Stretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(Stretch.Uniform));
|
||||
private static readonly DependencyProperty DecodePixelHeightProperty = DependencyProperty.Register(nameof(DecodePixelHeight), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
|
||||
private static readonly DependencyProperty DecodePixelTypeProperty = DependencyProperty.Register(nameof(DecodePixelType), typeof(int), typeof(ImageExBase), new PropertyMetadata(DecodePixelType.Physical));
|
||||
private static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register(nameof(DecodePixelWidth), typeof(int), typeof(ImageExBase), new PropertyMetadata(0));
|
||||
private static readonly DependencyProperty IsCacheEnabledProperty = DependencyProperty.Register(nameof(IsCacheEnabled), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false));
|
||||
private static readonly DependencyProperty EnableLazyLoadingProperty = DependencyProperty.Register(nameof(EnableLazyLoading), typeof(bool), typeof(ImageExBase), new PropertyMetadata(false, EnableLazyLoadingChanged));
|
||||
private static readonly DependencyProperty LazyLoadingThresholdProperty = DependencyProperty.Register(nameof(LazyLoadingThreshold), typeof(double), typeof(ImageExBase), new PropertyMetadata(default(double), LazyLoadingThresholdChanged));
|
||||
private static readonly DependencyProperty PlaceholderSourceProperty = DependencyProperty.Register(nameof(PlaceholderSource), typeof(ImageSource), typeof(ImageExBase), new PropertyMetadata(default(ImageSource), PlaceholderSourceChanged));
|
||||
private static readonly DependencyProperty PlaceholderStretchProperty = DependencyProperty.Register(nameof(PlaceholderStretch), typeof(Stretch), typeof(ImageExBase), new PropertyMetadata(default(Stretch)));
|
||||
private static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(object), typeof(ImageExBase), new PropertyMetadata(null, SourceChanged));
|
||||
|
||||
private CancellationTokenSource? tokenSource;
|
||||
private object? lazyLoadingSource;
|
||||
private bool isInViewport;
|
||||
|
||||
public event ImageExFailedEventHandler? ImageExFailed;
|
||||
|
||||
public event ImageExOpenedEventHandler? ImageExOpened;
|
||||
|
||||
public event EventHandler? ImageExInitialized;
|
||||
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
public int DecodePixelHeight
|
||||
{
|
||||
get => (int)GetValue(DecodePixelHeightProperty);
|
||||
set => SetValue(DecodePixelHeightProperty, value);
|
||||
}
|
||||
|
||||
public DecodePixelType DecodePixelType
|
||||
{
|
||||
get => (DecodePixelType)GetValue(DecodePixelTypeProperty);
|
||||
set => SetValue(DecodePixelTypeProperty, value);
|
||||
}
|
||||
|
||||
public int DecodePixelWidth
|
||||
{
|
||||
get => (int)GetValue(DecodePixelWidthProperty);
|
||||
set => SetValue(DecodePixelWidthProperty, value);
|
||||
}
|
||||
|
||||
public Stretch Stretch
|
||||
{
|
||||
get => (Stretch)GetValue(StretchProperty);
|
||||
set => SetValue(StretchProperty, value);
|
||||
}
|
||||
|
||||
public bool IsCacheEnabled
|
||||
{
|
||||
get => (bool)GetValue(IsCacheEnabledProperty);
|
||||
set => SetValue(IsCacheEnabledProperty, value);
|
||||
}
|
||||
|
||||
public bool EnableLazyLoading
|
||||
{
|
||||
get => (bool)GetValue(EnableLazyLoadingProperty);
|
||||
set => SetValue(EnableLazyLoadingProperty, value);
|
||||
}
|
||||
|
||||
public double LazyLoadingThreshold
|
||||
{
|
||||
get => (double)GetValue(LazyLoadingThresholdProperty);
|
||||
set => SetValue(LazyLoadingThresholdProperty, value);
|
||||
}
|
||||
|
||||
public ImageSource PlaceholderSource
|
||||
{
|
||||
get => (ImageSource)GetValue(PlaceholderSourceProperty);
|
||||
set => SetValue(PlaceholderSourceProperty, value);
|
||||
}
|
||||
|
||||
public Stretch PlaceholderStretch
|
||||
{
|
||||
get => (Stretch)GetValue(PlaceholderStretchProperty);
|
||||
set => SetValue(PlaceholderStretchProperty, value);
|
||||
}
|
||||
|
||||
public object Source
|
||||
{
|
||||
get => GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
public bool WaitUntilLoaded
|
||||
{
|
||||
get => true;
|
||||
}
|
||||
|
||||
protected object? Image { get; private set; }
|
||||
|
||||
public abstract CompositionBrush GetAlphaMask();
|
||||
|
||||
protected virtual void OnPlaceholderSourceChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual Task<ImageSource?> ProvideCachedResourceAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
// By default we just use the built-in UWP image cache provided within the Image control.
|
||||
return Task.FromResult<ImageSource?>(new BitmapImage(imageUri));
|
||||
}
|
||||
|
||||
protected virtual void OnImageOpened(object sender, RoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, LoadedState, true);
|
||||
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
|
||||
}
|
||||
|
||||
protected virtual void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new FileNotFoundException(e.ErrorMessage)));
|
||||
}
|
||||
|
||||
protected void AttachImageOpened(RoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageOpened += handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageOpened += handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveImageOpened(RoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageOpened -= handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageOpened -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void AttachImageFailed(ExceptionRoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageFailed += handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageFailed += handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected void RemoveImageFailed(ExceptionRoutedEventHandler handler)
|
||||
{
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.ImageFailed -= handler;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageFailed -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
RemoveImageOpened(OnImageOpened);
|
||||
RemoveImageFailed(OnImageFailed);
|
||||
|
||||
Image = GetTemplateChild(PartImage);
|
||||
|
||||
IsInitialized = true;
|
||||
|
||||
ImageExInitialized?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (Source is null || !EnableLazyLoading || isInViewport)
|
||||
{
|
||||
lazyLoadingSource = null;
|
||||
SetSource(Source);
|
||||
}
|
||||
else
|
||||
{
|
||||
lazyLoadingSource = Source;
|
||||
}
|
||||
|
||||
AttachImageOpened(OnImageOpened);
|
||||
AttachImageFailed(OnImageFailed);
|
||||
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
private static void EnableLazyLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ImageExBase control)
|
||||
{
|
||||
bool value = (bool)e.NewValue;
|
||||
if (value)
|
||||
{
|
||||
control.LayoutUpdated += control.ImageExBase_LayoutUpdated;
|
||||
|
||||
control.InvalidateLazyLoading();
|
||||
}
|
||||
else
|
||||
{
|
||||
control.LayoutUpdated -= control.ImageExBase_LayoutUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void LazyLoadingThresholdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ImageExBase control && control.EnableLazyLoading)
|
||||
{
|
||||
control.InvalidateLazyLoading();
|
||||
}
|
||||
}
|
||||
|
||||
private static void PlaceholderSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ImageExBase control)
|
||||
{
|
||||
control.OnPlaceholderSourceChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not ImageExBase control)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.OldValue is null || e.NewValue is null || !e.OldValue.Equals(e.NewValue))
|
||||
{
|
||||
if (e.NewValue is null || !control.EnableLazyLoading || control.isInViewport)
|
||||
{
|
||||
control.lazyLoadingSource = null;
|
||||
control.SetSource(e.NewValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
control.lazyLoadingSource = e.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsHttpUri(Uri uri)
|
||||
{
|
||||
return uri.IsAbsoluteUri && (uri.Scheme == "http" || uri.Scheme == "https");
|
||||
}
|
||||
|
||||
private void AttachSource(ImageSource? source)
|
||||
{
|
||||
// Setting the source at this point should call ImageExOpened/VisualStateManager.GoToState
|
||||
// as we register to both the ImageOpened/ImageFailed events of the underlying control.
|
||||
// We only need to call those methods if we fail in other cases before we get here.
|
||||
if (Image is Microsoft.UI.Xaml.Controls.Image image)
|
||||
{
|
||||
image.Source = source;
|
||||
}
|
||||
else if (Image is ImageBrush brush)
|
||||
{
|
||||
brush.ImageSource = source;
|
||||
}
|
||||
|
||||
if (source is null)
|
||||
{
|
||||
VisualStateManager.GoToState(this, UnloadedState, true);
|
||||
}
|
||||
else if (source is BitmapSource { PixelHeight: > 0, PixelWidth: > 0 })
|
||||
{
|
||||
VisualStateManager.GoToState(this, LoadedState, true);
|
||||
ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "IDE0019")]
|
||||
private async void SetSource(object? source)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tokenSource?.Cancel();
|
||||
|
||||
tokenSource = new CancellationTokenSource();
|
||||
|
||||
AttachSource(null);
|
||||
|
||||
if (source is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VisualStateManager.GoToState(this, LoadingState, true);
|
||||
|
||||
ImageSource? imageSource = source as ImageSource;
|
||||
if (imageSource is not null)
|
||||
{
|
||||
AttachSource(imageSource);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Uri? uri = source as Uri;
|
||||
if (uri is null)
|
||||
{
|
||||
string? url = source as string ?? source.ToString();
|
||||
if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out uri))
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(new UriFormatException("Invalid uri specified.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsHttpUri(uri) && !uri.IsAbsoluteUri)
|
||||
{
|
||||
uri = new Uri("ms-appx:///" + uri.OriginalString.TrimStart('/'));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await LoadImageAsync(uri, tokenSource.Token).ConfigureAwait(true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// nothing to do as cancellation has been requested.
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, FailedState, true);
|
||||
ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadImageAsync(Uri imageUri, CancellationToken token)
|
||||
{
|
||||
if (imageUri is not null)
|
||||
{
|
||||
if (IsCacheEnabled)
|
||||
{
|
||||
ImageSource? img = await ProvideCachedResourceAsync(imageUri, token).ConfigureAwait(true);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(tokenSource);
|
||||
if (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
// Only attach our image if we still have a valid request.
|
||||
AttachSource(img);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(imageUri.Scheme, "data", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string source = imageUri.OriginalString;
|
||||
const string base64Head = "base64,";
|
||||
int index = source.IndexOf(base64Head, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(source[(index + base64Head.Length)..]);
|
||||
BitmapImage bitmap = new();
|
||||
await bitmap.SetSourceAsync(new MemoryStream(bytes).AsRandomAccessStream());
|
||||
|
||||
ArgumentNullException.ThrowIfNull(tokenSource);
|
||||
if (!tokenSource.IsCancellationRequested)
|
||||
{
|
||||
AttachSource(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AttachSource(new BitmapImage(imageUri)
|
||||
{
|
||||
CreateOptions = BitmapCreateOptions.IgnoreImageCache,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ImageExBase_LayoutUpdated(object? sender, object e)
|
||||
{
|
||||
InvalidateLazyLoading();
|
||||
}
|
||||
|
||||
private void InvalidateLazyLoading()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
isInViewport = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first ascendant ScrollViewer, if not found, use the root element.
|
||||
FrameworkElement? hostElement = default;
|
||||
IEnumerable<FrameworkElement> ascendants = this.FindAscendants().OfType<FrameworkElement>();
|
||||
foreach (FrameworkElement ascendant in ascendants)
|
||||
{
|
||||
hostElement = ascendant;
|
||||
if (hostElement is Microsoft.UI.Xaml.Controls.ScrollViewer)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hostElement is null)
|
||||
{
|
||||
isInViewport = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Rect controlRect = TransformToVisual(hostElement)
|
||||
.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight));
|
||||
double lazyLoadingThreshold = LazyLoadingThreshold;
|
||||
Rect hostRect = new(
|
||||
0 - lazyLoadingThreshold,
|
||||
0 - lazyLoadingThreshold,
|
||||
hostElement.ActualWidth + (2 * lazyLoadingThreshold),
|
||||
hostElement.ActualHeight + (2 * lazyLoadingThreshold));
|
||||
|
||||
if (controlRect.IntersectsWith(hostRect))
|
||||
{
|
||||
isInViewport = true;
|
||||
|
||||
if (lazyLoadingSource is not null)
|
||||
{
|
||||
object source = lazyLoadingSource;
|
||||
lazyLoadingSource = null;
|
||||
SetSource(source);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isInViewport = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal sealed class ImageExFailedEventArgs : EventArgs
|
||||
{
|
||||
public ImageExFailedEventArgs(Exception errorException)
|
||||
{
|
||||
ErrorMessage = ErrorException?.Message;
|
||||
ErrorException = errorException;
|
||||
}
|
||||
|
||||
public Exception? ErrorException { get; private set; }
|
||||
|
||||
public string? ErrorMessage { get; private set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Control.Image.Implementation;
|
||||
|
||||
internal sealed class ImageExOpenedEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using System.Numerics;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
internal sealed class DefaultItemCollectionTransitionProvider : ItemCollectionTransitionProvider
|
||||
{
|
||||
private const double DefaultAnimationDurationInMs = 300.0;
|
||||
|
||||
static DefaultItemCollectionTransitionProvider()
|
||||
{
|
||||
AnimationSlowdownFactor = 1.0;
|
||||
}
|
||||
|
||||
public static double AnimationSlowdownFactor { get; set; }
|
||||
|
||||
protected override bool ShouldAnimateCore(ItemCollectionTransition transition)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void StartTransitions(IList<ItemCollectionTransition> transitions)
|
||||
{
|
||||
List<ItemCollectionTransition> addTransitions = new();
|
||||
List<ItemCollectionTransition> removeTransitions = new();
|
||||
List<ItemCollectionTransition> moveTransitions = new();
|
||||
|
||||
foreach (ItemCollectionTransition transition in addTransitions)
|
||||
{
|
||||
switch (transition.Operation)
|
||||
{
|
||||
case ItemCollectionTransitionOperation.Add:
|
||||
addTransitions.Add(transition);
|
||||
break;
|
||||
case ItemCollectionTransitionOperation.Remove:
|
||||
removeTransitions.Add(transition);
|
||||
break;
|
||||
case ItemCollectionTransitionOperation.Move:
|
||||
moveTransitions.Add(transition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StartAddTransitions(addTransitions, removeTransitions.Count > 0, moveTransitions.Count > 0);
|
||||
StartRemoveTransitions(removeTransitions);
|
||||
StartMoveTransitions(moveTransitions, removeTransitions.Count > 0);
|
||||
}
|
||||
|
||||
private static void StartAddTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveTransitions, bool hasMoveTransitions)
|
||||
{
|
||||
foreach (ItemCollectionTransition transition in transitions)
|
||||
{
|
||||
ItemCollectionTransitionProgress progress = transition.Start();
|
||||
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
|
||||
Compositor compositor = visual.Compositor;
|
||||
|
||||
ScalarKeyFrameAnimation fadeInAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||
fadeInAnimation.InsertKeyFrame(0.0f, 0.0f);
|
||||
|
||||
if (hasMoveTransitions && hasRemoveTransitions)
|
||||
{
|
||||
fadeInAnimation.InsertKeyFrame(0.66f, 0.0f);
|
||||
}
|
||||
else if (hasMoveTransitions || hasRemoveTransitions)
|
||||
{
|
||||
fadeInAnimation.InsertKeyFrame(0.5f, 0.0f);
|
||||
}
|
||||
|
||||
fadeInAnimation.InsertKeyFrame(1.0f, 1.0f);
|
||||
fadeInAnimation.Duration = TimeSpan.FromMilliseconds(
|
||||
DefaultAnimationDurationInMs * ((hasRemoveTransitions ? 1 : 0) + (hasMoveTransitions ? 1 : 0) + 1) * AnimationSlowdownFactor);
|
||||
|
||||
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
|
||||
visual.StartAnimation("Opacity", fadeInAnimation);
|
||||
batch.End();
|
||||
batch.Completed += (_, _) => progress.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartRemoveTransitions(IList<ItemCollectionTransition> transitions)
|
||||
{
|
||||
foreach (ItemCollectionTransition transition in transitions)
|
||||
{
|
||||
ItemCollectionTransitionProgress progress = transition.Start();
|
||||
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
|
||||
Compositor compositor = visual.Compositor;
|
||||
|
||||
ScalarKeyFrameAnimation fadeOutAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||
fadeOutAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue");
|
||||
fadeOutAnimation.InsertKeyFrame(1.0f, 0.0f);
|
||||
fadeOutAnimation.Duration = TimeSpan.FromMilliseconds(DefaultAnimationDurationInMs * AnimationSlowdownFactor);
|
||||
|
||||
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
|
||||
visual.StartAnimation(nameof(Visual.Opacity), fadeOutAnimation);
|
||||
batch.End();
|
||||
batch.Completed += (_, _) =>
|
||||
{
|
||||
visual.Opacity = 1.0f;
|
||||
progress.Complete();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartMoveTransitions(IList<ItemCollectionTransition> transitions, bool hasRemoveAnimations)
|
||||
{
|
||||
foreach (ItemCollectionTransition transition in transitions)
|
||||
{
|
||||
ItemCollectionTransitionProgress progress = transition.Start();
|
||||
Visual visual = ElementCompositionPreview.GetElementVisual(progress.Element);
|
||||
Compositor compositor = visual.Compositor;
|
||||
CompositionScopedBatch batch = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
|
||||
|
||||
// Animate offset.
|
||||
if (transition.OldBounds.X != transition.NewBounds.X ||
|
||||
transition.OldBounds.Y != transition.NewBounds.Y)
|
||||
{
|
||||
AnimateOffset(visual, compositor, transition.OldBounds, transition.NewBounds, hasRemoveAnimations);
|
||||
}
|
||||
|
||||
batch.End();
|
||||
batch.Completed += (_, _) => progress.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AnimateOffset(Visual visual, Compositor compositor, Rect oldBounds, Rect newBounds, bool hasRemoveAnimations)
|
||||
{
|
||||
Vector2KeyFrameAnimation offsetAnimation = compositor.CreateVector2KeyFrameAnimation();
|
||||
|
||||
offsetAnimation.SetVector2Parameter("delta", new Vector2(
|
||||
(float)(oldBounds.X - newBounds.X),
|
||||
(float)(oldBounds.Y - newBounds.Y)));
|
||||
offsetAnimation.SetVector2Parameter("final", default);
|
||||
offsetAnimation.InsertExpressionKeyFrame(0.0f, "this.CurrentValue + delta");
|
||||
if (hasRemoveAnimations)
|
||||
{
|
||||
offsetAnimation.InsertExpressionKeyFrame(0.5f, "delta");
|
||||
}
|
||||
|
||||
offsetAnimation.InsertExpressionKeyFrame(1.0f, "final");
|
||||
offsetAnimation.Duration = TimeSpan.FromMilliseconds(
|
||||
DefaultAnimationDurationInMs * ((hasRemoveAnimations ? 1 : 0) + 1) * AnimationSlowdownFactor);
|
||||
|
||||
visual.StartAnimation("TransformMatrix._41_42", offsetAnimation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
[DebuggerDisplay("Count = {Count}, Height = {Height}")]
|
||||
internal class UniformStaggeredColumnLayout : List<UniformStaggeredItem>
|
||||
{
|
||||
public double Height { get; private set; }
|
||||
|
||||
public new void Add(UniformStaggeredItem item)
|
||||
{
|
||||
Height = item.Top + item.Height;
|
||||
base.Add(item);
|
||||
}
|
||||
|
||||
public new void Clear()
|
||||
{
|
||||
Height = 0;
|
||||
base.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
internal sealed class UniformStaggeredItem
|
||||
{
|
||||
public UniformStaggeredItem(int index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public double Top { get; internal set; }
|
||||
|
||||
public double Height { get; internal set; }
|
||||
|
||||
public int Index { get; }
|
||||
|
||||
public UIElement? Element { get; internal set; }
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
[DependencyProperty("MinItemWidth", typeof(double), 0D, nameof(OnMinItemWidthChanged))]
|
||||
[DependencyProperty("MinColumnSpacing", typeof(double), 0D, nameof(OnSpacingChanged))]
|
||||
[DependencyProperty("MinRowSpacing", typeof(double), 0D, nameof(OnSpacingChanged))]
|
||||
internal sealed partial class UniformStaggeredLayout : VirtualizingLayout
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void InitializeForContextCore(VirtualizingLayoutContext context)
|
||||
{
|
||||
context.LayoutState = new UniformStaggeredLayoutState(context);
|
||||
base.InitializeForContextCore(context);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void UninitializeForContextCore(VirtualizingLayoutContext context)
|
||||
{
|
||||
context.LayoutState = null;
|
||||
base.UninitializeForContextCore(context);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
UniformStaggeredLayoutState state = (UniformStaggeredLayoutState)context.LayoutState;
|
||||
|
||||
switch (args.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
state.RemoveFromIndex(args.NewStartingIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
state.RemoveFromIndex(args.NewStartingIndex);
|
||||
state.RecycleElementAt(args.NewStartingIndex); // We must recycle the element to ensure that it gets the correct context
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
int minIndex = Math.Min(args.NewStartingIndex, args.OldStartingIndex);
|
||||
int maxIndex = Math.Max(args.NewStartingIndex, args.OldStartingIndex);
|
||||
state.RemoveRange(minIndex, maxIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
state.RemoveFromIndex(args.OldStartingIndex);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
state.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
base.OnItemsChangedCore(context, source, args);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
|
||||
{
|
||||
if (context.ItemCount == 0)
|
||||
{
|
||||
return new Size(availableSize.Width, 0);
|
||||
}
|
||||
|
||||
if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0))
|
||||
{
|
||||
return new Size(availableSize.Width, 0.0f);
|
||||
}
|
||||
|
||||
UniformStaggeredLayoutState state = (UniformStaggeredLayoutState)context.LayoutState;
|
||||
|
||||
double availableWidth = availableSize.Width;
|
||||
double availableHeight = availableSize.Height;
|
||||
|
||||
(int numberOfColumns, double columnWidth) = GetNumberOfColumnsAndWidth(availableWidth, MinItemWidth, MinColumnSpacing);
|
||||
|
||||
if (columnWidth != state.ColumnWidth)
|
||||
{
|
||||
// The items will need to be remeasured
|
||||
state.Clear();
|
||||
}
|
||||
|
||||
state.ColumnWidth = columnWidth;
|
||||
|
||||
// adjust for column spacing on all columns expect the first
|
||||
double totalWidth = state.ColumnWidth + ((numberOfColumns - 1) * (state.ColumnWidth + MinColumnSpacing));
|
||||
if (totalWidth > availableWidth)
|
||||
{
|
||||
numberOfColumns--;
|
||||
}
|
||||
else if (double.IsInfinity(availableWidth))
|
||||
{
|
||||
availableWidth = totalWidth;
|
||||
}
|
||||
|
||||
if (numberOfColumns != state.NumberOfColumns)
|
||||
{
|
||||
// The items will not need to be remeasured, but they will need to go into new columns
|
||||
state.ClearColumns();
|
||||
}
|
||||
|
||||
if (MinRowSpacing != state.RowSpacing)
|
||||
{
|
||||
// If the RowSpacing changes the height of the rows will be different.
|
||||
// The columns stores the height so we'll want to clear them out to
|
||||
// get the proper height
|
||||
state.ClearColumns();
|
||||
state.RowSpacing = MinRowSpacing;
|
||||
}
|
||||
|
||||
Span<double> columnHeights = new double[numberOfColumns];
|
||||
Span<int> itemsPerColumn = new int[numberOfColumns];
|
||||
HashSet<int> deadColumns = new();
|
||||
|
||||
for (int i = 0; i < context.ItemCount; i++)
|
||||
{
|
||||
int columnIndex = GetLowestColumnIndex(columnHeights);
|
||||
|
||||
bool measured = false;
|
||||
UniformStaggeredItem item = state.GetItemAt(i);
|
||||
if (item.Height == 0)
|
||||
{
|
||||
// Item has not been measured yet. Get the element and store the values
|
||||
UIElement element = context.GetOrCreateElementAt(i);
|
||||
element.Measure(new Size(state.ColumnWidth, availableHeight));
|
||||
item.Height = element.DesiredSize.Height;
|
||||
item.Element = element;
|
||||
measured = true;
|
||||
}
|
||||
|
||||
double spacing = itemsPerColumn[columnIndex] > 0 ? MinRowSpacing : 0;
|
||||
item.Top = columnHeights[columnIndex] + spacing;
|
||||
double bottom = item.Top + item.Height;
|
||||
columnHeights[columnIndex] = bottom;
|
||||
itemsPerColumn[columnIndex]++;
|
||||
state.AddItemToColumn(item, columnIndex);
|
||||
|
||||
if (bottom < context.RealizationRect.Top)
|
||||
{
|
||||
// The bottom of the element is above the realization area
|
||||
if (item.Element is not null)
|
||||
{
|
||||
context.RecycleElement(item.Element);
|
||||
item.Element = null;
|
||||
}
|
||||
}
|
||||
else if (item.Top > context.RealizationRect.Bottom)
|
||||
{
|
||||
// The top of the element is below the realization area
|
||||
if (item.Element is not null)
|
||||
{
|
||||
context.RecycleElement(item.Element);
|
||||
item.Element = null;
|
||||
}
|
||||
|
||||
deadColumns.Add(columnIndex);
|
||||
}
|
||||
else if (measured == false)
|
||||
{
|
||||
// We ALWAYS want to measure an item that will be in the bounds
|
||||
item.Element = context.GetOrCreateElementAt(i);
|
||||
item.Element.Measure(new Size(state.ColumnWidth, availableHeight));
|
||||
if (item.Height != item.Element.DesiredSize.Height)
|
||||
{
|
||||
// this item changed size; we need to recalculate layout for everything after this
|
||||
state.RemoveFromIndex(i + 1);
|
||||
item.Height = item.Element.DesiredSize.Height;
|
||||
columnHeights[columnIndex] = item.Top + item.Height;
|
||||
}
|
||||
}
|
||||
|
||||
if (deadColumns.Count == numberOfColumns)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double desiredHeight = state.GetHeight();
|
||||
|
||||
return new Size(availableWidth, desiredHeight);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
|
||||
{
|
||||
if ((context.RealizationRect.Width == 0) && (context.RealizationRect.Height == 0))
|
||||
{
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
UniformStaggeredLayoutState state = (UniformStaggeredLayoutState)context.LayoutState;
|
||||
|
||||
// Cycle through each column and arrange the items that are within the realization bounds
|
||||
for (int columnIndex = 0; columnIndex < state.NumberOfColumns; columnIndex++)
|
||||
{
|
||||
UniformStaggeredColumnLayout layout = state.GetColumnLayout(columnIndex);
|
||||
Span<UniformStaggeredItem> layoutSpan = CollectionsMarshal.AsSpan(layout);
|
||||
for (int i = 0; i < layoutSpan.Length; i++)
|
||||
{
|
||||
ref readonly UniformStaggeredItem item = ref layoutSpan[i];
|
||||
|
||||
double bottom = item.Top + item.Height;
|
||||
if (bottom < context.RealizationRect.Top)
|
||||
{
|
||||
// element is above the realization bounds
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Top <= context.RealizationRect.Bottom)
|
||||
{
|
||||
double itemHorizontalOffset = (state.ColumnWidth * columnIndex) + (MinColumnSpacing * columnIndex);
|
||||
|
||||
Rect bounds = new(itemHorizontalOffset, item.Top, state.ColumnWidth, item.Height);
|
||||
UIElement element = context.GetOrCreateElementAt(item.Index);
|
||||
element.Arrange(bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private static (int NumberOfColumns, double ColumnWidth) GetNumberOfColumnsAndWidth(double availableWidth, double minItemWidth, double minColumnSpacing)
|
||||
{
|
||||
// test if the width can fit in 2 items
|
||||
if ((2 * minItemWidth) + minColumnSpacing > availableWidth)
|
||||
{
|
||||
return (1, availableWidth);
|
||||
}
|
||||
|
||||
int columnCount = Math.Max(1, (int)((availableWidth + minColumnSpacing) / (minItemWidth + minColumnSpacing)));
|
||||
double columnWidthAddSpacing = (availableWidth + minColumnSpacing) / columnCount;
|
||||
return (columnCount, columnWidthAddSpacing - minColumnSpacing);
|
||||
}
|
||||
|
||||
private static int GetLowestColumnIndex(in ReadOnlySpan<double> columnHeights)
|
||||
{
|
||||
int columnIndex = 0;
|
||||
double height = columnHeights[0];
|
||||
for (int j = 1; j < columnHeights.Length; j++)
|
||||
{
|
||||
if (columnHeights[j] < height)
|
||||
{
|
||||
columnIndex = j;
|
||||
height = columnHeights[j];
|
||||
}
|
||||
}
|
||||
|
||||
return columnIndex;
|
||||
}
|
||||
|
||||
private static void OnMinItemWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UniformStaggeredLayout panel = (UniformStaggeredLayout)d;
|
||||
panel.InvalidateMeasure();
|
||||
}
|
||||
|
||||
private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UniformStaggeredLayout panel = (UniformStaggeredLayout)d;
|
||||
panel.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Snap.Hutao.Control.Layout;
|
||||
|
||||
internal sealed class UniformStaggeredLayoutState
|
||||
{
|
||||
private readonly List<UniformStaggeredItem> items = new();
|
||||
private readonly VirtualizingLayoutContext context;
|
||||
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = new();
|
||||
private double lastAverageHeight;
|
||||
|
||||
public UniformStaggeredLayoutState(VirtualizingLayoutContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public double ColumnWidth { get; internal set; }
|
||||
|
||||
public int NumberOfColumns
|
||||
{
|
||||
get => columnLayout.Count;
|
||||
}
|
||||
|
||||
public double RowSpacing { get; internal set; }
|
||||
|
||||
internal void AddItemToColumn(UniformStaggeredItem item, int columnIndex)
|
||||
{
|
||||
if (!this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout))
|
||||
{
|
||||
columnLayout = new();
|
||||
this.columnLayout[columnIndex] = columnLayout;
|
||||
}
|
||||
|
||||
if (!columnLayout.Contains(item))
|
||||
{
|
||||
columnLayout.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA2201")]
|
||||
internal UniformStaggeredItem GetItemAt(int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
if (index <= (items.Count - 1))
|
||||
{
|
||||
return items[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
UniformStaggeredItem item = new(index);
|
||||
items.Add(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("", "SH007")]
|
||||
internal UniformStaggeredColumnLayout GetColumnLayout(int columnIndex)
|
||||
{
|
||||
this.columnLayout.TryGetValue(columnIndex, out UniformStaggeredColumnLayout? columnLayout);
|
||||
return columnLayout!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear everything that has been calculated.
|
||||
/// </summary>
|
||||
internal void Clear()
|
||||
{
|
||||
columnLayout.Clear();
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the layout columns so they will be recalculated.
|
||||
/// </summary>
|
||||
internal void ClearColumns()
|
||||
{
|
||||
columnLayout.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the estimated height of the layout.
|
||||
/// </summary>
|
||||
/// <returns>The estimated height of the layout.</returns>
|
||||
/// <remarks>
|
||||
/// If all of the items have been calculated then the actual height will be returned.
|
||||
/// If all of the items have not been calculated then an estimated height will be calculated based on the average height of the items.
|
||||
/// </remarks>
|
||||
internal double GetHeight()
|
||||
{
|
||||
double desiredHeight = columnLayout.Values.Max(c => c.Height);
|
||||
int itemCount = columnLayout.Values.Sum(c => c.Count);
|
||||
|
||||
if (itemCount == context.ItemCount)
|
||||
{
|
||||
return desiredHeight;
|
||||
}
|
||||
|
||||
double averageHeight = 0;
|
||||
foreach ((_, UniformStaggeredColumnLayout layout) in columnLayout)
|
||||
{
|
||||
averageHeight += layout.Height / layout.Count;
|
||||
}
|
||||
|
||||
averageHeight /= columnLayout.Count;
|
||||
double estimatedHeight = (averageHeight * context.ItemCount) / columnLayout.Count;
|
||||
if (estimatedHeight > desiredHeight)
|
||||
{
|
||||
desiredHeight = estimatedHeight;
|
||||
}
|
||||
|
||||
if (Math.Abs(desiredHeight - lastAverageHeight) < 5)
|
||||
{
|
||||
return lastAverageHeight;
|
||||
}
|
||||
|
||||
lastAverageHeight = desiredHeight;
|
||||
return desiredHeight;
|
||||
}
|
||||
|
||||
internal void RecycleElementAt(int index)
|
||||
{
|
||||
UIElement element = context.GetOrCreateElementAt(index);
|
||||
context.RecycleElement(element);
|
||||
}
|
||||
|
||||
internal void RemoveFromIndex(int index)
|
||||
{
|
||||
if (index >= items.Count)
|
||||
{
|
||||
// Item was added/removed but we haven't realized that far yet
|
||||
return;
|
||||
}
|
||||
|
||||
int numToRemove = items.Count - index;
|
||||
items.RemoveRange(index, numToRemove);
|
||||
|
||||
foreach ((_, UniformStaggeredColumnLayout layout) in columnLayout)
|
||||
{
|
||||
Span<UniformStaggeredItem> layoutSpan = CollectionsMarshal.AsSpan(layout);
|
||||
for (int i = 0; i < layoutSpan.Length; i++)
|
||||
{
|
||||
if (layoutSpan[i].Index >= index)
|
||||
{
|
||||
numToRemove = layoutSpan.Length - i;
|
||||
layout.RemoveRange(i, numToRemove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveRange(int startIndex, int endIndex)
|
||||
{
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
if (i > items.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
ref readonly UniformStaggeredItem item = ref CollectionsMarshal.AsSpan(items)[i];
|
||||
item.Height = 0;
|
||||
item.Top = 0;
|
||||
|
||||
// We must recycle all elements to ensure that it gets the correct context
|
||||
RecycleElementAt(i);
|
||||
}
|
||||
|
||||
foreach ((_, UniformStaggeredColumnLayout layout) in columnLayout)
|
||||
{
|
||||
Span<UniformStaggeredItem> layoutSpan = CollectionsMarshal.AsSpan(layout);
|
||||
for (int i = 0; i < layoutSpan.Length; i++)
|
||||
{
|
||||
if ((startIndex <= layoutSpan[i].Index) && (layoutSpan[i].Index <= endIndex))
|
||||
{
|
||||
int numToRemove = layoutSpan.Length - i;
|
||||
layout.RemoveRange(i, numToRemove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/Snap.Hutao/Snap.Hutao/Control/Loading.cs
Normal file
48
src/Snap.Hutao/Snap.Hutao/Control/Loading.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Snap.Hutao.Control;
|
||||
|
||||
[TemplateVisualState(Name = "LoadingIn", GroupName = "CommonStates")]
|
||||
[TemplateVisualState(Name = "LoadingOut", GroupName = "CommonStates")]
|
||||
internal class Loading : Microsoft.UI.Xaml.Controls.ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(Loading), new PropertyMetadata(default(bool), IsLoadingPropertyChanged));
|
||||
|
||||
[SuppressMessage("", "IDE0052")]
|
||||
private FrameworkElement? presenter;
|
||||
|
||||
public Loading()
|
||||
{
|
||||
DefaultStyleKey = typeof(Loading);
|
||||
DefaultStyleResourceUri = new("ms-appx:///Control/Loading.xaml");
|
||||
}
|
||||
|
||||
public bool IsLoading
|
||||
{
|
||||
get => (bool)GetValue(IsLoadingProperty);
|
||||
set => SetValue(IsLoadingProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private static void IsLoadingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
Loading control = (Loading)d;
|
||||
control.presenter ??= control.GetTemplateChild("ContentGrid") as FrameworkElement;
|
||||
|
||||
control?.Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
VisualStateManager.GoToState(this, IsLoading ? "LoadingIn" : "LoadingOut", true);
|
||||
}
|
||||
}
|
||||
85
src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml
Normal file
85
src/Snap.Hutao/Snap.Hutao/Control/Loading.xaml
Normal file
@@ -0,0 +1,85 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shc="using:Snap.Hutao.Control">
|
||||
|
||||
<Style TargetType="shc:Loading">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="shc:Loading">
|
||||
<Border
|
||||
x:Name="RootGrid"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Opacity="0"
|
||||
Visibility="Collapsed">
|
||||
<ContentPresenter
|
||||
x:Name="ContentGrid"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
|
||||
<ContentPresenter.RenderTransform>
|
||||
<CompositeTransform/>
|
||||
</ContentPresenter.RenderTransform>
|
||||
</ContentPresenter>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="LoadingIn">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="0">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<Visibility>Visible</Visibility>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="LoadingOut">
|
||||
<Storyboard>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Opacity">
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0" Value="1">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseInOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
|
||||
<DiscreteObjectKeyFrame.Value>
|
||||
<Visibility>Collapsed</Visibility>
|
||||
</DiscreteObjectKeyFrame.Value>
|
||||
</DiscreteObjectKeyFrame>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -12,7 +12,6 @@ internal sealed class Int32Extension : MarkupExtension
|
||||
|
||||
protected override object ProvideValue()
|
||||
{
|
||||
_ = int.TryParse(Value, out int result);
|
||||
return result;
|
||||
return XamlBindingHelper.ConvertValue(typeof(int), Value);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System.Buffers.Binary;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.UI;
|
||||
|
||||
@@ -39,7 +39,7 @@ internal struct Rgba32
|
||||
/// </summary>
|
||||
/// <param name="hex">色值字符串</param>
|
||||
public Rgba32(string hex)
|
||||
: this(Convert.ToUInt32(hex, 16))
|
||||
: this(hex.Length == 6 ? Convert.ToUInt32($"{hex}FF", 16) : Convert.ToUInt32(hex, 16))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
/// <summary>
|
||||
/// 纵横比控件
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("TargetWidth", typeof(double), 1.0D)]
|
||||
[DependencyProperty("TargetHeight", typeof(double), 1.0D)]
|
||||
internal sealed partial class AspectRatio : Microsoft.UI.Xaml.Controls.Control
|
||||
{
|
||||
private const double Epsilon = 2.2204460492503131e-016;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
double ratio = TargetWidth / TargetHeight;
|
||||
double ratioAvailable = availableSize.Width / availableSize.Height;
|
||||
|
||||
if (Math.Abs(ratioAvailable - ratio) < Epsilon)
|
||||
{
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
// 更宽
|
||||
if (ratioAvailable > ratio)
|
||||
{
|
||||
double newWidth = ratio * availableSize.Height;
|
||||
return new Size(newWidth, availableSize.Height);
|
||||
}
|
||||
|
||||
// 更高
|
||||
if (ratioAvailable < ratio)
|
||||
{
|
||||
double newHeight = availableSize.Width / ratio;
|
||||
return new Size(availableSize.Width, newHeight);
|
||||
}
|
||||
|
||||
return availableSize;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<cwc:SegmentedItem
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentBulletedList}}"
|
||||
Tag="List"
|
||||
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
|
||||
<cwc:SegmentedItem
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentGridView}}"
|
||||
Tag="Grid"
|
||||
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Snap.Hutao.Core.Setting;
|
||||
|
||||
namespace Snap.Hutao.Control.Panel;
|
||||
|
||||
@@ -11,6 +12,8 @@ namespace Snap.Hutao.Control.Panel;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("Current", typeof(string), List)]
|
||||
[DependencyProperty("LocalSettingKeySuffixForCurrent", typeof(string))]
|
||||
[DependencyProperty("LocalSettingKeyExtraForCurrent", typeof(string), "")]
|
||||
internal sealed partial class PanelSelector : Segmented
|
||||
{
|
||||
public const string List = nameof(List);
|
||||
@@ -42,21 +45,41 @@ internal sealed partial class PanelSelector : Segmented
|
||||
selectedIndexChangedCallbackToken = RegisterPropertyChangedCallback(SelectedIndexProperty, OnSelectedIndexChanged);
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
Current = IndexTypeMap[(int)GetValue(dp)];
|
||||
}
|
||||
|
||||
private void OnRootLoaded(object sender, RoutedEventArgs e)
|
||||
private static void OnSelectedIndexChanged(DependencyObject sender, DependencyProperty dp)
|
||||
{
|
||||
PanelSelector selector = (PanelSelector)sender;
|
||||
selector.SelectedItem = selector.Items.Cast<SegmentedItem>().Single(item => (string)item.Tag == Current);
|
||||
selector.Current = IndexTypeMap[(int)selector.GetValue(dp)];
|
||||
|
||||
if (!string.IsNullOrEmpty(selector.LocalSettingKeySuffixForCurrent))
|
||||
{
|
||||
LocalSetting.Set(GetSettingKey(selector), selector.Current);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRootUnload(object sender, RoutedEventArgs e)
|
||||
private static void OnRootLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UnregisterPropertyChangedCallback(SelectedIndexProperty, selectedIndexChangedCallbackToken);
|
||||
Loaded -= loadedEventHandler;
|
||||
Unloaded -= unloadedEventHandler;
|
||||
PanelSelector selector = (PanelSelector)sender;
|
||||
|
||||
if (string.IsNullOrEmpty(selector.LocalSettingKeySuffixForCurrent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string value = LocalSetting.Get(GetSettingKey(selector), selector.Current);
|
||||
selector.Current = value;
|
||||
|
||||
selector.SelectedItem = selector.Items.Cast<SegmentedItem>().Single(item => (string)item.Tag == selector.Current);
|
||||
}
|
||||
|
||||
private static void OnRootUnload(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PanelSelector selector = (PanelSelector)sender;
|
||||
selector.UnregisterPropertyChangedCallback(SelectedIndexProperty, selector.selectedIndexChangedCallbackToken);
|
||||
selector.Unloaded -= selector.unloadedEventHandler;
|
||||
}
|
||||
|
||||
private static string GetSettingKey(PanelSelector selector)
|
||||
{
|
||||
return $"Control.PanelSelector.{selector.LocalSettingKeySuffixForCurrent}{selector.LocalSettingKeyExtraForCurrent}";
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ namespace Snap.Hutao.Control.Text;
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
|
||||
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
|
||||
internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
{
|
||||
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
|
||||
@@ -40,6 +41,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
Content = new TextBlock()
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Style = TextStyle,
|
||||
};
|
||||
|
||||
actualThemeChangedEventHandler = OnActualThemeChanged;
|
||||
@@ -54,6 +56,12 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
UpdateDescription(textBlock, description);
|
||||
}
|
||||
|
||||
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content;
|
||||
textBlock.Style = (Style)e.NewValue;
|
||||
}
|
||||
|
||||
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
|
||||
{
|
||||
textBlock.Inlines.Clear();
|
||||
@@ -66,7 +74,7 @@ internal sealed partial class DescriptionTextBlock : ContentControl
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
AppendLineBreak(textBlock);
|
||||
i += 1;
|
||||
i += 2;
|
||||
last = i;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Snap.Hutao.Control.Extension;
|
||||
using Snap.Hutao.Control.Media;
|
||||
using Snap.Hutao.Control.Theme;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Text;
|
||||
|
||||
[DependencyProperty("Description", typeof(string), "", nameof(OnDescriptionChanged))]
|
||||
[DependencyProperty("TextStyle", typeof(Style), default(Style), nameof(OnTextStyleChanged))]
|
||||
internal sealed partial class HtmlDescriptionTextBlock : ContentControl
|
||||
{
|
||||
private static readonly int ColorTagFullLength = "<color style='color:#FFFFFF;'></color>".Length;
|
||||
private static readonly int ColorTagLeftLength = "<color style='color:#FFFFFF;'>".Length;
|
||||
|
||||
private static readonly int ItalicTagFullLength = "<i></i>".Length;
|
||||
private static readonly int ItalicTagLeftLength = "<i>".Length;
|
||||
|
||||
private static readonly int BoldTagFullLength = "<b></b>".Length;
|
||||
private static readonly int BoldTagLeftLength = "<b>".Length;
|
||||
|
||||
private readonly TypedEventHandler<FrameworkElement, object> actualThemeChangedEventHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的呈现描述文本的文本块
|
||||
/// </summary>
|
||||
public HtmlDescriptionTextBlock()
|
||||
{
|
||||
this.DisableInteraction();
|
||||
|
||||
Content = new TextBlock()
|
||||
{
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Style = TextStyle,
|
||||
};
|
||||
|
||||
actualThemeChangedEventHandler = OnActualThemeChanged;
|
||||
ActualThemeChanged += actualThemeChangedEventHandler;
|
||||
}
|
||||
|
||||
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
TextBlock textBlock = (TextBlock)((HtmlDescriptionTextBlock)d).Content;
|
||||
ReadOnlySpan<char> description = (string)e.NewValue;
|
||||
|
||||
UpdateDescription(textBlock, description);
|
||||
}
|
||||
|
||||
private static void OnTextStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
TextBlock textBlock = (TextBlock)((HtmlDescriptionTextBlock)d).Content;
|
||||
textBlock.Style = (Style)e.NewValue;
|
||||
}
|
||||
|
||||
private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan<char> description)
|
||||
{
|
||||
textBlock.Inlines.Clear();
|
||||
|
||||
int last = 0;
|
||||
for (int i = 0; i < description.Length;)
|
||||
{
|
||||
// newline
|
||||
if (description[i..].StartsWith(@"<br>"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
AppendLineBreak(textBlock);
|
||||
i += 4;
|
||||
last = i;
|
||||
}
|
||||
|
||||
// color tag
|
||||
else if (description[i..].StartsWith("<c"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
string a = description.Slice(i + 21, 6).ToString();
|
||||
Rgba32 color = new(a);
|
||||
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
|
||||
AppendColorText(textBlock, description.Slice(i + ColorTagLeftLength, length), color);
|
||||
|
||||
i += length + ColorTagFullLength;
|
||||
last = i;
|
||||
}
|
||||
|
||||
// italic
|
||||
else if (description[i..].StartsWith("<i"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
|
||||
int length = description[(i + ItalicTagLeftLength)..].IndexOf('<');
|
||||
AppendItalicText(textBlock, description.Slice(i + ItalicTagLeftLength, length));
|
||||
|
||||
i += length + ItalicTagFullLength;
|
||||
last = i;
|
||||
}
|
||||
|
||||
// bold
|
||||
else if (description[i..].StartsWith("<b"))
|
||||
{
|
||||
AppendText(textBlock, description[last..i]);
|
||||
|
||||
int length = description[(i + BoldTagLeftLength)..].IndexOf('<');
|
||||
AppendBoldText(textBlock, description.Slice(i + BoldTagLeftLength, length));
|
||||
|
||||
i += length + BoldTagFullLength;
|
||||
last = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (i == description.Length - 1)
|
||||
{
|
||||
AppendText(textBlock, description[last..(i + 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendText(TextBlock text, in ReadOnlySpan<char> slice)
|
||||
{
|
||||
text.Inlines.Add(new Run { Text = slice.ToString() });
|
||||
}
|
||||
|
||||
private static void AppendColorText(TextBlock text, in ReadOnlySpan<char> slice, Rgba32 color)
|
||||
{
|
||||
Color targetColor;
|
||||
if (ThemeHelper.IsDarkMode(text.ActualTheme))
|
||||
{
|
||||
targetColor = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make lighter in light mode
|
||||
Hsl32 hsl = color.ToHsl();
|
||||
hsl.L *= 0.3;
|
||||
targetColor = Rgba32.FromHsl(hsl);
|
||||
}
|
||||
|
||||
text.Inlines.Add(new Run
|
||||
{
|
||||
Text = slice.ToString(),
|
||||
Foreground = new SolidColorBrush(targetColor),
|
||||
});
|
||||
}
|
||||
|
||||
private static void AppendBoldText(TextBlock text, in ReadOnlySpan<char> slice)
|
||||
{
|
||||
text.Inlines.Add(new Run
|
||||
{
|
||||
Text = slice.ToString(),
|
||||
FontWeight = FontWeights.Bold,
|
||||
});
|
||||
}
|
||||
|
||||
private static void AppendItalicText(TextBlock text, in ReadOnlySpan<char> slice)
|
||||
{
|
||||
text.Inlines.Add(new Run
|
||||
{
|
||||
Text = slice.ToString(),
|
||||
FontStyle = Windows.UI.Text.FontStyle.Italic,
|
||||
});
|
||||
}
|
||||
|
||||
private static void AppendLineBreak(TextBlock text)
|
||||
{
|
||||
text.Inlines.Add(new LineBreak());
|
||||
}
|
||||
|
||||
private void OnActualThemeChanged(FrameworkElement sender, object args)
|
||||
{
|
||||
// Simply re-apply texts
|
||||
UpdateDescription((TextBlock)Content, Description);
|
||||
}
|
||||
}
|
||||
21
src/Snap.Hutao/Snap.Hutao/Control/Theme/Card.xaml
Normal file
21
src/Snap.Hutao/Snap.Hutao/Control/Theme/Card.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwm="using:CommunityToolkit.WinUI.Media">
|
||||
<Style x:Key="BorderCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
</Style>
|
||||
<Style x:Key="GridCardStyle" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
</Style>
|
||||
<cwm:AttachedCardShadow
|
||||
x:Key="CompatCardShadow"
|
||||
Opacity="0.1"
|
||||
Offset="0,4,0"/>
|
||||
</ResourceDictionary>
|
||||
31
src/Snap.Hutao/Snap.Hutao/Control/Theme/Color.xaml
Normal file
31
src/Snap.Hutao/Snap.Hutao/Control/Theme/Color.xaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Color x:Key="AvatarPropertyAddValueColor">#FF74BF00</Color>
|
||||
<Color x:Key="CompatBackgroundColor">#FFF4F4F4</Color>
|
||||
<Color x:Key="DarkOnlyOverlayMaskColor">#00000000</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="AvatarPropertyAddValueColor">#FF90E800</Color>
|
||||
<Color x:Key="CompatBackgroundColor">#FF242424</Color>
|
||||
<Color x:Key="DarkOnlyOverlayMaskColor">#60000000</Color>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Color x:Key="BlueColor">#FF5180CB</Color>
|
||||
<Color x:Key="PurpleColor">#FFA156E0</Color>
|
||||
<Color x:Key="OrangeColor">#FFBC6932</Color>
|
||||
|
||||
<Color x:Key="GuaranteePullColor">#FF0063FF</Color>
|
||||
<Color x:Key="UpPullColor">#FFFFA400</Color>
|
||||
|
||||
<SolidColorBrush x:Key="BlueColorBrush" Color="{ThemeResource BlueColor}"/>
|
||||
<SolidColorBrush x:Key="PurpleColorBrush" Color="{ThemeResource PurpleColor}"/>
|
||||
<SolidColorBrush x:Key="OrangeColorBrush" Color="{ThemeResource OrangeColor}"/>
|
||||
|
||||
<SolidColorBrush x:Key="GuaranteePullCoolorBrush" Color="{ThemeResource GuaranteePullColor}"/>
|
||||
<SolidColorBrush x:Key="UpPullColorBrush" Color="{ThemeResource UpPullColor}"/>
|
||||
|
||||
<SolidColorBrush x:Key="DarkOnlyOverlayMaskColorBrush" Color="{ThemeResource DarkOnlyOverlayMaskColor}"/>
|
||||
<SolidColorBrush x:Key="AvatarPropertyAddValueColorBrush" Color="{ThemeResource AvatarPropertyAddValueColor}"/>
|
||||
</ResourceDictionary>
|
||||
35
src/Snap.Hutao/Snap.Hutao/Control/Theme/Converter.xaml
Normal file
35
src/Snap.Hutao/Snap.Hutao/Control/Theme/Converter.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Converter">
|
||||
<cwc:BoolNegationConverter x:Key="BoolNegationConverter"/>
|
||||
<cwc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<cwc:FileSizeToFriendlyStringConverter x:Key="FileSizeToFriendlyStringConverter"/>
|
||||
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
|
||||
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
|
||||
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
|
||||
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
|
||||
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>
|
||||
<shmmc:DescriptionsParametersDescriptor x:Key="DescParamDescriptor"/>
|
||||
<shmmc:ElementNameIconConverter x:Key="ElementNameIconConverter"/>
|
||||
<shmmc:EmotionIconConverter x:Key="EmotionIconConverter"/>
|
||||
<shmmc:EquipIconConverter x:Key="EquipIconConverter"/>
|
||||
<shmmc:GachaAvatarImgConverter x:Key="GachaAvatarImgConverter"/>
|
||||
<shmmc:GachaAvatarIconConverter x:Key="GachaAvatarIconConverter"/>
|
||||
<shmmc:GachaEquipIconConverter x:Key="GachaEquipIconConverter"/>
|
||||
<shmmc:ItemIconConverter x:Key="ItemIconConverter"/>
|
||||
<shmmc:MonsterIconConverter x:Key="MonsterIconConverter"/>
|
||||
<shmmc:PropertiesParametersDescriptor x:Key="PropertyDescriptor"/>
|
||||
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
|
||||
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
|
||||
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
|
||||
<shvc:EmptyObjectToBoolConverter x:Key="EmptyObjectToBoolConverter"/>
|
||||
<shvc:EmptyObjectToBoolRevertConverter x:Key="EmptyObjectToBoolRevertConverter"/>
|
||||
<shvc:EmptyObjectToVisibilityConverter x:Key="EmptyObjectToVisibilityConverter"/>
|
||||
<shvc:EmptyObjectToVisibilityRevertConverter x:Key="EmptyObjectToVisibilityRevertConverter"/>
|
||||
<shvc:Int32ToVisibilityConverter x:Key="Int32ToVisibilityConverter"/>
|
||||
<shvc:Int32ToVisibilityRevertConverter x:Key="Int32ToVisibilityRevertConverter"/>
|
||||
<shvc:StringBoolConverter x:Key="StringBoolConverter"/>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,4 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<CornerRadius x:Key="ControlCornerRadiusTop">4,4,0,0</CornerRadius>
|
||||
<CornerRadius x:Key="ControlCornerRadiusBottom">0,0,4,4</CornerRadius>
|
||||
</ResourceDictionary>
|
||||
22
src/Snap.Hutao/Snap.Hutao/Control/Theme/FlyoutStyle.xaml
Normal file
22
src/Snap.Hutao/Snap.Hutao/Control/Theme/FlyoutStyle.xaml
Normal file
@@ -0,0 +1,22 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style
|
||||
x:Key="WebViewerFlyoutPresenterStyle"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="CornerRadius" Value="0"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="FlyoutPresenterPadding0And2Style"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="0,2"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="FlyoutPresenterPadding6Style"
|
||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}"
|
||||
TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="6"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -1,4 +1,7 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<FontFamily x:Key="MiSans">ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
|
||||
<FontFamily x:Key="CascadiaMonoAndMiSans">ms-appx:///Resource/Font/CascadiaMono.ttf#Cascadia Mono, ms-appx:///Resource/Font/MiSans-Regular.ttf#MiSans</FontFamily>
|
||||
|
||||
@@ -79,13 +82,6 @@
|
||||
<Setter Property="OpticalMarginAlignment" Value="TrimSideBearings"/>
|
||||
</Style>
|
||||
|
||||
<x:Double x:Key="SecondaryTextFontSize">12</x:Double>
|
||||
|
||||
<Style x:Key="SecondaryTextStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="{StaticResource SecondaryTextFontSize}"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}"/>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultMenuFlyoutItemStyle}" TargetType="MenuFlyoutItem">
|
||||
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
|
||||
</Style>
|
||||
@@ -97,273 +93,4 @@
|
||||
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
|
||||
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
|
||||
</Style>
|
||||
<!-- TODO: When will DefaultInfoBarStyle added -->
|
||||
<Style TargetType="InfoBar">
|
||||
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="CloseButtonStyle" Value="{StaticResource InfoBarCloseButtonStyle}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource InfoBarBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource InfoBarBorderThickness}"/>
|
||||
<Setter Property="AutomationProperties.LandmarkType" Value="Custom"/>
|
||||
<Setter Property="AutomationProperties.IsDialog" Value="True"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="InfoBar">
|
||||
<Border
|
||||
x:Name="ContentRoot"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<!-- Background is used here so that it overrides the severity status color if set. -->
|
||||
<Grid
|
||||
MinHeight="{ThemeResource InfoBarMinHeight}"
|
||||
Padding="{StaticResource InfoBarContentRootPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- Icon -->
|
||||
<ColumnDefinition Width="*"/>
|
||||
<!-- Title, message, and action -->
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- Close button -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="StandardIconArea" Visibility="Collapsed">
|
||||
<TextBlock
|
||||
x:Name="IconBackground"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource InfoBarIconMargin}"
|
||||
VerticalAlignment="Top"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="{StaticResource InfoBarIconFontSize}"
|
||||
Foreground="{ThemeResource InfoBarInformationalSeverityIconBackground}"
|
||||
Text="{StaticResource InfoBarIconBackgroundGlyph}"/>
|
||||
<TextBlock
|
||||
x:Name="StandardIcon"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource InfoBarIconMargin}"
|
||||
VerticalAlignment="Top"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="{StaticResource InfoBarIconFontSize}"
|
||||
Foreground="{ThemeResource InfoBarInformationalSeverityIconForeground}"
|
||||
Text="{StaticResource InfoBarInformationalIconGlyph}"/>
|
||||
</Grid>
|
||||
<Viewbox
|
||||
x:Name="UserIconBox"
|
||||
Grid.Column="0"
|
||||
MaxWidth="{ThemeResource InfoBarIconFontSize}"
|
||||
MaxHeight="{ThemeResource InfoBarIconFontSize}"
|
||||
Margin="{ThemeResource InfoBarIconMargin}"
|
||||
VerticalAlignment="Top"
|
||||
Child="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.IconElement}"
|
||||
Visibility="Collapsed"/>
|
||||
<InfoBarPanel
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource InfoBarPanelMargin}"
|
||||
HorizontalOrientationPadding="{StaticResource InfoBarPanelHorizontalOrientationPadding}"
|
||||
VerticalOrientationPadding="{StaticResource InfoBarPanelVerticalOrientationPadding}">
|
||||
<TextBlock
|
||||
x:Name="Title"
|
||||
FontSize="{StaticResource InfoBarTitleFontSize}"
|
||||
FontWeight="{StaticResource InfoBarTitleFontWeight}"
|
||||
Foreground="{ThemeResource InfoBarTitleForeground}"
|
||||
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarTitleHorizontalOrientationMargin}"
|
||||
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarTitleVerticalOrientationMargin}"
|
||||
Text="{TemplateBinding Title}"
|
||||
TextWrapping="WrapWholeWords"/>
|
||||
<TextBlock
|
||||
x:Name="Message"
|
||||
FontSize="{StaticResource InfoBarMessageFontSize}"
|
||||
FontWeight="{StaticResource InfoBarMessageFontWeight}"
|
||||
Foreground="{ThemeResource InfoBarMessageForeground}"
|
||||
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarMessageHorizontalOrientationMargin}"
|
||||
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarMessageVerticalOrientationMargin}"
|
||||
Text="{TemplateBinding Message}"
|
||||
TextWrapping="WrapWholeWords"/>
|
||||
<ContentPresenter
|
||||
VerticalAlignment="Top"
|
||||
Content="{TemplateBinding ActionButton}"
|
||||
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarActionHorizontalOrientationMargin}"
|
||||
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarActionVerticalOrientationMargin}">
|
||||
<ContentPresenter.Resources>
|
||||
<Style BasedOn="{StaticResource DefaultHyperlinkButtonStyle}" TargetType="HyperlinkButton">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="{StaticResource InfoBarHyperlinkButtonMargin}"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource InfoBarHyperlinkButtonForeground}"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</ContentPresenter.Resources>
|
||||
</ContentPresenter>
|
||||
</InfoBarPanel>
|
||||
<ContentPresenter
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||
<Button
|
||||
Name="CloseButton"
|
||||
Grid.Column="2"
|
||||
Command="{TemplateBinding CloseButtonCommand}"
|
||||
CommandParameter="{TemplateBinding CloseButtonCommandParameter}"
|
||||
Style="{TemplateBinding CloseButtonStyle}">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
|
||||
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
|
||||
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
|
||||
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
|
||||
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
|
||||
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
|
||||
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
|
||||
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
|
||||
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
|
||||
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
<Viewbox
|
||||
Width="{StaticResource InfoBarCloseButtonGlyphSize}"
|
||||
Height="{StaticResource InfoBarCloseButtonGlyphSize}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<SymbolIcon Symbol="{StaticResource InfoBarCloseButtonSymbol}"/>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="SeverityLevels">
|
||||
<VisualState x:Name="Informational"/>
|
||||
<VisualState x:Name="Error">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarErrorSeverityBackgroundBrush}"/>
|
||||
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconBackground}"/>
|
||||
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarErrorIconGlyph}"/>
|
||||
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconForeground}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Warning">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarWarningSeverityBackgroundBrush}"/>
|
||||
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconBackground}"/>
|
||||
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarWarningIconGlyph}"/>
|
||||
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconForeground}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Success">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarSuccessSeverityBackgroundBrush}"/>
|
||||
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconBackground}"/>
|
||||
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarSuccessIconGlyph}"/>
|
||||
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconForeground}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="IconStates">
|
||||
<VisualState x:Name="StandardIconVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="UserIconBox.Visibility" Value="Collapsed"/>
|
||||
<Setter Target="StandardIconArea.Visibility" Value="Visible"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="UserIconVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="UserIconBox.Visibility" Value="Visible"/>
|
||||
<Setter Target="StandardIconArea.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoIconVisible"/>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="CloseButtonVisible"/>
|
||||
<VisualState x:Name="CloseButtonCollapsed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="InfoBarVisibility">
|
||||
<VisualState x:Name="InfoBarVisible"/>
|
||||
<VisualState x:Name="InfoBarCollapsed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Visibility" Value="Collapsed"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="ForegroundNotSet"/>
|
||||
<VisualState x:Name="ForegroundSet">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="Title.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
|
||||
<Setter Target="Message.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
|
||||
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
20
src/Snap.Hutao/Snap.Hutao/Control/Theme/Glyph.xaml
Normal file
20
src/Snap.Hutao/Snap.Hutao/Control/Theme/Glyph.xaml
Normal file
@@ -0,0 +1,20 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<x:String x:Key="FontIconContentAdd"></x:String>
|
||||
<x:String x:Key="FontIconContentSetting"></x:String>
|
||||
<x:String x:Key="FontIconContentRefresh"></x:String>
|
||||
<x:String x:Key="FontIconContentDelete"></x:String>
|
||||
<x:String x:Key="FontIconContentChevronRight"></x:String>
|
||||
<x:String x:Key="FontIconContentWarning"></x:String>
|
||||
<x:String x:Key="FontIconContentGame"></x:String>
|
||||
<x:String x:Key="FontIconContentOpenInNewWindow"></x:String>
|
||||
<x:String x:Key="FontIconContentFolder"></x:String>
|
||||
<x:String x:Key="FontIconContentCopy"></x:String>
|
||||
<x:String x:Key="FontIconContentBulletedList"></x:String>
|
||||
<x:String x:Key="FontIconContentCheckList"></x:String>
|
||||
<x:String x:Key="FontIconContentWebsite"></x:String>
|
||||
<x:String x:Key="FontIconContentHomeGroup"></x:String>
|
||||
<x:String x:Key="FontIconContentAsteriskBadge12"></x:String>
|
||||
<x:String x:Key="FontIconContentZipFolder"></x:String>
|
||||
<x:String x:Key="FontIconContentGridView"></x:String>
|
||||
<x:String x:Key="FontIconContentGiftboxOpen"></x:String>
|
||||
</ResourceDictionary>
|
||||
265
src/Snap.Hutao/Snap.Hutao/Control/Theme/InfoBarOverride.xaml
Normal file
265
src/Snap.Hutao/Snap.Hutao/Control/Theme/InfoBarOverride.xaml
Normal file
@@ -0,0 +1,265 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shch="using:Snap.Hutao.Control.Helper">
|
||||
<Thickness x:Key="InfoBarIconMargin">19,16,19,16</Thickness>
|
||||
<Thickness x:Key="InfoBarContentRootPadding">0,0,0,0</Thickness>
|
||||
<x:Double x:Key="InfoBarIconFontSize">20</x:Double>
|
||||
<!-- TODO: When will DefaultInfoBarStyle added -->
|
||||
<Style TargetType="InfoBar">
|
||||
<Setter Property="shch:InfoBarHelper.IsTextSelectionEnabled" Value="False"/>
|
||||
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
|
||||
<Setter Property="IsTabStop" Value="False"/>
|
||||
<Setter Property="CloseButtonStyle" Value="{StaticResource InfoBarCloseButtonStyle}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource InfoBarBorderBrush}"/>
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource InfoBarBorderThickness}"/>
|
||||
<Setter Property="AutomationProperties.LandmarkType" Value="Custom"/>
|
||||
<Setter Property="AutomationProperties.IsDialog" Value="True"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="InfoBar">
|
||||
<Border
|
||||
x:Name="ContentRoot"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<!-- Background is used here so that it overrides the severity status color if set. -->
|
||||
<Grid
|
||||
MinHeight="{ThemeResource InfoBarMinHeight}"
|
||||
Padding="{StaticResource InfoBarContentRootPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- Icon -->
|
||||
<ColumnDefinition Width="*"/>
|
||||
<!-- Title, message, and action -->
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<!-- Close button -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="StandardIconArea" Visibility="Collapsed">
|
||||
<TextBlock
|
||||
x:Name="IconBackground"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource InfoBarIconMargin}"
|
||||
VerticalAlignment="Top"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="{StaticResource InfoBarIconFontSize}"
|
||||
Foreground="{ThemeResource InfoBarInformationalSeverityIconBackground}"
|
||||
Text="{StaticResource InfoBarIconBackgroundGlyph}"/>
|
||||
<TextBlock
|
||||
x:Name="StandardIcon"
|
||||
Grid.Column="0"
|
||||
Margin="{StaticResource InfoBarIconMargin}"
|
||||
VerticalAlignment="Top"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="{StaticResource InfoBarIconFontSize}"
|
||||
Foreground="{ThemeResource InfoBarInformationalSeverityIconForeground}"
|
||||
Text="{StaticResource InfoBarInformationalIconGlyph}"/>
|
||||
</Grid>
|
||||
<Viewbox
|
||||
x:Name="UserIconBox"
|
||||
Grid.Column="0"
|
||||
MaxWidth="{ThemeResource InfoBarIconFontSize}"
|
||||
MaxHeight="{ThemeResource InfoBarIconFontSize}"
|
||||
Margin="{ThemeResource InfoBarIconMargin}"
|
||||
VerticalAlignment="Top"
|
||||
Child="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.IconElement}"
|
||||
Visibility="Collapsed"/>
|
||||
<InfoBarPanel
|
||||
Grid.Column="1"
|
||||
Margin="{StaticResource InfoBarPanelMargin}"
|
||||
HorizontalOrientationPadding="{StaticResource InfoBarPanelHorizontalOrientationPadding}"
|
||||
VerticalOrientationPadding="{StaticResource InfoBarPanelVerticalOrientationPadding}">
|
||||
<TextBlock
|
||||
x:Name="Title"
|
||||
FontSize="{StaticResource InfoBarTitleFontSize}"
|
||||
FontWeight="{StaticResource InfoBarTitleFontWeight}"
|
||||
Foreground="{ThemeResource InfoBarTitleForeground}"
|
||||
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarTitleHorizontalOrientationMargin}"
|
||||
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarTitleVerticalOrientationMargin}"
|
||||
IsTextSelectionEnabled="{TemplateBinding shch:InfoBarHelper.IsTextSelectionEnabled}"
|
||||
Text="{TemplateBinding Title}"
|
||||
TextWrapping="WrapWholeWords"/>
|
||||
<TextBlock
|
||||
x:Name="Message"
|
||||
FontSize="{StaticResource InfoBarMessageFontSize}"
|
||||
FontWeight="{StaticResource InfoBarMessageFontWeight}"
|
||||
Foreground="{ThemeResource InfoBarMessageForeground}"
|
||||
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarMessageHorizontalOrientationMargin}"
|
||||
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarMessageVerticalOrientationMargin}"
|
||||
IsTextSelectionEnabled="{TemplateBinding shch:InfoBarHelper.IsTextSelectionEnabled}"
|
||||
Text="{TemplateBinding Message}"
|
||||
TextWrapping="WrapWholeWords"/>
|
||||
<ContentPresenter
|
||||
VerticalAlignment="Top"
|
||||
Content="{TemplateBinding ActionButton}"
|
||||
InfoBarPanel.HorizontalOrientationMargin="{StaticResource InfoBarActionHorizontalOrientationMargin}"
|
||||
InfoBarPanel.VerticalOrientationMargin="{StaticResource InfoBarActionVerticalOrientationMargin}">
|
||||
<ContentPresenter.Resources>
|
||||
<Style BasedOn="{StaticResource DefaultHyperlinkButtonStyle}" TargetType="HyperlinkButton">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="{StaticResource InfoBarHyperlinkButtonMargin}"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource InfoBarHyperlinkButtonForeground}"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
</ContentPresenter.Resources>
|
||||
</ContentPresenter>
|
||||
</InfoBarPanel>
|
||||
<ContentPresenter
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||
<Button
|
||||
Name="CloseButton"
|
||||
Grid.Column="2"
|
||||
Command="{TemplateBinding CloseButtonCommand}"
|
||||
CommandParameter="{TemplateBinding CloseButtonCommandParameter}"
|
||||
Style="{TemplateBinding CloseButtonStyle}">
|
||||
<Button.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
|
||||
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
|
||||
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
|
||||
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
|
||||
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
|
||||
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
|
||||
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="AppBarButtonBackground"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="AppBarButtonBackgroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="AppBarButtonBackgroundPressed"/>
|
||||
<StaticResource x:Key="ButtonBackgroundDisabled" ResourceKey="AppBarButtonBackgroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonForeground" ResourceKey="AppBarButtonForeground"/>
|
||||
<StaticResource x:Key="ButtonForegroundPointerOver" ResourceKey="AppBarButtonForegroundPointerOver"/>
|
||||
<StaticResource x:Key="ButtonForegroundPressed" ResourceKey="AppBarButtonForegroundPressed"/>
|
||||
<StaticResource x:Key="ButtonForegroundDisabled" ResourceKey="AppBarButtonForegroundDisabled"/>
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="AppBarButtonBorderBrush"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="AppBarButtonBorderBrushPointerOver"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="AppBarButtonBorderBrushPressed"/>
|
||||
<StaticResource x:Key="ButtonBorderBrushDisabled" ResourceKey="AppBarButtonBorderBrushDisabled"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
<Viewbox
|
||||
Width="{StaticResource InfoBarCloseButtonGlyphSize}"
|
||||
Height="{StaticResource InfoBarCloseButtonGlyphSize}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<SymbolIcon Symbol="{StaticResource InfoBarCloseButtonSymbol}"/>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="SeverityLevels">
|
||||
<VisualState x:Name="Informational"/>
|
||||
<VisualState x:Name="Error">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarErrorSeverityBackgroundBrush}"/>
|
||||
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconBackground}"/>
|
||||
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarErrorIconGlyph}"/>
|
||||
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarErrorSeverityIconForeground}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Warning">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarWarningSeverityBackgroundBrush}"/>
|
||||
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconBackground}"/>
|
||||
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarWarningIconGlyph}"/>
|
||||
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarWarningSeverityIconForeground}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Success">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Background" Value="{ThemeResource InfoBarSuccessSeverityBackgroundBrush}"/>
|
||||
<Setter Target="IconBackground.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconBackground}"/>
|
||||
<Setter Target="StandardIcon.Text" Value="{StaticResource InfoBarSuccessIconGlyph}"/>
|
||||
<Setter Target="StandardIcon.Foreground" Value="{ThemeResource InfoBarSuccessSeverityIconForeground}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="IconStates">
|
||||
<VisualState x:Name="StandardIconVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="UserIconBox.Visibility" Value="Collapsed"/>
|
||||
<Setter Target="StandardIconArea.Visibility" Value="Visible"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="UserIconVisible">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="UserIconBox.Visibility" Value="Visible"/>
|
||||
<Setter Target="StandardIconArea.Visibility" Value="Collapsed"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoIconVisible"/>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="CloseButtonVisible"/>
|
||||
<VisualState x:Name="CloseButtonCollapsed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="CloseButton.Visibility" Value="Collapsed"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="InfoBarVisibility">
|
||||
<VisualState x:Name="InfoBarVisible"/>
|
||||
<VisualState x:Name="InfoBarCollapsed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentRoot.Visibility" Value="Collapsed"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="ForegroundNotSet"/>
|
||||
<VisualState x:Name="ForegroundSet">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="Title.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
|
||||
<Setter Target="Message.Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,38 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cwcont="using:CommunityToolkit.WinUI.Controls">
|
||||
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
|
||||
<ItemsStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="WrapPanelSpacing0Template">
|
||||
<cwcont:WrapPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="WrapPanelSpacing4Template">
|
||||
<cwcont:WrapPanel HorizontalSpacing="4" VerticalSpacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing0Template">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="HorizontalStackPanelSpacing2Template">
|
||||
<StackPanel Orientation="Horizontal" Spacing="2"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="UniformGridColumns2Spacing2Template">
|
||||
<cwcont:UniformGrid
|
||||
ColumnSpacing="2"
|
||||
Columns="2"
|
||||
RowSpacing="2"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="UniformGridColumns5Spacing4Template">
|
||||
<cwcont:UniformGrid
|
||||
ColumnSpacing="4"
|
||||
Columns="5"
|
||||
RowSpacing="4"/>
|
||||
</ItemsPanelTemplate>
|
||||
<ItemsPanelTemplate x:Key="UniformGridColumns5Spacing8Template">
|
||||
<cwcont:UniformGrid
|
||||
ColumnSpacing="8"
|
||||
Columns="5"
|
||||
RowSpacing="8"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ResourceDictionary>
|
||||
14
src/Snap.Hutao/Snap.Hutao/Control/Theme/NumericValue.xaml
Normal file
14
src/Snap.Hutao/Snap.Hutao/Control/Theme/NumericValue.xaml
Normal file
@@ -0,0 +1,14 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Length -->
|
||||
<GridLength x:Key="CompatGridLength2">288</GridLength>
|
||||
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength">212</x:Double>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength2">304</x:Double>
|
||||
<x:Double x:Key="CompatSplitViewOpenPaneLength3">320</x:Double>
|
||||
<x:Double x:Key="HomeAdaptiveCardHeight">180</x:Double>
|
||||
<x:Double x:Key="ContentDialogMinHeight">64</x:Double>
|
||||
<x:Double x:Key="LargeAppBarButtonWidth">100</x:Double>
|
||||
|
||||
<!-- ProgressBar -->
|
||||
<x:Double x:Key="LargeBackgroundProgressBarOpacity">0.2</x:Double>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,6 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Page Transparent Background -->
|
||||
<StaticResource x:Key="ApplicationPageBackgroundThemeBrush" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<!-- https://github.com/microsoft/microsoft-ui-xaml/issues/4811 -->
|
||||
<x:Int32 x:Key="__DiscardPageOverride">0</x:Int32>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,5 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<x:Double x:Key="PivotHeaderItemFontSize">16</x:Double>
|
||||
<Thickness x:Key="PivotHeaderItemMargin">16,0,0,0</Thickness>
|
||||
<Thickness x:Key="PivotItemMargin">0</Thickness>
|
||||
</ResourceDictionary>
|
||||
35
src/Snap.Hutao/Snap.Hutao/Control/Theme/SettingsStyle.xaml
Normal file
35
src/Snap.Hutao/Snap.Hutao/Control/Theme/SettingsStyle.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Settings -->
|
||||
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
|
||||
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
|
||||
|
||||
<x:Double x:Key="SettingsCardContentControlMinWidth">120</x:Double>
|
||||
|
||||
<Style
|
||||
x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Style.Setters>
|
||||
<Setter Property="Margin" Value="1,29,0,5"/>
|
||||
</Style.Setters>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingsContentComboBoxStyle"
|
||||
BasedOn="{StaticResource DefaultComboBoxStyle}"
|
||||
TargetType="ComboBox">
|
||||
<Setter Property="MinWidth" Value="{ThemeResource SettingsCardContentControlMinWidth}"/>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SettingButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}"/>
|
||||
<Setter Property="Padding" Value="16,6,16,6"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="MinWidth" Value="{ThemeResource SettingsCardContentControlMinWidth}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,23 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<TransitionCollection x:Key="ContentThemeTransitions">
|
||||
<ContentThemeTransition/>
|
||||
</TransitionCollection>
|
||||
<TransitionCollection x:Key="EntranceThemeTransitions">
|
||||
<EntranceThemeTransition/>
|
||||
</TransitionCollection>
|
||||
<TransitionCollection x:Key="ListViewLikeThemeTransitions">
|
||||
<AddDeleteThemeTransition/>
|
||||
<ContentThemeTransition/>
|
||||
<ReorderThemeTransition/>
|
||||
<EntranceThemeTransition IsStaggeringEnabled="False"/>
|
||||
</TransitionCollection>
|
||||
<TransitionCollection x:Key="ReorderThemeTransitions">
|
||||
<ReorderThemeTransition/>
|
||||
</TransitionCollection>
|
||||
<TransitionCollection x:Key="RepositionThemeTransitions">
|
||||
<RepositionThemeTransition/>
|
||||
</TransitionCollection>
|
||||
<TransitionCollection x:Key="NavigationThemeTransitions">
|
||||
<NavigationThemeTransition/>
|
||||
</TransitionCollection>
|
||||
</ResourceDictionary>
|
||||
34
src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml
Normal file
34
src/Snap.Hutao/Snap.Hutao/Control/Theme/Uri.xaml
Normal file
@@ -0,0 +1,34 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- DocumentLink -->
|
||||
<x:String x:Key="DocumentLink_BugReport">https://hut.ao/statements/bug-report.html</x:String>
|
||||
<x:String x:Key="DocumentLink_Home">https://hut.ao</x:String>
|
||||
<x:String x:Key="DocumentLink_MhyAccountSwitch">https://hut.ao/features/mhy-account-switch.html</x:String>
|
||||
<x:String x:Key="DocumentLink_Translate">https://translate.hut.ao</x:String>
|
||||
|
||||
<!-- Other -->
|
||||
<x:String x:Key="HolographicHat_GetToken_Release">https://github.com/HolographicHat/GetToken/releases/latest</x:String>
|
||||
<x:String x:Key="Sponsor_Afadian">https://afdian.net/a/DismissedLight</x:String>
|
||||
|
||||
<!-- AvatarCard -->
|
||||
<x:String x:Key="UI_AvatarIcon_Costume_Card">https://static.snapgenshin.com/AvatarCard/UI_AvatarIcon_Costume_Card.png</x:String>
|
||||
|
||||
<!-- Bg -->
|
||||
<x:String x:Key="UI_Icon_Intee_Explore_1">https://static.snapgenshin.com/Bg/UI_Icon_Intee_Explore_1.png</x:String>
|
||||
<x:String x:Key="UI_ImgSign_ItemIcon">https://static.snapgenshin.com/Bg/UI_ImgSign_ItemIcon.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_None">https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png</x:String>
|
||||
<x:String x:Key="UI_MarkQuest_Events_Proce">https://static.snapgenshin.com/Bg/UI_MarkQuest_Events_Proce.png</x:String>
|
||||
<x:String x:Key="UI_MarkTower">https://static.snapgenshin.com/Bg/UI_MarkTower.png</x:String>
|
||||
|
||||
<!-- ItemIcon -->
|
||||
<x:String x:Key="UI_ItemIcon_201">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_201.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_204">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_204.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_210">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_210.png</x:String>
|
||||
<x:String x:Key="UI_ItemIcon_220021">https://static.snapgenshin.com/ItemIcon/UI_ItemIcon_220021.png</x:String>
|
||||
|
||||
<!-- EmotionIcon -->
|
||||
<x:String x:Key="UI_EmotionIcon25">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon25.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon71">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon71.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon250">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon250.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon272">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon272.png</x:String>
|
||||
<x:String x:Key="UI_EmotionIcon293">https://static.snapgenshin.com/EmotionIcon/UI_EmotionIcon293.png</x:String>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,5 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Modify Window title bar color -->
|
||||
<StaticResource x:Key="WindowCaptionBackground" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
<StaticResource x:Key="WindowCaptionBackgroundDisabled" ResourceKey="ControlFillColorTransparentBrush"/>
|
||||
</ResourceDictionary>
|
||||
@@ -15,22 +15,7 @@ internal abstract class ValueConverter<TFrom, TTo> : IValueConverter
|
||||
/// <inheritdoc/>
|
||||
public object? Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
#if DEBUG
|
||||
try
|
||||
{
|
||||
return Convert((TFrom)value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<ILogger<ValueConverter<TFrom, TTo>>>()
|
||||
.LogError(ex, "值转换器异常");
|
||||
|
||||
throw;
|
||||
}
|
||||
#else
|
||||
return Convert((TFrom)value);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 指示此方法为命令的调用方法
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
internal sealed class CommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示此方法为命令的调用方法
|
||||
/// </summary>
|
||||
/// <param name="name">命令名称</param>
|
||||
public CommandAttribute(string name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许并行执行
|
||||
/// </summary>
|
||||
public bool AllowConcurrentExecutions { get; set; }
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 指示此类自动生成构造器
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
internal sealed class ConstructorGeneratedAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示此类自动生成构造器
|
||||
/// </summary>
|
||||
public ConstructorGeneratedAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否调用基类构造函数
|
||||
/// </summary>
|
||||
public bool CallBaseConstructor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在构造函数中插入 HttpClient
|
||||
/// </summary>
|
||||
public bool ResolveHttpClient { get; set; }
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class DependencyPropertyAttribute : Attribute
|
||||
{
|
||||
public DependencyPropertyAttribute(string name, Type type)
|
||||
{
|
||||
}
|
||||
|
||||
public DependencyPropertyAttribute(string name, Type type, object defaultValue)
|
||||
{
|
||||
}
|
||||
|
||||
public DependencyPropertyAttribute(string name, Type type, object defaultValue, string valueChangedCallbackName)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsAttached { get; set; }
|
||||
|
||||
public Type AttachedType { get; set; } = default!;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Core.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 高质量代码
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All, Inherited = false)]
|
||||
[Conditional("DEBUG")]
|
||||
internal sealed class HighQualityAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -30,9 +30,6 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
[0] = TimeSpan.FromSeconds(4),
|
||||
[1] = TimeSpan.FromSeconds(16),
|
||||
[2] = TimeSpan.FromSeconds(64),
|
||||
[3] = TimeSpan.FromSeconds(4),
|
||||
[4] = TimeSpan.FromSeconds(16),
|
||||
[5] = TimeSpan.FromSeconds(64),
|
||||
};
|
||||
|
||||
private readonly ILogger logger;
|
||||
@@ -173,7 +170,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
int retryCount = 0;
|
||||
HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache));
|
||||
while (retryCount < 6)
|
||||
while (retryCount < 3)
|
||||
{
|
||||
using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false))
|
||||
{
|
||||
@@ -191,10 +188,6 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
|
||||
switch (message.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.NotFound:
|
||||
// directly goto https://static.hut.ao
|
||||
retryCount += 3;
|
||||
break;
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
{
|
||||
retryCount++;
|
||||
@@ -208,11 +201,6 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (retryCount == 3)
|
||||
{
|
||||
uri = new UriBuilder(uri) { Host = Web.HutaoEndpoints.StaticHutao }.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
|
||||
/// <summary>
|
||||
/// 指示被标注的类型可注入 HttpClient
|
||||
/// 由源生成器生成注入代码
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
internal sealed class HttpClientAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的特性
|
||||
/// </summary>
|
||||
/// <param name="configuration">配置</param>
|
||||
public HttpClientAttribute(HttpClientConfiguration configuration)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的特性
|
||||
/// </summary>
|
||||
/// <param name="configuration">配置</param>
|
||||
/// <param name="interfaceType">实现的接口类型</param>
|
||||
public HttpClientAttribute(HttpClientConfiguration configuration, Type interfaceType)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Http客户端配置
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal enum HttpClientConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认配置
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// 米游社请求配置
|
||||
/// </summary>
|
||||
XRpc,
|
||||
|
||||
/// <summary>
|
||||
/// 米游社登录请求配置
|
||||
/// </summary>
|
||||
XRpc2,
|
||||
|
||||
/// <summary>
|
||||
/// Hoyolab app
|
||||
/// </summary>
|
||||
XRpc3,
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
|
||||
/// <summary>
|
||||
/// 配置首选Http消息处理器特性
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
internal sealed class PrimaryHttpMessageHandlerAttribute : Attribute
|
||||
{
|
||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.MaxConnectionsPerServer"/>
|
||||
public int MaxConnectionsPerServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="System.Net.Http.HttpClientHandler.UseCookies"/>
|
||||
/// </summary>
|
||||
public bool UseCookies { get; set; }
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 注入方法
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal enum InjectAs
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示应注册为单例对象
|
||||
/// </summary>
|
||||
Singleton,
|
||||
|
||||
/// <summary>
|
||||
/// 指示应注册为短期对象
|
||||
/// </summary>
|
||||
Transient,
|
||||
|
||||
/// <summary>
|
||||
/// 指示应注册为范围对象
|
||||
/// </summary>
|
||||
Scoped,
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Core.DependencyInjection.Annotation;
|
||||
|
||||
/// <summary>
|
||||
/// 指示被标注的类型可注入
|
||||
/// 由源生成器生成注入代码
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class InjectionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指示该类将注入为不带有接口实现的类
|
||||
/// </summary>
|
||||
/// <param name="injectAs">指示注入方法</param>
|
||||
public InjectionAttribute(InjectAs injectAs)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示该类将注入为带有接口实现的类
|
||||
/// </summary>
|
||||
/// <param name="injectAs">指示注入方法</param>
|
||||
/// <param name="interfaceType">实现的接口类型</param>
|
||||
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Service;
|
||||
using System.Globalization;
|
||||
|
||||
@@ -44,7 +44,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(HoyolabOptions.UserAgent);
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd(ApplicationJson);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", HoyolabOptions.XrpcVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", SaltConstants.CNVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd(ApplicationJson);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-aigis", string.Empty);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_id", "bll8iq97cem8");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", HoyolabOptions.XrpcVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", SaltConstants.CNVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "2");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-game_biz", "bbs_cn");
|
||||
@@ -77,7 +77,7 @@ internal static partial class IocHttpClientConfiguration
|
||||
client.Timeout = Timeout.InfiniteTimeSpan;
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd(HoyolabOptions.UserAgentOversea);
|
||||
client.DefaultRequestHeaders.Accept.ParseAdd(ApplicationJson);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", HoyolabOptions.XrpcVersionOversea);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-app_version", SaltConstants.OSVersion);
|
||||
client.DefaultRequestHeaders.Add("x-rpc-client_type", "5");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-language", "zh-cn");
|
||||
client.DefaultRequestHeaders.Add("x-rpc-device_id", HoyolabOptions.DeviceId);
|
||||
|
||||
@@ -14,9 +14,7 @@ namespace Snap.Hutao.Core.ExceptionService;
|
||||
internal sealed partial class ExceptionRecorder
|
||||
{
|
||||
private readonly ILogger<ExceptionRecorder> logger;
|
||||
#if RELEASE
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 记录应用程序异常
|
||||
@@ -29,17 +27,17 @@ internal sealed partial class ExceptionRecorder
|
||||
app.DebugSettings.XamlResourceReferenceFailed += OnXamlResourceReferenceFailed;
|
||||
}
|
||||
|
||||
[SuppressMessage("", "CA2012")]
|
||||
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
#if RELEASE
|
||||
#pragma warning disable VSTHRD002
|
||||
serviceProvider
|
||||
ValueTask<string?> task = serviceProvider
|
||||
.GetRequiredService<Web.Hutao.Log.HomaLogUploadClient>()
|
||||
.UploadLogAsync(serviceProvider, e.Exception)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
#pragma warning restore VSTHRD002
|
||||
#endif
|
||||
.UploadLogAsync(e.Exception);
|
||||
|
||||
if (!task.IsCompleted)
|
||||
{
|
||||
task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user