Compare commits

..

335 Commits
1.7.6 ... 1.8.3

Author SHA1 Message Date
Masterain
b3f8093c09 Update README.md 2023-11-28 02:39:41 -08:00
Lightczx
8e386c1457 1.8.2 hotfix package 2023-11-28 13:32:09 +08:00
DismissedLight
330154c9ec Merge pull request #1136 from DGP-Studio/develop 2023-11-28 13:18:42 +08:00
Lightczx
f97385089a fix #1134 2023-11-28 13:16:16 +08:00
DismissedLight
8982fcd427 1.8.1 package 2023-11-27 19:57:27 +08:00
DismissedLight
aaa2e09dde Merge pull request #1131 from DGP-Studio/develop 2023-11-27 19:34:55 +08:00
Masterain
58fdcc7804 Update MGMT-publish.yml 2023-11-27 03:23:39 -08:00
DismissedLight
ca352a5262 revoke some static resources version 2023-11-27 19:22:57 +08:00
DismissedLight
0f9e34ffb8 Merge pull request #1118 from DGP-Studio/l10n_develop 2023-11-27 19:17:34 +08:00
DismissedLight
76a60e30fc #1123 repeat notification regression 2023-11-27 19:15:55 +08:00
Lightczx
693566812b add new server localization keys 2023-11-27 17:30:14 +08:00
DismissedLight
8fbe2ee831 Merge pull request #1129 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-0fa0185f23 2023-11-27 17:28:50 +08:00
dependabot[bot]
639a86d11b Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn).

- [Release notes](https://github.com/dotnet/roslyn/releases)
- [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md)
- [Commits](https://github.com/dotnet/roslyn/commits)

---
updated-dependencies:
- dependency-name: Microsoft.CodeAnalysis.CSharp
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 07:12:58 +00:00
Masterain
3f21a5d8fc New translations sh.resx (English) 2023-11-26 08:34:00 -08:00
Masterain
8a62327e30 New translations sh.resx (Chinese Traditional) 2023-11-26 08:33:58 -08:00
Masterain
69b9a0c9dc New translations sh.resx (Korean) 2023-11-26 08:33:57 -08:00
Masterain
af56f3ac30 New translations sh.resx (Japanese) 2023-11-26 08:33:56 -08:00
DismissedLight
ffc899efe5 fix #1123 2023-11-26 20:15:22 +08:00
DismissedLight
80b958d9d3 impl #1121 2023-11-26 19:59:02 +08:00
DismissedLight
b60f7c215e minor ui fix 2023-11-26 19:14:49 +08:00
DismissedLight
7128dddb57 remove skill bottom padding 2023-11-26 16:42:00 +08:00
DismissedLight
9960b6f6b1 fix #1126 2023-11-26 15:55:37 +08:00
DismissedLight
7099ca43b6 impl #1127 2023-11-26 15:04:41 +08:00
DismissedLight
c9627e19e7 fix #1124 2023-11-26 14:41:53 +08:00
DismissedLight
6999103aaa refine wiki avatar page ui 2023-11-26 14:35:33 +08:00
Masterain
e54eef3aa7 New translations sh.resx (English) 2023-11-25 08:02:24 -08:00
DismissedLight
f2ef6ff8ec fix #1099 again 2023-11-25 10:10:16 +08:00
Masterain
35ddaaeb35 New translations sh.resx (English) 2023-11-24 07:47:53 -08:00
Masterain
c833655231 New translations sh.resx (Chinese Traditional) 2023-11-24 07:47:52 -08:00
Masterain
8724784803 New translations sh.resx (Korean) 2023-11-24 07:47:51 -08:00
Masterain
d1ceac0fe9 New translations sh.resx (Japanese) 2023-11-24 07:47:49 -08:00
Lightczx
43415ebd0d IPinnable 2023-11-24 17:31:10 +08:00
Lightczx
a8b697e782 fix #1119 2023-11-24 13:41:58 +08:00
Lightczx
602b31c52d restrict combobox size 2023-11-24 11:51:46 +08:00
Masterain
3b43389049 New translations sh.resx (Chinese Traditional) 2023-11-23 07:49:51 -08:00
Masterain
3a1fc839eb New translations sh.resx (Japanese) 2023-11-23 07:49:50 -08:00
DismissedLight
e0f967341e TCG decrypt [skip ci] 2023-11-23 23:34:31 +08:00
Lightczx
74ac738236 bump static resource version 2023-11-23 13:08:32 +08:00
Masterain
0189c4824b New translations sh.resx (English) 2023-11-22 07:43:58 -08:00
Masterain
1a43841833 New translations sh.resx (Chinese Traditional) 2023-11-22 07:43:56 -08:00
Masterain
29ad939498 New translations sh.resx (Korean) 2023-11-22 07:43:55 -08:00
Masterain
60c8e948e8 New translations sh.resx (Japanese) 2023-11-22 07:43:54 -08:00
DismissedLight
a17f9ca543 optimize discord activity 2023-11-22 22:28:06 +08:00
Lightczx
ee86f12168 impl #1082 2023-11-22 16:59:01 +08:00
DismissedLight
9fb79a9fbd Merge pull request #1117 from qhy040404/develop 2023-11-22 13:51:56 +08:00
Lightczx
467eb13c87 file nesting 2023-11-22 13:48:05 +08:00
Lightczx
40b055d310 lock on get winrt obj disposed 2023-11-22 13:43:35 +08:00
qhy040404
126d19e96a Apply suggestions 2023-11-22 13:39:07 +08:00
Lightczx
485ac1e682 trim unused xml 2023-11-22 13:28:40 +08:00
qhy040404
1117e322a6 Support the coexistence of Snap Hutao and Snap Hutao Dev 2023-11-22 13:10:05 +08:00
Masterain
85b40b71e5 Update PublishDistribution.yml 2023-11-21 17:33:53 -08:00
Masterain
66bbf4335a Update PublishDistribution.yml 2023-11-21 17:30:52 -08:00
DismissedLight
cb20bd0df4 Merge pull request #1116 from qhy040404/develop 2023-11-22 09:05:47 +08:00
qhy040404
cb9c9a0af2 Sync tab when navigate from GachaLog's HutaoCloudView to SpiralAbyssRecordPage 2023-11-21 22:14:27 +08:00
Lightczx
28ea71cf77 Add CollectionsMarshal Test 2023-11-21 16:17:53 +08:00
Lightczx
94744c4bc1 1.8.0 package 2023-11-21 15:03:43 +08:00
DismissedLight
58af255485 Merge pull request #1114 from DGP-Studio/develop 2023-11-21 14:10:59 +08:00
Masterain
c45e6560b8 New Crowdin updates (#1091)
Co-authored-by: DismissedLight <1686188646@qq.com>
2023-11-21 13:58:15 +08:00
Lightczx
29d7d36b66 Frozen Collections 2023-11-21 13:53:32 +08:00
Lightczx
2bbf6f192e fix conditional ci 2023-11-21 10:57:23 +08:00
Lightczx
cc8565428b replace hutao endpoints 2023-11-21 10:47:33 +08:00
DismissedLight
d84e0bf199 fix typo 2023-11-20 21:56:53 +08:00
DismissedLight
9d8c981f5a add profile picture basic support 2023-11-20 21:34:37 +08:00
Lightczx
1f412b289a move localization annotations 2023-11-20 17:25:03 +08:00
Lightczx
07b3e98ac0 add more specifically http message 2023-11-20 16:58:48 +08:00
DismissedLight
8a9f4ced34 Merge pull request #1111 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-dfb7e59321 2023-11-20 15:23:19 +08:00
dependabot[bot]
0a0aa3e404 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime).

- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.DependencyInjection
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 07:19:21 +00:00
Lightczx
84861c5e6b update nuget package version 2023-11-20 14:44:57 +08:00
DismissedLight
a23e6f55d4 add basic discord support 2023-11-19 20:51:58 +08:00
DismissedLight
c1cf5aa499 prevent binding failure when achievement page disposed 2023-11-19 16:59:26 +08:00
DismissedLight
91b6e6793c simplify the startup statement 2023-11-19 16:48:43 +08:00
DismissedLight
ac1ef44801 fix startup user role change caused dead lock 2023-11-19 16:16:10 +08:00
DismissedLight
e7b23c8a5a minor bug fixes 2023-11-19 13:16:22 +08:00
DismissedLight
68a3851595 refine #1096 2023-11-18 22:47:09 +08:00
DismissedLight
7861ebf998 #impl 1096 2023-11-18 22:41:25 +08:00
DismissedLight
0cc75ab245 fix #1106 2023-11-17 22:29:17 +08:00
Lightczx
ef09c87835 fix service register type for geetest 2023-11-17 13:06:49 +08:00
Lightczx
b13ec8c12a win32 dialog 2023-11-16 16:58:41 +08:00
Lightczx
29e14111f0 fix azure pipeline 2023-11-16 14:40:52 +08:00
Lightczx
0dd66288cd fix azure pipeline 2023-11-16 14:30:59 +08:00
DismissedLight
bb835ee20e Merge pull request #1103 from DGP-Studio/dotnet8 2023-11-16 14:24:57 +08:00
Lightczx
a1037e1827 fix #1099 2023-11-16 14:13:32 +08:00
Lightczx
ba6ee9bb85 replace format extension 2023-11-16 13:15:37 +08:00
Lightczx
f1c50dc6c3 UnsafeAccessor 2023-11-16 10:47:07 +08:00
Lightczx
8e841b1295 remove using 2023-11-16 09:24:35 +08:00
DismissedLight
ea2000b03d Update DataSignAlgorithm.cs 2023-11-16 00:08:43 +08:00
DismissedLight
d186dd6f98 DynamicSecret -> DataSign 2023-11-16 00:08:01 +08:00
DismissedLight
5c46c92a1a rename constants 2023-11-15 22:36:57 +08:00
DismissedLight
98f2dd13d1 bulk refactor 2023-11-15 22:31:56 +08:00
Lightczx
3e0493be31 collection experssion 2 2023-11-15 17:26:18 +08:00
Lightczx
7025074170 fix collection style 2023-11-15 16:36:15 +08:00
Lightczx
0eade5e81a collection experssion 2023-11-15 16:19:43 +08:00
Lightczx
163d076ed5 Revert "fix shadow crash on announcement page"
This reverts commit cf5b377532.
2023-11-15 16:08:10 +08:00
Lightczx
fa5aac3366 Revert "remove AttachedCardShadow to fix startup crash"
This reverts commit afbc91abe8.
2023-11-15 16:07:57 +08:00
Lightczx
f98a40d080 fix NRE in DbCurrent and change UnsafeDateTimeOffset ns 2023-11-15 13:16:38 +08:00
Lightczx
cf5b377532 fix shadow crash on announcement page 2023-11-15 12:59:32 +08:00
Lightczx
afbc91abe8 remove AttachedCardShadow to fix startup crash 2023-11-15 12:53:20 +08:00
Lightczx
be572fa327 change csproj to net8 2023-11-15 12:52:52 +08:00
Lightczx
fe594864f2 use CoCreateInstance to activate ShellLinkW 2023-11-14 09:24:37 +08:00
DismissedLight
d8b6315e7e capture dispatched exceptions 2023-11-13 23:13:25 +08:00
Lightczx
d4e9c2aa9c localenames 2023-11-13 15:37:06 +08:00
DismissedLight
800c948c38 Merge pull request #1094 from DGP-Studio/dependabot/nuget/src/Snap.Hutao/develop/packages-c2c108d172 2023-11-13 15:35:59 +08:00
dependabot[bot]
32589bc994 Bump the packages group in /src/Snap.Hutao with 1 update
Bumps the packages group in /src/Snap.Hutao with 1 update: [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest).

- [Release notes](https://github.com/microsoft/vstest/releases)
- [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md)
- [Commits](https://github.com/microsoft/vstest/compare/v17.7.2...v17.8.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: packages
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 07:08:36 +00:00
DismissedLight
afc91b1a29 remove unused using 2023-11-12 13:06:41 +08:00
DismissedLight
40db69825f Merge pull request #1090 from Tangweirui2021/main 2023-11-12 12:49:41 +08:00
DismissedLight
407dd7bac8 code style 2023-11-12 12:48:46 +08:00
Daniel
4708cd5629 fix user log in/register/unregister/reset password info bar localization 2023-11-11 18:42:14 +08:00
DismissedLight
2b93d31788 fix #1087 and improve some null checks 2023-11-11 12:55:47 +08:00
Lightczx
39831b0ae1 Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2023-11-10 16:08:54 +08:00
Lightczx
51c9936018 1.7.17 package 2023-11-10 16:08:35 +08:00
Masterain
3466a98ffb Create MGMT-publish.yml 2023-11-09 23:48:29 -08:00
DismissedLight
a064cc10ee Merge pull request #1085 from DGP-Studio/develop 2023-11-10 15:41:33 +08:00
Masterain
3479a19164 New Crowdin updates (#1078)
Co-authored-by: DismissedLight <1686188646@qq.com>
2023-11-10 15:39:00 +08:00
Lightczx
d4549581c1 fix exception capture 2023-11-10 15:33:11 +08:00
Lightczx
f97bc344d0 fix announcement time incorrectness 2023-11-10 14:20:44 +08:00
Lightczx
26d23fec7f impl #830 in previous commit 2023-11-10 11:39:53 +08:00
Lightczx
7442f7f1ec support UIGF v2.4-preview 2023-11-10 11:37:45 +08:00
DismissedLight
3eb2556393 update gacha info endpoints 2023-11-09 23:39:51 +08:00
DismissedLight
cfff6f39fc adjust server timezone 2023-11-09 23:15:08 +08:00
Lightczx
3005031b39 add basic timezone support for gachaitem 2023-11-09 17:18:56 +08:00
Lightczx
71363f4d8d fix #1081 2023-11-09 15:23:51 +08:00
Lightczx
e833578334 rename jsbridge 2023-11-09 11:51:56 +08:00
Lightczx
d529b3cea6 fix #1079 2023-11-09 11:38:30 +08:00
Lightczx
1c0ce62885 fix gacha item corner radius 2023-11-08 13:34:55 +08:00
DismissedLight
acdf2baa9a improve webviewer & hotkey 2023-11-07 21:02:25 +08:00
DismissedLight
ec007d5d81 add fp to jsbridge 2023-11-07 19:08:48 +08:00
Lightczx
5e734ac689 impl #961 2023-11-07 15:37:53 +08:00
DismissedLight
b0ecd048b6 1.7.16 package 2023-11-06 21:41:04 +08:00
DismissedLight
91010d0d8b Merge pull request #1076 from DGP-Studio/develop 2023-11-06 20:57:23 +08:00
Masterain
fc771eb90a New Crowdin updates (#1063) 2023-11-06 20:56:40 +08:00
DismissedLight
80f2fed722 Merge pull request #1075 from DGP-Studio/develop 2023-11-06 20:56:15 +08:00
DismissedLight
bdb406c451 add copy hint for #1074 2023-11-06 20:53:29 +08:00
DismissedLight
5bc957c6a5 fix spiral abyss crash when using 4.2 metadata 2023-11-06 20:32:29 +08:00
Lightczx
416c6f15a6 partial #1074 2023-11-06 17:07:35 +08:00
Lightczx
9eed633e05 DefaultItemCollectionTransitionProvider 2023-11-06 15:52:50 +08:00
Lightczx
7e30173990 update to 4.2 metadata 2023-11-06 14:57:57 +08:00
Lightczx
2200e2e58e fonticon resources 2023-11-06 14:36:36 +08:00
Lightczx
b8886c5cd3 fix #1072 2023-11-06 13:46:59 +08:00
DismissedLight
43007d8fb4 Merge pull request #1073 from qhy040404/develop 2023-11-06 12:40:53 +08:00
Lightczx
88684bff00 code style 2023-11-06 12:40:19 +08:00
qhy040404
0c7ce7a72f Add files 2023-11-06 12:32:22 +08:00
qhy040404
075d92f754 Set IsEnabled by a new property instead of setting it separately for each SettingsCard 2023-11-06 12:18:05 +08:00
Lightczx
a0cba171cc update feat template 2023-11-06 11:43:57 +08:00
Lightczx
f41185310b adjust wish typename 2023-11-06 11:41:33 +08:00
Lightczx
2a4c93d241 impl #778 all 2023-11-06 11:17:16 +08:00
DismissedLight
c0980fabe8 impl #1071 2023-11-05 16:03:10 +08:00
DismissedLight
f2ba316059 recycle fingerprint 2023-11-04 17:47:45 +08:00
DismissedLight
e4e9dd91f1 impl #1062 2023-11-04 17:21:31 +08:00
DismissedLight
749ef0e138 introducing game service facade 2023-11-04 16:53:08 +08:00
DismissedLight
24086ee4d0 optimize UniformStaggeredColumnLayout 2023-11-03 23:32:52 +08:00
DismissedLight
aeb6962ae4 impl #1015 2023-11-03 22:06:51 +08:00
Lightczx
87e5ede91f impl #1068 2023-11-03 16:20:11 +08:00
Lightczx
91de6d170e add fingerprint fetch & fix #1060 2023-11-03 11:52:52 +08:00
Lightczx
3057673cdb fix #1069 2023-11-03 10:11:47 +08:00
Lightczx
c3ace405ac fix pushpage 2023-11-03 09:26:44 +08:00
DismissedLight
0b48581e65 Merge pull request #1065 from qhy040404/main 2023-11-02 21:16:40 +08:00
DismissedLight
4ab129e4a2 code style 2023-11-02 21:14:39 +08:00
qhy040404
13ad36f5b4 Added a master switch for launchOptions 2023-11-02 18:43:07 +08:00
Lightczx
f026321aa8 1.7.15 package 2023-11-02 15:27:40 +08:00
DismissedLight
d1dfdf107b Merge pull request #1064 from DGP-Studio/develop 2023-11-02 15:10:51 +08:00
Lightczx
59f8895675 MarqueeText 2023-11-02 15:05:06 +08:00
Lightczx
4cb3d5f03f launch scheme renewed 2023-11-02 14:23:34 +08:00
Lightczx
067c7d7c4d fix ci 2023-11-02 12:45:11 +08:00
Lightczx
1cc072ba28 EmailSmtpAddress 2023-11-02 11:35:31 +08:00
Lightczx
0e7afa8efb clear username & password after cancel registration 2023-11-02 11:30:00 +08:00
Lightczx
b753728b7e verify code request set token 2023-11-02 11:12:26 +08:00
Lightczx
df019da891 complete cancel registration 2023-11-02 10:42:55 +08:00
Lightczx
c6435f30eb add verify for cancel registration 2023-11-02 10:26:27 +08:00
Lightczx
3ac0be4220 fix unregister passport 2023-11-02 10:07:20 +08:00
Lightczx
24f6a33256 clear username & password after logout 2023-11-02 09:48:22 +08:00
Lightczx
dc9278eb4f fix verifycode crash 2023-11-02 09:42:08 +08:00
Lightczx
4b2c82db62 fix xaml parsing failed 2023-11-02 09:06:56 +08:00
Lightczx
70f30edd7c xaml style rework 2023-11-01 17:03:00 +08:00
Lightczx
c8e8213df6 code style 2023-11-01 15:56:22 +08:00
Lightczx
7cad996902 process cmdline #1061 2023-11-01 15:28:44 +08:00
Lightczx
eec47b72c7 fix #1061 2023-11-01 15:26:36 +08:00
Lightczx
5943b1a1fb impl #886 2023-11-01 13:46:50 +08:00
Lightczx
9f9a5670bc fix dailynote webhook error 2023-11-01 10:45:47 +08:00
Lightczx
10ba927136 fix #1059 2023-10-31 16:45:24 +08:00
Lightczx
07d42cedd1 page style 2023-10-31 15:19:25 +08:00
Lightczx
07c52019f4 1.7.14 hotfix package 2023-10-31 11:38:51 +08:00
DismissedLight
77cb2fc603 Merge pull request #1056 from DGP-Studio/develop 2023-10-31 11:11:03 +08:00
Masterain
b5c16e2dae New Crowdin updates (#1053) 2023-10-31 11:10:06 +08:00
DismissedLight
29c954b032 fingerprint 2023-10-30 22:03:33 +08:00
DismissedLight
8df5d5d6eb fix #1052 & user account add crash 2023-10-30 19:42:36 +08:00
Lightczx
827d944987 1.7.13 package 2023-10-30 15:38:00 +08:00
DismissedLight
4c47f3c08b Merge pull request #1050 from DGP-Studio/develop 2023-10-30 15:16:10 +08:00
DismissedLight
6540cc4577 Merge pull request #1045 from DGP-Studio/l10n_develop 2023-10-30 15:14:10 +08:00
Lightczx
df22d30a96 ui/ux 2023-10-30 15:12:30 +08:00
Lightczx
5ea9dd533f remove hutao user changed message 2023-10-30 09:14:21 +08:00
DismissedLight
744c1079e1 fix #903 2023-10-29 22:56:10 +08:00
Masterain
1539863415 New translations sh.resx (English) 2023-10-29 07:32:38 -07:00
Masterain
3527e43118 New translations sh.resx (Chinese Traditional) 2023-10-29 07:32:37 -07:00
Masterain
8388def548 New translations sh.resx (Korean) 2023-10-29 07:32:36 -07:00
Masterain
d0bfbfa505 New translations sh.resx (Japanese) 2023-10-29 07:32:35 -07:00
DismissedLight
fa640d27f0 implement #911 2023-10-29 22:08:51 +08:00
DismissedLight
389c1417f7 built in resx generator 2023-10-28 15:00:43 +08:00
Masterain
6dcacb1bf4 New translations sh.resx (Japanese) 2023-10-27 10:45:51 -07:00
Lightczx
4ffd09cce8 roslyn generated resx 2023-10-27 15:50:32 +08:00
Lightczx
0dcbac3ee1 fix #431 input crash 2023-10-27 09:23:05 +08:00
Masterain
b1ea5332fc Merge pull request #1037 from DGP-Studio/l10n_develop
New Crowdin updates
2023-10-26 10:29:22 -07:00
Masterain
b34dab0f99 New translations sh.resx (English) 2023-10-26 10:28:57 -07:00
Masterain
791e517e39 New translations sh.resx (Chinese Traditional) 2023-10-26 10:28:55 -07:00
Masterain
4408e3994e New translations sh.resx (Korean) 2023-10-26 10:28:54 -07:00
Masterain
32cbbefe1a New translations sh.resx (Japanese) 2023-10-26 10:28:52 -07:00
DismissedLight
d754c0d117 ignore designer file 2 2023-10-26 23:04:21 +08:00
DismissedLight
4c75295f2c ignore designer file 1 2023-10-26 23:04:01 +08:00
DismissedLight
34e5312d75 impl #1021 2023-10-26 22:58:40 +08:00
Masterain
465d6b631e Update issue templates 2023-10-26 03:37:04 -07:00
Masterain
0d2d1b8115 New translations sh.resx (English) 2023-10-26 02:34:48 -07:00
Masterain
981949651e New translations sh.resx (English) 2023-10-26 02:28:47 -07:00
Masterain
874dac1119 New translations sh.resx (Chinese Traditional) 2023-10-26 02:28:46 -07:00
Masterain
3f0694b28e New translations sh.resx (Korean) 2023-10-26 02:28:44 -07:00
Masterain
6b166b6aed New translations sh.resx (Japanese) 2023-10-26 02:28:43 -07:00
Lightczx
b351231c84 typo fix 2023-10-26 17:27:55 +08:00
Lightczx
0603b24466 implement #431 2023-10-26 15:31:55 +08:00
Lightczx
f97ad4eac0 fix IsGameRunning 2023-10-26 14:12:45 +08:00
DismissedLight
28fc4558be support server l10n 2023-10-25 23:26:46 +08:00
Masterain
ea3391b112 New translations sh.resx (English) 2023-10-25 02:28:14 -07:00
DismissedLight
ec95e42d7d fix #1041 2023-10-23 19:04:00 +08:00
Masterain
e9f12aeb09 Update .github configurations 2023-10-21 19:52:34 -07:00
Masterain
04850dd136 New translations sh.resx (Japanese) 2023-10-21 11:07:53 -07:00
Masterain
4782d61ed0 New translations sh.resx (English) 2023-10-20 06:16:12 -07:00
Masterain
28ade90926 New translations sh.resx (Chinese Traditional) 2023-10-20 06:16:11 -07:00
Masterain
dde97b6489 New translations sh.resx (Korean) 2023-10-20 06:16:10 -07:00
Masterain
44fe729e1a New translations sh.resx (Japanese) 2023-10-20 06:16:09 -07:00
Lightczx
2a1d814cc5 fix #899 2023-10-20 11:44:42 +08:00
Lightczx
c1ee37bd8f fix #1023 2023-10-20 10:59:30 +08:00
Lightczx
65b81f0ad8 fix #1035 2023-10-20 09:07:40 +08:00
DismissedLight
91fea88623 launch page redo 2023-10-19 22:32:01 +08:00
DismissedLight
026c68229a fix #925 2023-10-19 20:46:29 +08:00
DismissedLight
91b2db886f 1.7.11 hotfix package 2023-10-18 19:53:00 +08:00
DismissedLight
101d316525 Merge pull request #1032 from DGP-Studio/develop 2023-10-18 19:38:01 +08:00
DismissedLight
59d62f931d fix launch args 2023-10-18 19:35:59 +08:00
Lightczx
f0bb19bc07 fix #1028 2023-10-18 17:21:45 +08:00
Masterain
c0b05e2c2f Update FUNDING.yml 2023-10-17 17:15:55 -07:00
Masterain
fc02f833a0 Update FUNDING.yml 2023-10-17 15:36:04 -07:00
DismissedLight
7b8ebd86b1 1.7.10 package 2023-10-17 22:12:58 +08:00
DismissedLight
47b24286b1 Merge pull request #1024 from DGP-Studio/develop 2023-10-17 20:31:47 +08:00
Masterain
0e3e3b9e4a New Crowdin updates (#1019) 2023-10-17 20:31:15 +08:00
DismissedLight
85d7b22e11 auto select existed account when detecting 2023-10-17 20:29:14 +08:00
Lightczx
7caeb17788 use reference in picker factory 2023-10-17 14:51:41 +08:00
Lightczx
b11b90e9f1 re-impl #1020 2023-10-16 11:03:41 +08:00
DismissedLight
58643a60b5 fix bugs 2023-10-15 16:14:22 +08:00
DismissedLight
a29b487c26 impl #1020 2023-10-15 16:14:13 +08:00
DismissedLight
1bd6023e0a fix issues 2023-10-15 12:30:44 +08:00
DismissedLight
579173d464 webview lifetime 2023-10-14 19:29:28 +08:00
DismissedLight
9ed53e8c34 fix spiralabyss damage rank empty crash 2023-10-14 17:26:06 +08:00
DismissedLight
830556a043 fix ICoreWebView2_13 not supported 2023-10-14 00:12:14 +08:00
DismissedLight
7ba27e184f ignore invalid launch schemes 2023-10-13 23:17:08 +08:00
DismissedLight
9aa6a2b57b fix invalid geetest url crash 2023-10-13 22:53:32 +08:00
DismissedLight
5773902f4a fix #1001 2023-10-13 22:36:51 +08:00
DismissedLight
06c5bcad3e fix #1013 #1012 #1011 2023-10-13 21:42:36 +08:00
Lightczx
c0165c57fd support Send on sync context 2023-10-13 15:27:46 +08:00
DismissedLight
4e57520115 Update AvatarIds.cs 2023-10-12 22:55:35 +08:00
Masterain
17c3480dae New Crowdin updates (#1006) 2023-10-12 22:10:54 +08:00
DismissedLight
7d5faadbb5 1.7.9 hotfix package 2023-10-12 22:03:24 +08:00
DismissedLight
b7df968ea7 fix #1008 2023-10-12 21:33:35 +08:00
DismissedLight
5f6cc46774 Merge pull request #1009 from DGP-Studio/develop 2023-10-12 21:04:39 +08:00
Lightczx
a34b5a5101 style 2023-10-12 16:58:29 +08:00
Lightczx
457e3c4af2 LoadImageSurfaceAsync fix 2023-10-12 16:45:59 +08:00
Lightczx
26143079b3 app resource moved 2023-10-12 15:59:42 +08:00
DismissedLight
20ad9aec60 fix 1.7.7 log upload issue 2023-10-11 22:28:10 +08:00
DismissedLight
61dd098d95 fix #1003 2023-10-11 22:12:07 +08:00
DismissedLight
e7233fbf2a fix #1007 2023-10-11 20:50:21 +08:00
DismissedLight
8de526274a fix #1005 2023-10-10 22:28:53 +08:00
DismissedLight
5b1fb6e1dd move resources 2023-10-10 22:27:53 +08:00
DismissedLight
1584fd1428 fix mutiple display window init size incorrectness 2023-10-10 20:23:39 +08:00
Masterain
65179a340f Update README.md 2023-10-09 04:02:38 -07:00
DismissedLight
9f984b4a9b 1.7.7 package 2023-10-08 22:59:36 +08:00
DismissedLight
8a36bfb62c Merge pull request #978 from DGP-Studio/l10n_develop 2023-10-08 22:34:22 +08:00
DismissedLight
c9c8f6a5cf Merge pull request #998 from DGP-Studio/develop 2023-10-08 22:28:58 +08:00
DismissedLight
f9e1ef7ab5 fix #990 2023-10-08 22:26:40 +08:00
DismissedLight
be06a91e0f code style 2023-10-08 22:08:17 +08:00
DismissedLight
174757f94d adjust nullability annotation 2023-10-08 21:51:24 +08:00
Lightczx
1cd3ec0cdb experiment 2023-10-08 17:30:35 +08:00
Lightczx
7b17bb5a01 reset window size 2023-10-08 14:15:27 +08:00
Masterain
c3bab3c677 New translations sh.resx (Japanese) 2023-10-07 15:35:50 -07:00
DismissedLight
17f1c873ae shadow complete 2023-10-07 23:49:23 +08:00
Lightczx
313a17b7eb code style 2023-10-07 11:25:06 +08:00
Lightczx
9ad321e10d fix ci build 2023-10-07 11:15:49 +08:00
Masterain
f18dfc0baf New translations sh.resx (Japanese) 2023-10-06 15:28:34 -07:00
DismissedLight
16946c58fe fix minor request bug 2023-10-06 21:48:10 +08:00
DismissedLight
c7b931956c import reqrest 2023-10-06 21:33:09 +08:00
Masterain
6c0c5b4669 New translations sh.resx (English) 2023-10-05 15:28:01 -07:00
Masterain
0342805293 New translations sh.resx (Chinese Traditional) 2023-10-05 15:28:00 -07:00
Masterain
e0b1896336 New translations sh.resx (Korean) 2023-10-05 15:27:59 -07:00
Masterain
6d1958af4c New translations sh.resx (Japanese) 2023-10-05 15:27:58 -07:00
DismissedLight
42ab2c3000 Merge pull request #989 from Xhichn/develop 2023-10-05 12:29:01 +08:00
Xhichn
d0fb00dccd Impl GetInfoAsync 2023-10-05 10:39:00 +08:00
DismissedLight
5bc484db78 signIn improvement 2023-10-05 10:02:25 +08:00
DismissedLight
c665834f77 Merge pull request #988 from DGP-Studio/main 2023-10-05 09:41:00 +08:00
DismissedLight
81d73568a7 Merge pull request #965 from Xhichn/DailyReward 2023-10-05 09:37:54 +08:00
DismissedLight
c940ec7006 dailynote & spiralabyss adjust 2023-10-05 09:32:17 +08:00
Masterain
71e5e6f190 New translations sh.resx (English) 2023-10-04 15:21:20 -07:00
Masterain
3983eaec0f New translations sh.resx (Chinese Traditional) 2023-10-04 15:21:19 -07:00
Masterain
9392911547 New translations sh.resx (Korean) 2023-10-04 15:21:18 -07:00
Masterain
f3d1fda053 New translations sh.resx (Japanese) 2023-10-04 15:21:16 -07:00
DismissedLight
14c3a14c2d impl #902 2023-10-04 23:15:32 +08:00
DismissedLight
31568ffba9 correct naming 2023-10-04 22:20:44 +08:00
DismissedLight
3d3d004441 rename signin interface 2023-10-04 22:20:06 +08:00
Masterain
a40e5f2a80 Update Qodana configuration 2023-10-03 14:47:34 -07:00
Masterain
186da74704 Merge pull request #985 from Masterain98/main
Create CONTRIBUTING.md
2023-10-03 03:53:59 -07:00
Masterain
c532799eb1 Update Qodana configuration 2023-10-03 03:34:54 -07:00
Masterain
595e19a1df Update CONTRIBUTING.md 2023-10-03 03:24:59 -07:00
Masterain
c376c4e0e8 Create CONTRIBUTING.md
resolve #909
2023-10-03 02:46:18 -07:00
DismissedLight
07f0c37b2c attempt to fix desktop icon creation 2023-10-03 17:39:30 +08:00
DismissedLight
ecdaeccc42 view style 2023-10-03 15:14:44 +08:00
Masterain
64bbcdfc80 New translations sh.resx (English) 2023-10-02 20:29:13 -07:00
Masterain
59fccf0cba New translations sh.resx (Chinese Traditional) 2023-10-02 20:29:12 -07:00
Masterain
f860211632 New translations sh.resx (Korean) 2023-10-02 20:29:11 -07:00
Masterain
a15a8cfe2d New translations sh.resx (Japanese) 2023-10-02 20:29:10 -07:00
DismissedLight
38bed7318f fix #982 2023-10-02 15:42:56 +08:00
DismissedLight
e513b8b00b add string 2023-10-02 13:24:51 +08:00
DismissedLight
1e711172af fix #980 2023-10-02 13:23:45 +08:00
Masterain
8beb47fd2c New translations sh.resx (English) 2023-10-01 20:29:31 -07:00
DismissedLight
41ab80511e separate win32 to increate analyzer response speed 2023-10-01 16:25:39 +08:00
DismissedLight
318b6190d2 fix game convert config not save 2023-10-01 11:29:23 +08:00
Masterain
6e3dbd41c7 New translations sh.resx (English) 2023-09-30 20:10:41 -07:00
Masterain
911f667e98 New translations sh.resx (Chinese Traditional) 2023-09-30 20:10:40 -07:00
Masterain
db6537bbe4 New translations sh.resx (Korean) 2023-09-30 20:10:39 -07:00
Masterain
6b640b5e15 New translations sh.resx (Japanese) 2023-09-30 20:10:38 -07:00
DismissedLight
bfe9837b36 fix several issues 2023-10-01 11:08:24 +08:00
Xhichn
b8255e77a3 Typo fix 2023-09-28 11:42:48 +08:00
DismissedLight
500888c2d1 launch scheme fix 2023-09-27 23:54:05 +08:00
Xhichn
5141d4a567 HoYolab Daily Reward Support 2023-09-27 18:51:41 +08:00
Lightczx
a86f5e1b59 add more compatonly scheme 2023-09-27 17:20:27 +08:00
Lightczx
390b89fc82 fix multiple channel/subchannel crash & setting clear invalid cache path crash 2023-09-27 17:12:32 +08:00
DismissedLight
6d6d97c59a fix mihoyo login no login_ticket crash 2023-09-26 23:40:30 +08:00
DismissedLight
df23fe54d6 fix manual input gacha url no auth_appid crash 2023-09-26 23:31:55 +08:00
DismissedLight
8caef9af0c MultiChannel: [ChannelType:Bili] [SubChannel:Official] [IsOversea: False] 2023-09-26 23:28:58 +08:00
DismissedLight
aac0e268b0 fix hoyoverse login crash 2023-09-26 23:23:02 +08:00
DismissedLight
bafee4ba18 fix detect game account crash 2023-09-26 22:26:01 +08:00
DismissedLight
4a1dccd7ac update intrinsics 2023-09-26 21:49:18 +08:00
Lightczx
1f8e73f255 disable concurrent launch game 2023-09-26 14:16:03 +08:00
591 changed files with 18107 additions and 16376 deletions

4
.github/FUNDING.yml vendored
View File

@@ -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

View File

@@ -1,7 +1,7 @@
name: 问题反馈
description: 告诉我们你的问题
description: 通过这个议题向开发团队反馈你发现的程序中的问题
title: "[Bug]: 在这里填写一个合适的标题"
labels: ["BUG"]
labels: ["BUG", "priority:none"]
body:
- type: markdown
attributes:
@@ -14,7 +14,7 @@ body:
attributes:
label: 检查清单
description: |-
请确保你已完整执行检查清单,否则你的 Issue 可能会被忽略
请确保你已完整执行检查清单,否则你的议题可能会被忽略
options:
- label: 我已阅读 Snap Hutao 文档中的[常见问题](https://hut.ao/advanced/FAQ.html)和[常见程序异常](https://hut.ao/advanced/exceptions.html),我的问题没有在文档中得到解答
required: true
@@ -51,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

View 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

View File

@@ -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

View File

@@ -1,7 +1,7 @@
name: BUG Report [English Form]
description: Tell us what issue you get
title: "[ENG][Bug]: Place your Issue Title Here"
labels: ["BUG"]
labels: ["BUG", "priority:none"]
body:
- type: markdown
attributes:
@@ -50,7 +50,8 @@ body:
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 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

View 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

View 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

61
.github/ISSUE_TEMPLATE/MGMT-publish.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: Publish Process
description: FOR ADMIN USE ONLY. WILL CAUSE A BAN IF NO PERMISSION.
title: "[Publish]: Version 1.9.98"
labels: ["Publish"]
assignees:
- Lightczx
body:
- type: textarea
id: main-body
attributes:
label: Publish Process
value: |
## 创建版本
- [ ] 同步一次 [Crowdin](https://crowdin.com/project/snap-hutao) 翻译
- [ ] 发布 RC 版本Optional
- [ ] 合并入主分支
- [ ] 整理更新内容,等待翻译
- [ ] 打包
- [ ] 提交微软商店
- [ ] 包含更新日志
- [ ] 在 [Snap.Hutao.Docs@next-patch](https://github.com/DGP-Studio/Snap.Hutao.Docs/tree/next-patch) 分支更新文档并直接开 PR
- [ ] 更新日志
- [ ] 功能文档更新
## 发布版本 [半自动]
- [ ] 在 GitHub 个人设置中更新 [Publish-Automate Beta PAT](https://github.com/settings/tokens?type=beta),有效期需小于预计发版需要天数
- [ ] 将更新的 PAT 更新至 Publish-Automate 库的 [Actions Secrets](https://github.com/DGP-Studio/Publish-Automate/settings/secrets/actions) 中
***
- [ ] 运行 [Auto Publish Action](https://github.com/DGP-Studio/Publish-Automate/actions/workflows/auto-publish.yml)
- [ ] 在 https://store.rg-adguard.net/ 下载新版本安装包
- [ ] Store URL: https://apps.microsoft.com/store/detail/snap-hutao/9PH4NXJ2JN52
- [ ] 命名格式为 `Snap.Hutao x.x.x.msix`
- [ ] Merge 文档 PR
- [ ] 发布 Release
- [ ] 更新日志格式(以 1.6.2 版本为例)
```jsx
## Update log
https://hut.ao/en/statements/update-log.html#_1-6-2
## 更新日志
[此处从文档复制]
## What's Changed
**Full Changelog**: https://github.com/DGP-Studio/Snap.Hutao/compare/1.6.0...1.6.2
```
- [ ] 通知用户
- type: checkboxes
id: checklist-final
attributes:
label: Final Check
description: Understand what you are doing
options:
- label: I understand that I will get banned from repository if I don't have permission to use this template
required: true

View File

@@ -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

View File

@@ -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

View File

@@ -12,40 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout Repo
uses: actions/checkout@v3
# Download Assets
- name: Download Release
timeout-minutes: 5
uses: robinraju/release-downloader@v1.7
with:
repository: "DGP-Studio/Snap.Hutao"
latest: true
fileName: "*.msix"
out-file-path: ./release-download
# Upload to Drive
- name: Upload Drive
timeout-minutes: 15
env:
RCCONF: ${{ secrets.RCCONF }}
run: |
curl https://rclone.org/install.sh | sudo bash
mkdir -p ~/.config/rclone/
cat << EOF > ~/.config/rclone/rclone.conf
$RCCONF
EOF
rclone copy ./release-download/* dgpODCN:/releases/
# Purge Patch System Cache
- name: Purge Patch
env:
PATCH_HOSTS: ${{ secrets.PATCH_HOSTS }}
PURGE_TOKEN: ${{ secrets.PURGE_TOKEN }}
PURGE_URL: ${{ secrets.PURGE_URL }}
run: |
sudo echo "$PATCH_HOSTS" | sudo tee -a /etc/hosts
curl --header "Authorization: token $PURGE_TOKEN" $PURGE_URL
curl -X PATCH $PURGE_URL

View File

@@ -1,19 +0,0 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2023.2
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}

6
.gitignore vendored
View File

@@ -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
View 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

View File

@@ -1,8 +1,9 @@
![](res/HutaoRepoBanner2.png)
![Banner2-large](https://github.com/DGP-Studio/Snap.Hutao/assets/10614984/742129f4-f903-4d16-bf1e-3cbfad873ee4)
胡桃工具箱是一个 Windows 平台的开源的原神工具箱,旨在帮助玩家获得更好的游戏体验; 它是对官方移动端工具的一种非破坏性功能扩展,为不习惯在移动端进行原神游戏的 PC 玩家提供一个在 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.
胡桃工具箱是一款以 MIT 协议开源的原神工具箱,专为现代化 Windows 平台设计,旨在改善桌面端玩家的游戏体验。通过将既有的官方资源与开发团队设计的全新 功能相结合,它提供了一套完整且实用的工具集,且无需依赖任何移动设备。它不对游戏客户端进行任何破坏性修改以确保工具箱的安全性
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
@@ -47,4 +48,4 @@ Snap Hutao is an open-source Genshin Impact toolbox on Windows platform, aim to
## 开发 / Development
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
[![Star History Chart](https://api.star-history.com/svg?repos=DGP-Studio/Snap.Hutao&type=Date)](https://star-history.com/#DGP-Studio/Snap.Hutao&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=DGP-Studio/Snap.Hutao&type=Date)](https://star-history.com/#DGP-Studio/Snap.Hutao&Date)

View File

@@ -55,7 +55,7 @@ steps:
displayName: Install dotNet
inputs:
packageType: 'sdk'
version: '7.x'
version: '8.x'
includePreviewVersions: true
- task: NuGetToolInstaller@1
@@ -86,7 +86,7 @@ steps:
- task: MagicChunks@2
inputs:
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\AppxManifest.xml'
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net8.0-windows10.0.22621.0\win-x64\AppxManifest.xml'
fileType: 'Xml'
targetPathType: 'source'
transformationType: 'json'
@@ -107,7 +107,7 @@ steps:
mkdir Assets
mkdir Resource
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64'
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net8.0-windows10.0.22621.0\win-x64'
- task: CopyFiles@2
@@ -115,19 +115,19 @@ steps:
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Assets'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\Assets'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net8.0-windows10.0.22621.0\win-x64\Assets'
- task: CopyFiles@2
displayName: Copy Resource Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Resource'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64\Resource'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net8.0-windows10.0.22621.0\win-x64\Resource'
- task: CmdLine@2
displayName: Build MSIX
inputs:
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.19041.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net8.0-windows10.0.22621.0\win-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
- task: MsixSigning@1
name: signMsix

View File

@@ -1,30 +0,0 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
exclude:
- name: Test
paths:
- Snap.Hutao.Test
- Snap.Hutao.SourceGeneration
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-dotnet:latest

View File

@@ -108,7 +108,9 @@ dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_diagnostic.SA1629.severity = none
dotnet_diagnostic.SA1642.severity = none
dotnet_diagnostic.IDE0005.severity = warning
dotnet_diagnostic.IDE0060.severity = none
dotnet_diagnostic.IDE0290.severity = none
# SA1208: System using directives should be placed before other using directives
dotnet_diagnostic.SA1208.severity = none
@@ -322,6 +324,8 @@ dotnet_diagnostic.CA2227.severity = suggestion
# CA2251: 使用 “string.Equals”
dotnet_diagnostic.CA2251.severity = suggestion
csharp_style_prefer_primary_constructors = true:suggestion
dotnet_diagnostic.SA1010.severity = none
[*.vb]
#### 命名样式 ####

View File

@@ -140,8 +140,28 @@ internal sealed class AttributeGenerator : IIncrementalGenerator
public InjectionAttribute(InjectAs injectAs, Type interfaceType)
{
}
public object Key { get; set; }
}
""";
context.AddSource("Snap.Hutao.Core.DependencyInjection.Annotation.Attributes.g.cs", coreDependencyInjectionAnnotations);
string resourceLocalization = """
namespace Snap.Hutao.Resource.Localization;
[AttributeUsage(AttributeTargets.Enum)]
internal sealed class LocalizationAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field)]
internal sealed class LocalizationKeyAttribute : Attribute
{
public LocalizationKeyAttribute(string key)
{
}
}
""";
context.AddSource("Snap.Hutao.Resource.Localization.Attributes.g.cs", resourceLocalization);
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Snap.Hutao.SourceGeneration.Primitive;
using System.Collections.Generic;
@@ -68,7 +69,8 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
string propertyName = (string)arguments[0].Value!;
string propertyType = arguments[1].Value!.ToString();
string defaultValue = GetLiteralString(arguments.ElementAtOrDefault(2)) ?? "default";
string defaultValue = arguments.ElementAtOrDefault(2).ToCSharpString() ?? "default";
defaultValue = defaultValue == "null" ? "default" : defaultValue;
string propertyChangedCallback = arguments.ElementAtOrDefault(3) is { IsNull: false } arg3 ? $", {arg3.Value}" : string.Empty;
string code;
@@ -125,25 +127,4 @@ internal sealed class DependencyPropertyGenerator : IIncrementalGenerator
production.AddSource($"{normalizedClassName}.{propertyName}.g.cs", code);
}
}
private static string? GetLiteralString(TypedConstant typedConstant)
{
if (typedConstant.IsNull)
{
return default;
}
if (typedConstant.Value is bool boolValue)
{
return boolValue ? "true" : "false";
}
string result = typedConstant.Value!.ToString();
if (string.IsNullOrEmpty(result))
{
return default;
}
return result;
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.CodeAnalysis;
using System;
using System.Net.Http;
using System.Runtime.Serialization;
@@ -10,7 +11,18 @@ namespace Snap.Hutao.SourceGeneration.Automation;
[Generator(LanguageNames.CSharp)]
internal sealed class SaltConstantGenerator : IIncrementalGenerator
{
private static readonly HttpClient httpClient = new();
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)
{
@@ -19,8 +31,7 @@ internal sealed class SaltConstantGenerator : IIncrementalGenerator
private static void GenerateSaltContstants(IncrementalGeneratorPostInitializationContext context)
{
string body = httpClient.GetStringAsync("https://internal.snapgenshin.cn/Archive/Salt/Latest").GetAwaiter().GetResult();
Response<SaltLatest> saltInfo = JsonParser.FromJson<Response<SaltLatest>>(body)!;
Response<SaltLatest> saltInfo = lazySaltInfo.Value;
string code = $$"""
namespace Snap.Hutao.Web.Hoyolab;

View File

@@ -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);
@@ -61,7 +60,6 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.DynamicSecret;
using System.Net.Http;
namespace Snap.Hutao.Core.DependencyInjection;
@@ -87,7 +85,7 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
private static void FillUpWithAddHttpClient(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
{
List<string> lines = new();
List<string> lines = [];
StringBuilder lineBuilder = new();
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
@@ -134,11 +132,6 @@ internal sealed class HttpClientGenerator : IIncrementalGenerator
lineBuilder.Append(" })");
}
if (context.HasAttributeWithName(UseDynamicSecretAttributeName))
{
lineBuilder.Append(".AddHttpMessageHandler<DynamicSecretHandler>()");
}
lineBuilder.Append(';');
lines.Add(lineBuilder.ToString());

View File

@@ -81,7 +81,7 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
private static void FillUpWithAddServices(StringBuilder sourceBuilder, SourceProductionContext production, ImmutableArray<GeneratorSyntaxContext2> contexts)
{
List<string> lines = new();
List<string> lines = [];
StringBuilder lineBuilder = new();
foreach (GeneratorSyntaxContext2 context in contexts.DistinctBy(c => c.Symbol.ToDisplayString()))
@@ -92,17 +92,29 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
ImmutableArray<TypedConstant> arguments = injectionInfo.ConstructorArguments;
string injectAsName = arguments[0].ToCSharpString();
switch (injectAsName)
bool hasKey = injectionInfo.TryGetNamedArgumentValue("Key", out TypedConstant key);
switch (injectAsName, hasKey)
{
case InjectAsSingletonName:
case (InjectAsSingletonName, false):
lineBuilder.Append(" services.AddSingleton<");
break;
case InjectAsTransientName:
case (InjectAsSingletonName, true):
lineBuilder.Append(" services.AddKeyedSingleton<");
break;
case (InjectAsTransientName, false):
lineBuilder.Append(" services.AddTransient<");
break;
case InjectAsScopedName:
case (InjectAsTransientName, true):
lineBuilder.Append(" services.AddKeyedTransient<");
break;
case (InjectAsScopedName, false):
lineBuilder.Append(" services.AddScoped<");
break;
case (InjectAsScopedName, true):
lineBuilder.Append(" services.AddKeyedScoped<");
break;
default:
production.ReportDiagnostic(Diagnostic.Create(invalidInjectionDescriptor, context.Context.Node.GetLocation(), injectAsName));
break;
@@ -113,7 +125,14 @@ internal sealed class InjectionGenerator : IIncrementalGenerator
lineBuilder.Append($"{arguments[1].Value}, ");
}
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();");
if (hasKey)
{
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>({key.ToCSharpString()});");
}
else
{
lineBuilder.Append($"{context.Symbol.ToDisplayString()}>();");
}
lines.Add(lineBuilder.ToString());
}

View File

@@ -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);
}
}
""");

View File

@@ -41,10 +41,10 @@ public static class JsonParser
public static T? FromJson<T>(this string json)
{
// Initialize, if needed, the ThreadStatic variables
propertyInfoCache ??= new Dictionary<Type, Dictionary<string, PropertyInfo>>();
fieldInfoCache ??= new Dictionary<Type, Dictionary<string, FieldInfo>>();
stringBuilder ??= new StringBuilder();
splitArrayPool ??= new Stack<List<string>>();
propertyInfoCache ??= [];
fieldInfoCache ??= [];
stringBuilder ??= new();
splitArrayPool ??= [];
// Remove all whitespace not within strings to make parsing simpler
stringBuilder.Length = 0;
@@ -99,7 +99,7 @@ public static class JsonParser
// Splits { <value>:<value>, <value>:<value> } and [ <value>, <value> ] into a list of <value> strings
private static List<string> Split(string json)
{
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : new List<string>();
List<string> splitArray = splitArrayPool!.Count > 0 ? splitArrayPool.Pop() : [];
splitArray.Clear();
if (json.Length == 2)
{

View File

@@ -3,6 +3,7 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.SourceGeneration.Primitive;
@@ -13,4 +14,19 @@ internal static class AttributeDataExtension
{
return data.NamedArguments.Any(a => a.Key == key && predicate((TValue)a.Value.Value!));
}
public static bool TryGetNamedArgumentValue(this AttributeData data, string key, out TypedConstant value)
{
foreach (KeyValuePair<string, TypedConstant> pair in data.NamedArguments)
{
if (pair.Key == key)
{
value = pair.Value;
return true;
}
}
value = default;
return false;
}
}

View File

@@ -20,7 +20,7 @@ internal static class EnumerableExtension
if (enumerator.MoveNext())
{
HashSet<TKey> set = new();
HashSet<TKey> set = [];
do
{

View File

@@ -0,0 +1,629 @@
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))
IOrderedEnumerable<IGrouping<string, AdditionalText>> group = files
.GroupBy(file => GetResourceName(file.Path), StringComparer.OrdinalIgnoreCase)
.OrderBy(x => x.Key, StringComparer.Ordinal);
List<IGrouping<string, AdditionalText>> resxGroups = [.. group];
foreach (IGrouping<string, AdditionalText>? resxGroup in resxGroups)
{
string? rootNamespaceConfiguration = GetMetadataValue(context, options, "RootNamespace", resxGroup);
string? projectDirConfiguration = GetMetadataValue(context, options, "ProjectDir", resxGroup);
string? namespaceConfiguration = GetMetadataValue(context, options, "Namespace", "DefaultResourcesNamespace", resxGroup);
string? resourceNameConfiguration = GetMetadataValue(context, options, "ResourceName", globalName: null, resxGroup);
string? classNameConfiguration = GetMetadataValue(context, options, "ClassName", globalName: null, resxGroup);
string rootNamespace = rootNamespaceConfiguration ?? assemblyName ?? "";
string projectDir = projectDirConfiguration ?? assemblyName ?? "";
string? defaultResourceName = ComputeResourceName(rootNamespace, projectDir, resxGroup.Key);
string? defaultNamespace = ComputeNamespace(rootNamespace, projectDir, resxGroup.Key);
string? ns = namespaceConfiguration ?? defaultNamespace;
string? resourceName = resourceNameConfiguration ?? defaultResourceName;
string className = classNameConfiguration ?? ToCSharpNameIdentifier(Path.GetFileName(resxGroup.Key));
if (ns == null)
{
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForNamespace, location: null, resxGroup.First().Path));
}
if (resourceName == null)
{
context.ReportDiagnostic(Diagnostic.Create(InvalidPropertiesForResourceName, location: null, resxGroup.First().Path));
}
List<ResxEntry>? entries = LoadResourceFiles(context, resxGroup);
string content = $"""
// Debug info:
// key: {resxGroup.Key}
// files: {string.Join(", ", resxGroup.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(resxGroup.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)
{
foreach((string? each, string locale) in entry.Values.Zip(entry.Locales,(x,y)=>(x,y)))
{
summary.Add(new XElement("para", $"{GetStringWithPadding(locale, 8)} Value: \"{each}\""));
}
}
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.Values.FirstOrDefault() is string value)
{
int args = Regex.Matches(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!)}}({{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 GetStringWithPadding(string source, int length)
{
if (source.Length >= length)
{
return source;
}
return source + new string('_', length - source.Length);
}
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 = [];
foreach (AdditionalText? entry in resxGroug.OrderBy(file => file.Path, StringComparer.Ordinal))
{
SourceText? content = entry.GetText(context.CancellationToken);
if (content is 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;
existingEntry.Values.Add(value);
existingEntry.Locales.Add(GetLocaleName(entry.Path));
}
else
{
entries.Add(new() { Name = name, Values = [value], Locales = [GetLocaleName(entry.Path)], 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,4})?$", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1))
? pathWithoutExtension.Substring(0, indexOf)
: pathWithoutExtension;
}
private static string GetLocaleName(string path)
{
string fileName = Path.GetFileNameWithoutExtension(path);
int indexOf = fileName.LastIndexOf('.');
if (indexOf < 0)
{
return "Neutral";
}
return fileName.Substring(indexOf + 1);
}
private sealed class ResxEntry
{
public string? Name { get; set; }
public List<string?> Values { get; set; } = default!;
public List<string> Locales { get; set; } = default!;
public string? Comment { get; set; }
public string? Type { get; set; }
public bool IsText
{
get
{
if (Type == null)
{
return true;
}
if (Values.FirstOrDefault() is string value)
{
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 (Values.FirstOrDefault() is string value)
{
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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -7,20 +7,13 @@
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
<Configurations>Debug;Release</Configurations>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" Version="3.3.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
</ItemGroup>
</Project>

View File

@@ -57,23 +57,13 @@ internal sealed class UniversalAnalyzer : DiagnosticAnalyzer
private static void CompilationStart(CompilationStartAnalysisContext context)
{
SyntaxKind[] types =
{
SyntaxKind.ClassDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.EnumDeclaration,
};
SyntaxKind[] types = [SyntaxKind.ClassDeclaration, SyntaxKind.InterfaceDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.EnumDeclaration,];
context.RegisterSyntaxNodeAction(HandleTypeShouldBeInternal, types);
context.RegisterSyntaxNodeAction(HandleMethodParameterShouldUseRefLikeKeyword, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleMethodReturnTypeShouldUseValueTaskInsteadOfTask, SyntaxKind.MethodDeclaration);
context.RegisterSyntaxNodeAction(HandleConstructorParameterShouldUseRefLikeKeyword, SyntaxKind.ConstructorDeclaration);
SyntaxKind[] expressions =
{
SyntaxKind.EqualsExpression,
SyntaxKind.NotEqualsExpression,
};
SyntaxKind[] expressions = [SyntaxKind.EqualsExpression, SyntaxKind.NotEqualsExpression,];
context.RegisterSyntaxNodeAction(HandleEqualsAndNotEqualsExpressionShouldUsePatternMatching, expressions);
context.RegisterSyntaxNodeAction(HandleIsPatternShouldUseRecursivePattern, SyntaxKind.IsPatternExpression);
context.RegisterSyntaxNodeAction(HandleArgumentNullExceptionThrowIfNull, SyntaxKind.SuppressNullableWarningExpression);

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass]
public class CollectionsMarshalTest
{
[TestMethod]
public void DictionaryMarshalGetValueRefOrNullRefIsNullRef()
{
#if NET8_0_OR_GREATER
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
Dictionary<string, string> dictionaryRefKeyRefValue = [];
#else
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
Dictionary<string, string> dictionaryRefKeyRefValue = new();
#endif
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyRefValue, 1U)));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyValueValue, 1U)));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryRefKeyValueValue, "no such key")));
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryRefKeyRefValue, "no such key")));
}
[TestMethod]
public void DictionaryMarshalGetValueRefOrAddDefaultIsDefault()
{
#if NET8_0_OR_GREATER
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
Dictionary<string, string> dictionaryRefKeyRefValue = [];
#else
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
Dictionary<string, string> dictionaryRefKeyRefValue = new();
#endif
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyRefValue, 1U, out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyValueValue, 1U, out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryRefKeyValueValue, "no such key", out _) == default);
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryRefKeyRefValue, "no such key", out _) == default);
}
}

View File

@@ -2,11 +2,18 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Test;
namespace Snap.Hutao.Test.BaseClassLibrary;
[TestClass]
public class JsonSerializeTest
{
public TestContext? TestContext { get; set; }
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
private const string SmapleObjectJson = """
{
"A" :1
@@ -44,13 +51,29 @@ public class JsonSerializeTest
[TestMethod]
public void NumberStringKeyCanSerializeAsKey()
{
JsonSerializerOptions options = new()
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, AlowStringNumberOptions)!;
Assert.AreEqual(sample[111], "12");
}
[TestMethod]
public void ByteArraySerializeAsBase64()
{
byte[] array =
#if NET8_0_OR_GREATER
[1, 2, 3, 4, 5];
#else
{ 1, 2, 3, 4, 5 };
#endif
ByteArraySample sample = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
Array = array,
};
Dictionary<int, string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberKeyDictionaryJson, options)!;
Assert.AreEqual(sample[111], "12");
string result = JsonSerializer.Serialize(sample);
TestContext!.WriteLine($"ByteArray Serialize Result: {result}");
Assert.AreEqual(result, """
{"Array":"AQIDBAU="}
""");
}
private sealed class Sample
@@ -64,4 +87,9 @@ public class JsonSerializeTest
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public int A { get; set; }
}
private sealed class ByteArraySample
{
public byte[]? Array { get; set; }
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Buffers.Binary;
using System.Globalization;
using System.Text;
namespace Snap.Hutao.Test.IncomingFeature;
[TestClass]
public sealed class GeniusInvokationDecoding
{
public TestContext? TestContext { get; set; }
/// <summary>
/// https://www.bilibili.com/video/av278125720
/// </summary>
[TestMethod]
public unsafe void GeniusInvokationShareCodeDecoding()
{
// 51 bytes obfuscated data
byte[] bytes = Convert.FromBase64String("BCHBwxQNAYERyVANCJGBynkOCZER2pgOCrFx8poQChGR9bYQDEGB9rkQDFKRD7oRDeEB");
// ---------------------------------------------
// | Data | Caesar Cipher Key |
// |----------|-------------------|
// | 50 Bytes | 1 Byte |
// ---------------------------------------------
// Data:
// 00000100 00100001 11000001 11000011 00010100
// 00001101 00000001 10000001 00010001 11001001
// 01010000 00001101 00001000 10010001 10000001
// 11001010 01111001 00001110 00001001 10010001
// 00010001 11011010 10011000 00001110 00001010
// 10110001 01110001 11110010 10011010 00010000
// 00001010 00010001 10010001 11110101 10110110
// 00010000 00001100 01000001 10000001 11110110
// 10111001 00010000 00001100 01010010 10010001
// 00001111 10111010 00010001 00001101 11100001
// ---------------------------------------------
// Caesar Cipher Key:
// 00000001
// ---------------------------------------------
fixed (byte* ptr = bytes)
{
// Reinterpret as 50 byte actual data and 1 deobfuscate key byte
EncryptedDataAndKey* data = (EncryptedDataAndKey*)ptr;
byte* dataPtr = data->Data;
// ----------------------------------------------------------
// | First | Second | Padding |
// |-----------|----------|---------|
// | 25 Bytes | 25 Bytes | 1 Byte |
// ----------------------------------------------------------
// We are doing two things here:
// 1. Retrieve actual data by subtracting key
// 2. Store data into two halves by alternating between them
// ----------------------------------------------------------
// What we will get after this step:
// ----------------------------------------------------------
// First:
// 00000011 11000000 00010011 00000000 00010000
// 01001111 00000111 10000000 01111000 00001000
// 00010000 10010111 00001001 01110000 10011001
// 00001001 10010000 10110101 00001011 10000000
// 10111000 00001011 10010000 10111001 00001100
// ----------------------------------------------------------
// Second:
// 00100000 11000010 00001100 10000000 11001000
// 00001100 10010000 11001001 00001101 10010000
// 11011001 00001101 10110000 11110001 00001111
// 00010000 11110100 00001111 01000000 11110101
// 00001111 01010001 00001110 00010000 11100000
// ----------------------------------------------------------
RearrangeBuffer rearranged = default;
byte* pFirst = rearranged.First;
byte* pSecond = rearranged.Second;
for (int i = 0; i < 50; i++)
{
// Determine which half are we going to insert
byte** ppTarget = i % 2 == 0 ? &pFirst : &pSecond;
// (actual data = data - key) and store it directly to the target half
**ppTarget = unchecked((byte)(dataPtr[i] - data->Key));
(*ppTarget)++;
}
// Prepare decoded data result storage
DecryptedData decoded = default;
ushort* pDecoded = decoded.Data;
// ----------------------------------------------------------
// | Data |
// |----------| x 17 = 51 Bytes
// | 3 Bytes |
// ----------------------------------------------------------
// Grouping each 3 bytes and read out as 2 ushort with
// 12 bits each (Big Endian)
// ----------------------------------------------------------
// 00000011 1100·0000 00010011|
// 00000000 0001·0000 01001111|
// 00000111 1000·0000 01111000|
// 00001000 0001·0000 10010111|
// 00001001 0111·0000 10011001|
// 00001001 1001·0000 10110101|
// 00001011 1000·0000 10111000|
// 00001011 1001·0000 10111001|
// 00001100 0010·0000 11000010|
// 00001100 1000·0000 11001000|
// 00001100 1001·0000 11001001|
// 00001101 1001·0000 11011001|
// 00001101 1011·0000 11110001|
// 00001111 0001·0000 11110100|
// 00001111 0100·0000 11110101|
// 00001111 0101·0001 00001110|
// 00010000 1110·0000 -padding|[padding32]
// ----------------------------------------------------------
// reinterpret as DecodeGroupingHelper for each 3 bytes
DecodeGroupingHelper* pGroup = (DecodeGroupingHelper*)&rearranged;
for (int i = 0; i < 17; i++)
{
(ushort first, ushort second) = pGroup->GetData();
*pDecoded = first;
*(pDecoded + 1) = second;
pDecoded += 2;
pGroup++;
}
// Now we get
// 60, 19, 1,
// 79,120,120,
// 129,151,151,
// 153,153,181,
// 184,184,185,
// 185,194,194,
// 200,200,201,
// 201,217,217,
// 219,241,241,
// 244,244,245,
// 245,270,270,
StringBuilder stringBuilder = new();
for (int i = 0; i < 33; i++)
{
stringBuilder
.AppendFormat(CultureInfo.InvariantCulture, "{0,3}", decoded.Data[i])
.Append(',');
if (i % 11 == 10)
{
stringBuilder.Append('\n');
}
}
TestContext?.WriteLine(stringBuilder.ToString(0, stringBuilder.Length - 1));
ushort[] resultArray = new ushort[33];
Span<ushort> result = new((ushort*)&decoded, 33);
result.CopyTo(resultArray);
ushort[] testKnownResult =
#if NET8_0_OR_GREATER
[
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
];
#else
{
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
};
#endif
CollectionAssert.AreEqual(resultArray, testKnownResult);
}
}
private struct EncryptedDataAndKey
{
public unsafe fixed byte Data[50];
public byte Key;
}
private struct RearrangeBuffer
{
public unsafe fixed byte First[25];
public unsafe fixed byte Second[25];
// Make it 51 bytes
// allow to be group as 17 DecodeGroupingHelper later
public byte padding;
// prevent accidently int32 cast access violation
public byte paddingTo32;
}
private struct DecodeGroupingHelper
{
public unsafe fixed byte Data[3];
public unsafe (ushort First, ushort Second) GetData()
{
fixed (byte* ptr = Data)
{
uint value = BinaryPrimitives.ReverseEndianness((*(uint*)ptr) & 0x00FFFFFF) >> 8; // keep low 24 bits only
return ((ushort)((value >> 12) & 0x0FFF), (ushort)(value & 0x0FFF));
}
}
}
private struct DecryptedData
{
public unsafe fixed ushort Data[33];
}
}

View File

@@ -1,7 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using System;
namespace Snap.Hutao.Test;
namespace Snap.Hutao.Test.PlatformExtensions;
[TestClass]
public sealed class DependencyInjectionTest

View File

@@ -9,11 +9,12 @@ public sealed class ForEachRuntimeBehaviorTest
[TestMethod]
public void ListOfStringCanEnumerateAsReadOnlySpanOfChar()
{
List<string> strings = new()
{
"a", "b", "c"
};
List<string> strings =
#if NET8_0_OR_GREATER
["a", "b", "c"];
#else
new() { "a", "b", "c" };
#endif
int count = 0;
foreach (ReadOnlySpan<char> chars in strings)
{

View File

@@ -8,8 +8,13 @@ public sealed class RangeRuntimeBehaviorTest
[TestMethod]
public void RangeTrimLastOne()
{
#if NET8_0_OR_GREATER
int[] array = [1, 2, 3, 4];
int[] test = [1, 2, 3];
#else
int[] array = { 1, 2, 3, 4 };
int[] test = { 1, 2, 3 };
#endif
int[] result = array[..^1];
Assert.AreEqual(3, result.Length);
Assert.IsTrue(MemoryExtensions.SequenceEqual<int>(test, result));

View File

@@ -6,11 +6,35 @@ public sealed class UnsafeRuntimeBehaviorTest
[TestMethod]
public unsafe void UInt32AllSetIs()
{
byte[] bytes = { 0xFF, 0xFF, 0xFF, 0xFF, };
byte[] bytes =
#if NET8_0_OR_GREATER
[0xFF, 0xFF, 0xFF, 0xFF];
#else
{ 0xFF, 0xFF, 0xFF, 0xFF, };
#endif
fixed (byte* pBytes = bytes)
{
Assert.AreEqual(uint.MaxValue, *(uint*)pBytes);
}
}
}
[TestClass]
public sealed class NewModifierRuntimeBehaviorTest
{
private interface IBase
{
int GetValue();
}
private interface IBaseImpl : IBase
{
new int GetValue();
}
private sealed class Impl : IBaseImpl
{
public int GetValue() => 1;
}
}

View File

@@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>Debug;Release;Debug As Fake Elevated</Configurations>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">

View File

@@ -2,5 +2,10 @@
"$schema": "https://raw.githubusercontent.com/microsoft/CsWin32/main/src/Microsoft.Windows.CsWin32/settings.schema.json",
"allowMarshaling": true,
"useSafeHandles": false,
"emitSingleFile": true
"comInterop": {
"preserveSigMethods": [
"IFileOpenDialog.Show",
"IFileSaveDialog.Show"
]
}
}

View File

@@ -28,8 +28,12 @@ WaitForSingleObject
WriteProcessMemory
// OLE32
CoCreateInstance
CoWaitForMultipleObjects
// SHELL32
SHCreateItemFromParsingName
// USER32
AttachThreadInput
FindWindowExW
@@ -46,6 +50,10 @@ SetForegroundWindow
UnregisterHotKey
// COM
FileOpenDialog
FileSaveDialog
IFileOpenDialog
IFileSaveDialog
IPersistFile
IShellLinkW
ShellLink
@@ -56,6 +64,7 @@ IMemoryBufferByteAccess
// Const value
INFINITE
MAX_PATH
WM_GETMINMAXINFO
WM_HOTKEY
WM_NCRBUTTONDOWN
@@ -63,6 +72,8 @@ WM_NCRBUTTONUP
WM_NULL
// Type & Enum definition
HRESULT_FROM_WIN32
SLGP_FLAGS
// System.Threading
LPTHREAD_START_ROUTINE

View File

@@ -0,0 +1,17 @@
using System;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
namespace Windows.Win32;
internal static partial class PInvoke
{
/// <inheritdoc cref="CoCreateInstance(Guid*, object, CLSCTX, Guid*, out object)"/>
internal static unsafe HRESULT CoCreateInstance<TClass, TInterface>(object? pUnkOuter, CLSCTX dwClsContext, out TInterface ppv)
where TInterface : class
{
HRESULT hr = CoCreateInstance(typeof(TClass).GUID, pUnkOuter, dwClsContext, typeof(TInterface).GUID, out object o);
ppv = (TInterface)o;
return hr;
}
}

View File

@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.22621.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.49-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
// 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 = unchecked((uint)sizeof(WINDOWPLACEMENT)) };
}
}

View 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);
}
}
}

View File

@@ -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

View File

@@ -4,12 +4,18 @@
"add": {
"extensionToExtension": {
"add": {
".json": [ ".txt" ]
".json": [
".txt"
]
}
},
"pathSegment": {
"add": {
".*": [ ".cs", ".resx" ]
".*": [
".cs",
".resx",
".appxmanifest"
]
}
},
"fileSuffixToExtension": {
@@ -19,12 +25,24 @@
},
"fileToFile": {
"add": {
".filenesting.json": [ "App.xaml.cs" ],
"app.manifest": [ "App.xaml.cs" ],
"Package.appxmanifest": [ "App.xaml" ],
"Package.StoreAssociation.xml": [ "App.xaml" ],
".editorconfig": [ "Program.cs" ],
"GlobalUsing.cs": [ "Program.cs" ]
".filenesting.json": [
"App.xaml.cs"
],
"app.manifest": [
"App.xaml.cs"
],
"Package.appxmanifest": [
"App.xaml"
],
"Package.StoreAssociation.xml": [
"App.xaml"
],
".editorconfig": [
"Program.cs"
],
"GlobalUsing.cs": [
"Program.cs"
]
}
}
}

View File

@@ -1,183 +1,39 @@
<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:cwcont="using:CommunityToolkit.WinUI.Controls"
xmlns:cwconv="using:CommunityToolkit.WinUI.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"/>
<ResourceDictionary Source="Control/Loading.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/ComboBox.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/ScrollViewer.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>
<SolidColorBrush x:Key="DarkOnlyOverlayMaskBrush" Color="#00000000"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<Color x:Key="AvatarPropertyAddValueColor">#FF90E800</Color>
<Color x:Key="CompatBackgroundColor">#FF242424</Color>
<SolidColorBrush x:Key="DarkOnlyOverlayMaskBrush" Color="#60000000"/>
</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">&#xE710;</x:String>
<x:String x:Key="FontIconContentSetting">&#xE713;</x:String>
<x:String x:Key="FontIconContentRefresh">&#xE72C;</x:String>
<x:String x:Key="FontIconContentDelete">&#xE74D;</x:String>
<x:String x:Key="FontIconContentFolder">&#xE8B7;</x:String>
<x:String x:Key="FontIconContentCheckList">&#xE9D5;</x:String>
<x:String x:Key="FontIconContentAsteriskBadge12">&#xEDAD;</x:String>
<x:String x:Key="FontIconContentZipFolder">&#xF012;</x:String>
<!-- Converters -->
<cwconv:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<cwconv: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}"
TargetType="GridViewItem">
<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"/>
</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>
<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
x:Name="NoneSelectionListViewItemStyle"
BasedOn="{StaticResource DefaultListViewItemStyle}"
@@ -192,115 +48,6 @@
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0,0,2,4"/>
</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="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>
<!-- ItemsPanelTemplate -->
<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="HorizontalStackPanelTemplate">
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
<ItemsPanelTemplate x:Key="UniformGridColumns5Spacing4Template">
<cwcont:UniformGrid
ColumnSpacing="4"
Columns="5"
RowSpacing="4"/>
</ItemsPanelTemplate>
<!-- Transitions -->
<TransitionCollection x:Key="ReorderThemeTransitions">
<ReorderThemeTransition/>
</TransitionCollection>
<TransitionCollection x:Key="ContentThemeTransitions">
<ContentThemeTransition/>
</TransitionCollection>
<TransitionCollection x:Key="ListViewLikeThemeTransitions">
<AddDeleteThemeTransition/>
<ContentThemeTransition/>
<ReorderThemeTransition/>
<EntranceThemeTransition IsStaggeringEnabled="False"/>
</TransitionCollection>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -3,12 +3,18 @@
namespace Snap.Hutao.Control.Animation;
/// <summary>
/// 动画时长
/// </summary>
[HighQuality]
internal static class AnimationDurations
internal static class ControlAnimationConstants
{
/// <summary>
/// 1
/// </summary>
public const string One = "1";
/// <summary>
/// 1.1
/// </summary>
public const string OnePointOne = "1.1";
/// <summary>
/// 图片缩放动画
/// </summary>

View File

@@ -19,10 +19,10 @@ internal sealed class ImageZoomInAnimation : ImplicitAnimation<string, Vector3>
/// </summary>
public ImageZoomInAnimation()
{
Duration = AnimationDurations.ImageZoom;
Duration = ControlAnimationConstants.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
To = Core.StringLiterals.OnePointOne;
To = ControlAnimationConstants.OnePointOne;
}
/// <inheritdoc/>

View File

@@ -19,10 +19,10 @@ internal sealed class ImageZoomOutAnimation : ImplicitAnimation<string, Vector3>
/// </summary>
public ImageZoomOutAnimation()
{
Duration = AnimationDurations.ImageZoom;
Duration = ControlAnimationConstants.ImageZoom;
EasingMode = Microsoft.UI.Xaml.Media.Animation.EasingMode.EaseOut;
EasingType = CommunityToolkit.WinUI.Animations.EasingType.Circle;
To = Core.StringLiterals.One;
To = ControlAnimationConstants.One;
}
/// <inheritdoc/>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -35,6 +35,11 @@ internal sealed partial class InvokeCommandOnLoadedBehavior : BehaviorBase<UIEle
private void TryExecuteCommand()
{
if (AssociatedObject is null)
{
return;
}
if (executed)
{
return;

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -11,12 +11,17 @@ namespace Snap.Hutao.Control.Behavior;
/// 打开附着的浮出控件操作
/// </summary>
[HighQuality]
internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction
internal sealed class ShowAttachedFlyoutAction : DependencyObject, IAction
{
/// <inheritdoc/>
public object Execute(object sender, object parameter)
public object? Execute(object sender, object parameter)
{
if (sender is null)
{
return default;
}
FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement);
return default!;
return default;
}
}

View File

@@ -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!;
}
}

View File

@@ -3,7 +3,7 @@
using Windows.UI;
namespace Snap.Hutao.Control;
namespace Snap.Hutao.Control.Brush;
internal sealed class ColorSegment : IColorSegment
{

View File

@@ -3,7 +3,7 @@
using Windows.UI;
namespace Snap.Hutao.Control;
namespace Snap.Hutao.Control.Brush;
internal interface IColorSegment
{

View File

@@ -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
@@ -35,9 +35,9 @@ internal sealed partial class SegmentedBar : ContentControl
double offset = 0;
foreach (ref readonly IColorSegment segment in CollectionsMarshal.AsSpan(list))
{
collection.Add(new GradientStop() { Color = segment.Color, Offset = offset, });
collection.Add(new() { Color = segment.Color, Offset = offset, });
offset += segment.Value / total;
collection.Add(new GradientStop() { Color = segment.Color, Offset = offset, });
collection.Add(new() { Color = segment.Color, Offset = offset, });
}
}
}

View File

@@ -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;

View File

@@ -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; }
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using System.Diagnostics;
namespace Snap.Hutao.Control.Extension;
/// <summary>
/// Bridge 拓展
/// </summary>
[HighQuality]
internal static class WebView2Extension
{
[Conditional("RELEASE")]
public static void DisableDevToolsForReleaseBuild(this CoreWebView2 webView)
{
CoreWebView2Settings settings = webView.Settings;
settings.AreBrowserAcceleratorKeysEnabled = false;
settings.AreDefaultContextMenusEnabled = false;
settings.AreDevToolsEnabled = false;
}
public static void DisableAutoCompletion(this CoreWebView2 webView)
{
CoreWebView2Settings settings = webView.Settings;
settings.IsGeneralAutofillEnabled = false;
settings.IsPasswordAutosaveEnabled = false;
}
public static async ValueTask DeleteCookiesAsync(this CoreWebView2 webView, string url)
{
CoreWebView2CookieManager manager = webView.CookieManager;
IReadOnlyList<CoreWebView2Cookie> cookies = await manager.GetCookiesAsync(url);
foreach (CoreWebView2Cookie item in cookies)
{
manager.DeleteCookie(item);
}
}
public static bool IsDisposed(this WebView2 webView2)
{
return WinRTExtension.IsDisposed(webView2);
}
}

View File

@@ -0,0 +1,14 @@
// 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("LeftPanelMaxWidth", typeof(double), IsAttached = true, AttachedType = typeof(ScrollViewer))]
[DependencyProperty("RightPanel", typeof(UIElement), IsAttached = true, AttachedType = typeof(ScrollViewer))]
public sealed partial class ScrollViewerHelper
{
}

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
internal interface IScopedPageScopeReferenceTracker
{
IServiceScope CreateScope();
}

View 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>

View File

@@ -30,7 +30,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
private readonly IServiceProvider serviceProvider;
private readonly RoutedEventHandler unloadEventHandler;
private readonly SizeChangedEventHandler sizeChangedEventHandler;
private readonly TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs> loadedImageSourceLoadCompletedEventHandler;
@@ -46,9 +45,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
serviceProvider = this.ServiceProvider();
this.DisableInteraction();
unloadEventHandler = OnUnload;
Unloaded += unloadEventHandler;
sizeChangedEventHandler = OnSizeChanged;
SizeChanged += sizeChangedEventHandler;
@@ -67,10 +63,6 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
}
protected virtual void Unloading()
{
}
/// <summary>
/// 更新视觉对象
/// </summary>
@@ -110,7 +102,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
if (exception is HttpRequestException httpRequestException)
{
infoBarService.Error(httpRequestException, SH.ControlImageCompositionImageHttpRequest.Format(uri));
infoBarService.Error(httpRequestException, SH.FormatControlImageCompositionImageHttpRequest(uri));
}
else
{
@@ -164,11 +156,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)
@@ -181,7 +188,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 0D, to: 1D, duration: AnimationDurations.ImageFadeIn)
.Opacity(from: 0D, to: 1D, duration: ControlAnimationConstants.ImageFadeIn)
.StartAsync(this, token)
.ConfigureAwait(true);
}
@@ -202,7 +209,7 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
{
await AnimationBuilder
.Create()
.Opacity(from: 1D, to: 0D, duration: AnimationDurations.ImageFadeOut)
.Opacity(from: 1D, to: 0D, duration: ControlAnimationConstants.ImageFadeOut)
.StartAsync(this, token)
.ConfigureAwait(true);
}
@@ -216,7 +223,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)
@@ -226,14 +232,4 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co
UpdateVisual(spriteVisual);
}
}
private void OnUnload(object sender, RoutedEventArgs e)
{
Unloading();
spriteVisual?.Dispose();
spriteVisual = null;
SizeChanged -= sizeChangedEventHandler;
Unloaded -= unloadEventHandler;
}
}

View File

@@ -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;

View File

@@ -45,16 +45,6 @@ internal sealed class MonoChrome : CompositionImage
return compositor.CompositeSpriteVisual(alphaMaskEffectBrush);
}
protected override void Unloading()
{
ActualThemeChanged -= actualThemeChangedEventHandler;
backgroundBrush?.Dispose();
backgroundBrush = null;
base.Unloading();
}
private void OnActualThemeChanged(FrameworkElement sender, object args)
{
if (backgroundBrush is not null)

View File

@@ -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 = [];
List<ItemCollectionTransition> removeTransitions = [];
List<ItemCollectionTransition> moveTransitions = [];
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);
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
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; }
}

View File

@@ -0,0 +1,272 @@
// 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 = [];
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);
foreach (ref readonly UniformStaggeredItem item in CollectionsMarshal.AsSpan(layout))
{
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();
}
}

View File

@@ -0,0 +1,205 @@
// 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 = [];
private readonly VirtualizingLayoutContext context;
private readonly Dictionary<int, UniformStaggeredColumnLayout> columnLayout = [];
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 = [];
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;
}
}
internal UniformStaggeredColumnLayout GetColumnLayout(int columnIndex)
{
return columnLayout[columnIndex];
}
/// <summary>
/// Clear everything that has been calculated.
/// </summary>
internal void Clear()
{
// https://github.com/DGP-Studio/Snap.Hutao/issues/1079
// The first element must be force refreshed otherwise
// it will use the old one realized
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
// Now we need to refresh the first element of each column
// https://github.com/DGP-Studio/Snap.Hutao/issues/1099
// Finally we need to refresh the whole layout when we reset
if (context.ItemCount > 0)
{
for (int i = 0; i < context.ItemCount; i++)
{
RecycleElementAt(i);
}
}
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;
}
}
}
}
}

View File

@@ -11,7 +11,6 @@ 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()

View File

@@ -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;
}
}

View File

@@ -9,11 +9,11 @@
mc:Ignorable="d">
<cwc:SegmentedItem
Icon="{shcm:FontIcon Glyph=&#xE8FD;}"
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentBulletedList}}"
Tag="List"
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownListName}"/>
<cwc:SegmentedItem
Icon="{shcm:FontIcon Glyph=&#xF0E2;}"
Icon="{shcm:FontIcon Glyph={StaticResource FontIconContentGridView}}"
Tag="Grid"
ToolTipService.ToolTip="{shcm:ResourceString Name=ControlPanelPanelSelectorDropdownGridName}"/>

View File

@@ -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}";
}
}

View File

@@ -17,9 +17,6 @@ namespace Snap.Hutao.Control;
[SuppressMessage("", "CA1001")]
internal class ScopedPage : Page
{
// Allow GC to Collect the IServiceScope
private static readonly WeakReference<IServiceScope> PreviousScopeReference = new(default!);
private readonly RoutedEventHandler unloadEventHandler;
private readonly CancellationTokenSource viewCancellationTokenSource = new();
private readonly IServiceScope currentScope;
@@ -31,22 +28,7 @@ internal class ScopedPage : Page
{
unloadEventHandler = OnUnloaded;
Unloaded += unloadEventHandler;
currentScope = Ioc.Default.CreateScope();
DisposePreviousScope();
// track current
PreviousScopeReference.SetTarget(currentScope);
}
/// <summary>
/// 释放上个范围
/// </summary>
public static void DisposePreviousScope()
{
if (PreviousScopeReference.TryGetTarget(out IServiceScope? scope))
{
scope.Dispose();
}
currentScope = Ioc.Default.GetRequiredService<IScopedPageScopeReferenceTracker>().CreateScope();
}
/// <summary>

View File

@@ -0,0 +1,40 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Control;
/// <summary>
/// By injecting into services, we take dvantage of the fact that
/// IServiceProvider disposes all injected services when it is disposed.
/// </summary>
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IScopedPageScopeReferenceTracker))]
internal sealed partial class ScopedPageScopeReferenceTracker : IScopedPageScopeReferenceTracker, IDisposable
{
private readonly IServiceProvider serviceProvider;
private readonly WeakReference<IServiceScope> previousScopeReference = new(default!);
public void Dispose()
{
DisposePreviousScope();
}
public IServiceScope CreateScope()
{
IServiceScope currentScope = serviceProvider.CreateScope();
// In case previous one is not disposed.
DisposePreviousScope();
previousScopeReference.SetTarget(currentScope);
return currentScope;
}
private void DisposePreviousScope()
{
if (previousScopeReference.TryGetTarget(out IServiceScope? scope))
{
scope.Dispose();
}
}
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Core;
using Windows.Foundation;
namespace Snap.Hutao.Control;
[DependencyProperty("IsWidthRestricted", typeof(bool), true)]
[DependencyProperty("IsHeightRestricted", typeof(bool), true)]
internal sealed partial class SizeRestrictedContentControl : ContentControl
{
private double minContentWidth;
private double minContentHeight;
protected override Size MeasureOverride(Size availableSize)
{
if (Content is FrameworkElement element)
{
element.Measure(availableSize);
Size contentDesiredSize = element.DesiredSize;
Size contentActualOrDesiredSize = new(
Math.Max(element.ActualWidth, contentDesiredSize.Width),
Math.Max(element.ActualHeight, contentDesiredSize.Height));
if (IsWidthRestricted)
{
if (contentActualOrDesiredSize.Width > minContentWidth)
{
minContentWidth = contentActualOrDesiredSize.Width;
}
element.MinWidth = minContentWidth;
}
if (IsHeightRestricted)
{
if (contentActualOrDesiredSize.Height > minContentHeight)
{
minContentHeight = contentActualOrDesiredSize.Height;
}
element.MinHeight = minContentHeight;
}
}
return base.MeasureOverride(availableSize);
}
}

View 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>

View 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>

View File

@@ -0,0 +1,11 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style
x:Key="CommandBarComboBoxStyle"
BasedOn="{StaticResource DefaultComboBoxStyle}"
TargetType="ComboBox">
<Setter Property="Padding" Value="12,7,0,7"/>
<Setter Property="Height" Value="36"/>
</Style>
<!-- https://github.com/microsoft/microsoft-ui-xaml/issues/4811 -->
<x:Int32 x:Key="__DiscardPageOverride">0</x:Int32>
</ResourceDictionary>

View 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>

View File

@@ -0,0 +1,5 @@
<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>
<CornerRadius x:Key="ControlCornerRadiusTopRightAndBottomLeft">0,4,0,4</CornerRadius>
</ResourceDictionary>

View File

@@ -0,0 +1,21 @@
<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="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>

View File

@@ -1,7 +1,7 @@
<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">
<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>
@@ -82,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>
@@ -100,276 +93,19 @@
<Style BasedOn="{StaticResource DefaultScrollViewerStyle}" TargetType="ScrollViewer">
<Setter Property="FontFamily" Value="{StaticResource MiSans}"/>
</Style>
<!-- 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>
<SolidColorBrush x:Key="TextControlTextForeground" Color="{ThemeResource TextFillColorPrimary}"/>
<Thickness x:Key="TextControlTextMargin">1,0</Thickness>
<Style
x:Key="TextControlTextBlockStyle"
BasedOn="{StaticResource BodyTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextControlTextForeground}"/>
<Setter Property="Margin" Value="{StaticResource TextControlTextMargin}"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalTextAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
</Style>
</ResourceDictionary>
</ResourceDictionary>

View 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">&#xE710;</x:String>
<x:String x:Key="FontIconContentSetting">&#xE713;</x:String>
<x:String x:Key="FontIconContentRefresh">&#xE72C;</x:String>
<x:String x:Key="FontIconContentDelete">&#xE74D;</x:String>
<x:String x:Key="FontIconContentChevronRight">&#xE76C;</x:String>
<x:String x:Key="FontIconContentWarning">&#xE7BA;</x:String>
<x:String x:Key="FontIconContentGame">&#xE7FC;</x:String>
<x:String x:Key="FontIconContentOpenInNewWindow">&#xE8A7;</x:String>
<x:String x:Key="FontIconContentFolder">&#xE8B7;</x:String>
<x:String x:Key="FontIconContentCopy">&#xE8C8;</x:String>
<x:String x:Key="FontIconContentBulletedList">&#xE8FD;</x:String>
<x:String x:Key="FontIconContentCheckList">&#xE9D5;</x:String>
<x:String x:Key="FontIconContentWebsite">&#xEB41;</x:String>
<x:String x:Key="FontIconContentHomeGroup">&#xEC26;</x:String>
<x:String x:Key="FontIconContentAsteriskBadge12">&#xEDAD;</x:String>
<x:String x:Key="FontIconContentZipFolder">&#xF012;</x:String>
<x:String x:Key="FontIconContentGridView">&#xF0E2;</x:String>
<x:String x:Key="FontIconContentGiftboxOpen">&#xF133;</x:String>
</ResourceDictionary>

View 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>

View File

@@ -0,0 +1,41 @@
<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="StackPanelSpacing4Template">
<StackPanel Spacing="4"/>
</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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="utf-8" ?>
<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">
<Style x:Key="TwoPanelScrollViewerStyle" TargetType="ScrollViewer">
<Setter Property="HorizontalScrollMode" Value="Auto"/>
<Setter Property="VerticalScrollMode" Value="Auto"/>
<Setter Property="IsHorizontalRailEnabled" Value="True"/>
<Setter Property="IsVerticalRailEnabled" Value="True"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="ZoomMode" Value="Disabled"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="VerticalScrollBarVisibility" Value="Visible"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ScrollViewer">
<Border
x:Name="Root"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid
Grid.RowSpan="2"
Grid.ColumnSpan="2"
Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="{Binding Path=(shch:ScrollViewerHelper.LeftPanelMaxWidth), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollContentPresenter x:Name="ScrollContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/>
<ContentPresenter Grid.Column="1" Content="{Binding Path=(shch:ScrollViewerHelper.RightPanel), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Grid>
<Grid Grid.RowSpan="2" Grid.ColumnSpan="2"/>
<Grid
Grid.Column="1"
Padding="{ThemeResource ScrollViewerScrollBarMargin}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}">
<ScrollBar
x:Name="VerticalScrollBar"
HorizontalAlignment="Right"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableHeight}"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{TemplateBinding VerticalOffset}"/>
</Grid>
<Grid
Grid.Row="1"
Padding="{ThemeResource ScrollViewerScrollBarMargin}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}">
<ScrollBar
x:Name="HorizontalScrollBar"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableWidth}"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{TemplateBinding HorizontalOffset}"/>
</Grid>
<Border
x:Name="ScrollBarSeparator"
Grid.Row="1"
Grid.Column="1"
Background="{ThemeResource ScrollViewerScrollBarSeparatorBackground}"
Opacity="0"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ScrollingIndicatorStates">
<VisualStateGroup.Transitions>
<VisualTransition From="MouseIndicator" To="NoIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="MouseIndicatorFull" To="NoIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="MouseIndicatorFull" To="MouseIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="{ThemeResource ScrollViewerSeparatorContractDelay}">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="TouchIndicator" To="NoIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0:0:0.5">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>None</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="NoIndicator"/>
<VisualState x:Name="TouchIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>TouchIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseIndicator">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseIndicatorFull">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="VerticalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HorizontalScrollBar" Storyboard.TargetProperty="IndicatorMode">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<ScrollingIndicatorMode>MouseIndicator</ScrollingIndicatorMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ScrollBarSeparatorStates">
<VisualStateGroup.Transitions>
<VisualTransition From="ScrollBarSeparatorExpanded" To="ScrollBarSeparatorCollapsed">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorContractBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="{ThemeResource ScrollViewerSeparatorContractDuration}"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="ScrollBarSeparatorCollapsed"/>
<VisualState x:Name="ScrollBarSeparatorExpanded">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorExpandBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="{ThemeResource ScrollViewerSeparatorExpandDuration}"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ScrollBarSeparatorExpandedWithoutAnimation">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorExpandBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="ScrollBarSeparatorCollapsedWithoutAnimation">
<Storyboard>
<DoubleAnimation
BeginTime="{ThemeResource ScrollViewerSeparatorContractBeginTime}"
Storyboard.TargetName="ScrollBarSeparator"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View 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>

Some files were not shown because too many files have changed in this diff Show More