Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b9a50df91 | ||
|
|
e4b0489a22 | ||
|
|
5ef81e490a | ||
|
|
ade6e04c6e | ||
|
|
9dcb799fb0 | ||
|
|
073546ba19 | ||
|
|
f8584954b8 | ||
|
|
a91d5e0079 | ||
|
|
5dcbd570a8 | ||
|
|
87a89e877d | ||
|
|
bc4ed0797f | ||
|
|
b4a8193bae | ||
|
|
0a3258974a | ||
|
|
d69ef35547 | ||
|
|
1ca8b78388 | ||
|
|
0e0d12fdce | ||
|
|
565b49321f | ||
|
|
8f4e6bbe05 | ||
|
|
d1e969d742 | ||
|
|
6d5c73fe98 | ||
|
|
8122a65e86 | ||
|
|
8f76864574 | ||
|
|
06125e2b89 | ||
|
|
39c49c8b9d | ||
|
|
8230882954 | ||
|
|
87cd3bc69d | ||
|
|
4759945743 | ||
|
|
092b2a2edd | ||
|
|
140348e10d | ||
|
|
05c8a47a06 | ||
|
|
b8651f109f | ||
|
|
e5d4db3a6d | ||
|
|
7c75fe4d4b | ||
|
|
9afbfe3f64 | ||
|
|
639de1b48a | ||
|
|
904de2af71 | ||
|
|
2fe1ceb40f | ||
|
|
0ce35fe7e5 | ||
|
|
7fb6e5d68e | ||
|
|
c7ccdd65dc | ||
|
|
bbb4fc31a8 | ||
|
|
d6c8266c58 | ||
|
|
217d34a237 | ||
|
|
671cf8bdf5 | ||
|
|
fd7b722346 | ||
|
|
cf7f8cbe19 | ||
|
|
5b05f87067 | ||
|
|
c6d0457946 | ||
|
|
124e3be6bf | ||
|
|
070c4f48b0 | ||
|
|
8bdf027647 | ||
|
|
6fe24e2522 | ||
|
|
bebbc3536a | ||
|
|
4d27f97a7a | ||
|
|
7b30b6d02f | ||
|
|
f1009d7d67 | ||
|
|
3f9bab9686 | ||
|
|
b868e193e5 | ||
|
|
00089c3b91 | ||
|
|
ae6bbccfda | ||
|
|
e25a06205f | ||
|
|
31909201fa | ||
|
|
1ae7e8e371 | ||
|
|
5de7050dff | ||
|
|
4319c991b3 | ||
|
|
a02602776a | ||
|
|
7370b1adcb | ||
|
|
9beabf6933 | ||
|
|
f109f75d96 | ||
|
|
5a1b560087 | ||
|
|
61638c75b1 | ||
|
|
348cc81252 | ||
|
|
cad37e9fc2 | ||
|
|
105e798744 | ||
|
|
9be217fe12 | ||
|
|
4d51dda61e | ||
|
|
c86fdc3af1 | ||
|
|
8c3896da36 | ||
|
|
d95364c2d5 | ||
|
|
3d4b768af9 | ||
|
|
ac18b979ce | ||
|
|
c2580809e0 | ||
|
|
bbb2182175 | ||
|
|
e24311c7cf | ||
|
|
f5370a1743 | ||
|
|
c10eae6596 | ||
|
|
b286183151 | ||
|
|
409e1ccdc1 | ||
|
|
1bc4f3feef | ||
|
|
53fe8204cf | ||
|
|
277c020186 | ||
|
|
4f46b6b4a8 | ||
|
|
a5602da9b9 | ||
|
|
b42f9c9efd | ||
|
|
570df497c8 | ||
|
|
20846ed523 | ||
|
|
1b4d1296e7 | ||
|
|
793e07da03 | ||
|
|
115a0cd9e9 | ||
|
|
e061feb957 | ||
|
|
4577a85d1a | ||
|
|
6d9033f099 | ||
|
|
0030ebe029 | ||
|
|
08c81b6951 | ||
|
|
67764b393d | ||
|
|
6aedd20512 | ||
|
|
db5f6b5773 | ||
|
|
3862d77e50 | ||
|
|
15fda76fd3 | ||
|
|
fa02f5e015 | ||
|
|
6becdcf666 | ||
|
|
dd70b59ea6 | ||
|
|
cbf9180bd4 | ||
|
|
cc1073ad03 | ||
|
|
24ff7e6f40 | ||
|
|
f578dbd784 | ||
|
|
7879112bc9 | ||
|
|
2a01043261 | ||
|
|
36b5bcbef4 | ||
|
|
c850596cab | ||
|
|
c3ee4c7e1c | ||
|
|
4d76936f22 | ||
|
|
be59ab8dfb | ||
|
|
fffa26cd4c | ||
|
|
d253b4f187 | ||
|
|
2e41bbc187 | ||
|
|
af8be24506 | ||
|
|
f14bb03361 | ||
|
|
de4ad48acb | ||
|
|
c6c7b818c9 | ||
|
|
46f4b0b2d2 | ||
|
|
ffaf5b9618 | ||
|
|
c89ebf3b19 | ||
|
|
6a140ce5b9 | ||
|
|
43963f8f7c | ||
|
|
17d8b269ef | ||
|
|
762f1adf2f | ||
|
|
3e89d9261a | ||
|
|
95f775c3f3 | ||
|
|
228e2d1fb9 | ||
|
|
df1bab0ebe | ||
|
|
be67c4ccc2 | ||
|
|
802ca80314 | ||
|
|
3951a12d00 | ||
|
|
be9798669d | ||
|
|
3db574fcdc | ||
|
|
dbffe737e9 | ||
|
|
2bffafcd7a | ||
|
|
8b49aea476 | ||
|
|
9aa882e2fa | ||
|
|
c184cc22e5 | ||
|
|
156dadc202 | ||
|
|
85aa14aa25 | ||
|
|
c75ee952c5 | ||
|
|
d2d302901f | ||
|
|
a601802dc5 | ||
|
|
dc7d6b3e91 | ||
|
|
4c1578f9aa | ||
|
|
26ecd049c1 | ||
|
|
15820c893f | ||
|
|
cd765a9097 | ||
|
|
9f87b694cf | ||
|
|
2749812fe3 | ||
|
|
73bdc53309 | ||
|
|
52b4c87fbc | ||
|
|
87f5cc2ea9 | ||
|
|
8323c1f116 | ||
|
|
4ad3acb8ff | ||
|
|
7ee3ebb51a | ||
|
|
1f1a3348bc | ||
|
|
8babdf990f | ||
|
|
b8eab42c33 | ||
|
|
3ec91e1b0b | ||
|
|
bd4498cec4 | ||
|
|
6dc9dadf1c | ||
|
|
68f4808c1c | ||
|
|
a674630f54 | ||
|
|
1ecff91c1c | ||
|
|
085af160a1 | ||
|
|
d6ba445e54 | ||
|
|
095bd473af | ||
|
|
730387d424 | ||
|
|
f79fc5267b | ||
|
|
15c04d46d4 | ||
|
|
526d1fe700 | ||
|
|
43db6f073e | ||
|
|
ae187a346d | ||
|
|
ad757f54b7 | ||
|
|
865f5a93e7 | ||
|
|
efd480f06c | ||
|
|
a067271995 | ||
|
|
f85361d009 | ||
|
|
4a5cc3f95b | ||
|
|
4c0f525cf3 | ||
|
|
8d2eb85aec | ||
|
|
e5dccf3a13 | ||
|
|
8ba86ffed8 | ||
|
|
76f4e761b1 | ||
|
|
e503a39baf | ||
|
|
5ed4a9bbbf | ||
|
|
382deaa197 | ||
|
|
00966bec6a | ||
|
|
a41579b1da | ||
|
|
86ed0e9ae0 | ||
|
|
ab697054c8 | ||
|
|
0ba2fd1edb | ||
|
|
49984a2913 | ||
|
|
6f6744dfd2 | ||
|
|
2210da6fda | ||
|
|
08e5e64ca4 | ||
|
|
ee385912f8 | ||
|
|
47c3c163ed | ||
|
|
45b6582f30 | ||
|
|
6f9262ef4f | ||
|
|
46fd4c1e8d | ||
|
|
c3d79e1f18 | ||
|
|
f63e9a896c | ||
|
|
9c9d98084a | ||
|
|
cfbde4ad1c | ||
|
|
46b96d6408 | ||
|
|
6f3bad680c | ||
|
|
ad5207470d | ||
|
|
b936608ec7 | ||
|
|
dc7f01dfdd | ||
|
|
3528bf5a3d | ||
|
|
f211df1ea2 | ||
|
|
9a3df4adec | ||
|
|
b09750ffff | ||
|
|
2f74640de5 | ||
|
|
78a5536bc7 | ||
|
|
54c45ff54b | ||
|
|
80f89cdf4f | ||
|
|
fe39495f2e | ||
|
|
20f9a71f68 | ||
|
|
e2c24a7d44 | ||
|
|
37b069c572 | ||
|
|
5622e50a54 | ||
|
|
86d1d387a5 | ||
|
|
7f2f7df05f | ||
|
|
b285ffa401 | ||
|
|
8e095ddaed | ||
|
|
afc08cc243 | ||
|
|
bac349a54c | ||
|
|
02c766ac08 | ||
|
|
273cee69e5 | ||
|
|
adf52d56b7 | ||
|
|
1d0947181f | ||
|
|
f67ef9c2c5 | ||
|
|
28640dcdf5 | ||
|
|
88e87011af | ||
|
|
c22f925807 | ||
|
|
5785ffdfb3 | ||
|
|
59857c3391 | ||
|
|
c90d96e65d | ||
|
|
838cb3c842 | ||
|
|
d133138984 | ||
|
|
128b8001ff | ||
|
|
c7e0dd3816 | ||
|
|
8e0f53c881 | ||
|
|
68483876f3 | ||
|
|
10a5a30968 | ||
|
|
fdc56a2a4b | ||
|
|
490626a293 | ||
|
|
9bc39f8a95 | ||
|
|
ce91fe6245 | ||
|
|
5a841de3ba | ||
|
|
fc910b1955 | ||
|
|
340e7457f8 | ||
|
|
b76266a69d | ||
|
|
5fc0693892 | ||
|
|
73d9eed9a9 | ||
|
|
28ef67ee0f | ||
|
|
add4153067 | ||
|
|
3e474b89ef | ||
|
|
b5cfebe8aa | ||
|
|
4486e7ea2c | ||
|
|
4d91d0b27e | ||
|
|
14e14b9810 | ||
|
|
d6575df6ab | ||
|
|
eed6c92f5a | ||
|
|
ae8de397ae | ||
|
|
2de22b780c | ||
|
|
4d954ae45d | ||
|
|
3d63ceba57 | ||
|
|
8b234d8e4f | ||
|
|
d6a5a8b83e | ||
|
|
885b3eada6 | ||
|
|
deb902904b | ||
|
|
1458ce95ce | ||
|
|
d8efb76bf0 | ||
|
|
a843b22f38 | ||
|
|
08e4414f34 | ||
|
|
8e3adbf473 | ||
|
|
ff225e9f37 | ||
|
|
96b6ef294b | ||
|
|
f271d7212c | ||
|
|
ad583c29c2 | ||
|
|
f98dcd8fef | ||
|
|
9b49235cda | ||
|
|
f090a63c54 | ||
|
|
c332a4dc94 | ||
|
|
6770e55214 | ||
|
|
dd028bb6d8 | ||
|
|
5126b257f8 | ||
|
|
35398052bb | ||
|
|
17031e1a65 | ||
|
|
5aad129121 | ||
|
|
e40a41a761 | ||
|
|
5ae813825c | ||
|
|
48684f8f85 | ||
|
|
3f5392d76d | ||
|
|
9af03d499a | ||
|
|
065b69b346 | ||
|
|
c708aca3fb | ||
|
|
e188f7392c | ||
|
|
d6043c7d32 | ||
|
|
a38e20870b | ||
|
|
72a2b5da8c | ||
|
|
f3017b6b62 | ||
|
|
5274ae8499 | ||
|
|
83e34b84fa | ||
|
|
65812b8754 | ||
|
|
6616afde6d | ||
|
|
7cd5747f4f | ||
|
|
70ec0b66f9 | ||
|
|
74e5eb82f5 | ||
|
|
fdf5407ea3 | ||
|
|
ebc9f5e31f | ||
|
|
4585f31082 | ||
|
|
a63c5b46a6 | ||
|
|
01b26cdf3c | ||
|
|
27b4df0e85 | ||
|
|
a219eca488 | ||
|
|
666243e072 | ||
|
|
66bf71a7f3 | ||
|
|
f1c046986c | ||
|
|
d1fa7109ed | ||
|
|
6c426b09cc | ||
|
|
cbf038d51f | ||
|
|
5e31a1d937 | ||
|
|
81619cbfc7 | ||
|
|
300d1ecf66 | ||
|
|
6dbb5c98cc | ||
|
|
2991e43b45 | ||
|
|
0c1b9e389a | ||
|
|
809a281a7e | ||
|
|
da8a50abd3 | ||
|
|
cddcd9e8b2 | ||
|
|
7ecf306445 | ||
|
|
3adf47aa2c | ||
|
|
70a2f28073 | ||
|
|
9c9216c02c | ||
|
|
f2ae0fbaa9 | ||
|
|
c2a476feff | ||
|
|
36c03d1654 | ||
|
|
ebd20195be | ||
|
|
5a94854809 | ||
|
|
dcac683bfd | ||
|
|
d2e210320b | ||
|
|
73d705aa5b | ||
|
|
878ff980dc | ||
|
|
e19f910251 | ||
|
|
6b0c923777 | ||
|
|
03a64108d9 | ||
|
|
bf121009ae | ||
|
|
971db70ae9 | ||
|
|
2359776803 | ||
|
|
8f8819d0f0 | ||
|
|
8e383ed436 | ||
|
|
34123d5944 | ||
|
|
0c8ccafd46 | ||
|
|
deefff4504 | ||
|
|
83856e5192 | ||
|
|
81d6e501cd | ||
|
|
65f17ba6f5 | ||
|
|
cc81df2e26 | ||
|
|
9a0ad08498 | ||
|
|
3ce005b06b | ||
|
|
30556c2347 | ||
|
|
2a15bcf2c6 | ||
|
|
fa2d66cc45 | ||
|
|
ae8b807ad8 | ||
|
|
b15c3e1da2 | ||
|
|
1c0bd6d02a | ||
|
|
ca3eb311b6 | ||
|
|
0f8632a10b | ||
|
|
5594dbdb8e | ||
|
|
c72cf1e3d5 | ||
|
|
443d306d31 | ||
|
|
acaff10bf0 | ||
|
|
bf6fa718c8 | ||
|
|
85bd89fbc0 | ||
|
|
825334cb9d | ||
|
|
ea9bb8f5ed | ||
|
|
26ea4bed1c | ||
|
|
e6d7c06f29 | ||
|
|
cfa7ce16bd | ||
|
|
fa12897ea9 | ||
|
|
1a71b72ab9 | ||
|
|
124f5c2f8b | ||
|
|
c492d74568 | ||
|
|
88be968866 | ||
|
|
44aea3e962 | ||
|
|
9c8be3a22e | ||
|
|
da71d25a8c | ||
|
|
1cd42280de | ||
|
|
967ff2a9c8 | ||
|
|
7d0b4b72c1 | ||
|
|
b29977ee2f | ||
|
|
f3680f0b78 | ||
|
|
67197bfa94 | ||
|
|
effdf5a3a9 | ||
|
|
e51a5cb6c2 | ||
|
|
5177d3ca0b | ||
|
|
e7bb3d2261 | ||
|
|
cc850c8850 | ||
|
|
aee0ea9147 | ||
|
|
0b214f20e4 | ||
|
|
3abb4493a2 | ||
|
|
580a76ab8e | ||
|
|
4823a834c6 | ||
|
|
1e98e91d4a | ||
|
|
9b9d138e40 | ||
|
|
d33cbdc3e0 | ||
|
|
c8f9066ef2 | ||
|
|
c44d1f066c | ||
|
|
38b22c5242 | ||
|
|
7cc3595e75 | ||
|
|
41334e69b3 | ||
|
|
31434a9b47 | ||
|
|
5353c109b7 | ||
|
|
59bdc75342 | ||
|
|
fd5b64bffc | ||
|
|
5d3b1786d7 | ||
|
|
03b5f79d44 | ||
|
|
8b16e1fe90 | ||
|
|
c2b4c5bb6b | ||
|
|
3d1833fd38 | ||
|
|
0c553219b2 | ||
|
|
a197845749 | ||
|
|
de7dffefe8 | ||
|
|
a13a8dd83f | ||
|
|
acde605ce5 | ||
|
|
ec06c310db | ||
|
|
dc611a982f | ||
|
|
7cdada22a9 | ||
|
|
74657d54c7 | ||
|
|
68f07aa6c2 | ||
|
|
2775ccc4fe | ||
|
|
683410743a | ||
|
|
5302b405b7 | ||
|
|
35c5b69747 | ||
|
|
8dc1148ebc | ||
|
|
4ef328e595 | ||
|
|
a849c3cfd1 | ||
|
|
bd24d4c304 | ||
|
|
0604427492 | ||
|
|
d915fc2362 | ||
|
|
cd736be212 | ||
|
|
2f0958afb7 | ||
|
|
e038911973 | ||
|
|
9021e96684 | ||
|
|
16e09c792c | ||
|
|
52ddb97938 | ||
|
|
31f0b81652 | ||
|
|
ca729067c1 | ||
|
|
36cc718efb | ||
|
|
13340edaf0 | ||
|
|
ca19c3cb35 | ||
|
|
8d85bb47c9 | ||
|
|
56cf544bb8 | ||
|
|
dcab78dcc4 | ||
|
|
c2a7882a4d | ||
|
|
c00d4e2bd6 | ||
|
|
4cd8840385 | ||
|
|
e00e78c88c | ||
|
|
d1a6a88a7f | ||
|
|
1746b16da3 | ||
|
|
f518d12c0a | ||
|
|
0b888f7b01 | ||
|
|
d91515c27d | ||
|
|
38c5f5e9a1 | ||
|
|
7d9923908b | ||
|
|
59e93114a9 | ||
|
|
d546528904 | ||
|
|
f2b6ef27ed | ||
|
|
207ebb4e27 | ||
|
|
6b9bfbf4f6 | ||
|
|
4e14427349 | ||
|
|
0aabb69e16 | ||
|
|
d1d19a4bb3 | ||
|
|
5f7c5b2770 | ||
|
|
502af24cec | ||
|
|
44be631d4a | ||
|
|
1f48948ca6 | ||
|
|
443cffa285 | ||
|
|
7eb3591353 | ||
|
|
ba3ed00e4b | ||
|
|
c41b6b361e | ||
|
|
34697c0d27 | ||
|
|
a4284dbc50 | ||
|
|
fce8781b22 | ||
|
|
05d51a1336 | ||
|
|
6ed967281b | ||
|
|
d42f67066a | ||
|
|
b8ce4ec899 | ||
|
|
4c8174d6db | ||
|
|
de303f4c8b | ||
|
|
ad8eb32ad9 | ||
|
|
92eeba5fff | ||
|
|
aa3f0cd432 | ||
|
|
0b181baf6f | ||
|
|
47969e62dd | ||
|
|
3b80d5915e | ||
|
|
2fd1235a99 | ||
|
|
5e24f91029 | ||
|
|
e6ba658e16 | ||
|
|
e7034db134 | ||
|
|
482d6f71fc | ||
|
|
bf86b0732a | ||
|
|
39b84e8701 | ||
|
|
415188c962 | ||
|
|
7bac28edd9 | ||
|
|
4d25a3d61f | ||
|
|
bccb2901ed | ||
|
|
715c4666f3 | ||
|
|
482812ce85 | ||
|
|
2e9ca1345c | ||
|
|
49a1016da5 | ||
|
|
5c70a594b0 | ||
|
|
855ff55495 | ||
|
|
a174c4856a | ||
|
|
d0b6e54b29 | ||
|
|
16755cd4a9 | ||
|
|
d88509018d | ||
|
|
2f6b283e0d | ||
|
|
013c64ba4f | ||
|
|
c24fb4ee6a | ||
|
|
60d14e8594 | ||
|
|
7c3d820f12 | ||
|
|
045c378526 | ||
|
|
287d3aeb45 | ||
|
|
e973b1174b | ||
|
|
da6b64c9bc | ||
|
|
80a004ceef | ||
|
|
0ee9215b8e | ||
|
|
40e0afefdd | ||
|
|
503244a0de | ||
|
|
1a0c7b0520 | ||
|
|
a6ebf3b43d | ||
|
|
bbb8e97712 | ||
|
|
bcc63bebe9 | ||
|
|
dbe09a3283 | ||
|
|
b7e7126572 | ||
|
|
51490a637a | ||
|
|
406d19b678 | ||
|
|
1db1323273 | ||
|
|
f31a5b14bc | ||
|
|
b5407ad644 | ||
|
|
5740b16325 | ||
|
|
9ff7f89f86 | ||
|
|
c27dfff84c | ||
|
|
25f9363170 | ||
|
|
74730f6102 | ||
|
|
2441c52526 | ||
|
|
aa7ee23039 | ||
|
|
e48f39ef5b | ||
|
|
df2cc61c27 | ||
|
|
4f2e20ff4e | ||
|
|
cde644c1e1 | ||
|
|
aa3e08a7fb | ||
|
|
4c1e9ccf72 | ||
|
|
f8cbbf8092 | ||
|
|
b0e4ca9cbc | ||
|
|
9118b1d20e | ||
|
|
fc1519e9b1 | ||
|
|
c5197e2f2d | ||
|
|
63d36f6595 | ||
|
|
72a33fe2cc | ||
|
|
847c433310 | ||
|
|
30579e6f41 | ||
|
|
feade470a4 | ||
|
|
3eed7c5b7f | ||
|
|
70636a9a77 | ||
|
|
86b0e6d459 | ||
|
|
1b8d164061 | ||
|
|
107a5ae016 | ||
|
|
391d50ce34 | ||
|
|
74a9d18fc5 | ||
|
|
08ad8e6682 | ||
|
|
1e5d5f0377 | ||
|
|
e6e9ebd5dc | ||
|
|
604bcd0997 | ||
|
|
96a4bd05bd | ||
|
|
36df43ad8a | ||
|
|
46713b3870 | ||
|
|
58dfc9a25d | ||
|
|
51751f2464 | ||
|
|
eaf7a93b52 | ||
|
|
718d61af29 | ||
|
|
49c2318ece | ||
|
|
3a90962001 | ||
|
|
838cb79420 | ||
|
|
f6cbb0f986 | ||
|
|
1dd235d313 | ||
|
|
fa778eedb7 | ||
|
|
7dff62aebf | ||
|
|
5034866c8f | ||
|
|
836260478c | ||
|
|
96c35a3bed | ||
|
|
81cf8c1901 | ||
|
|
3b1ab78fb9 | ||
|
|
df83b248f8 | ||
|
|
8b0694fdd0 | ||
|
|
28c32765ce | ||
|
|
5e55e48259 | ||
|
|
ed72973cef | ||
|
|
a1da68a8c2 | ||
|
|
53f7aa0b40 | ||
|
|
68bb80fc81 | ||
|
|
a8bb82b033 | ||
|
|
7083b0f4f4 | ||
|
|
6ed0c6e4fe | ||
|
|
f678dfe853 | ||
|
|
b37c264e32 | ||
|
|
68f8b3eeac | ||
|
|
37354d15f7 | ||
|
|
38b3cefa89 | ||
|
|
bce8e71571 | ||
|
|
f300858ced | ||
|
|
bdcbab700e | ||
|
|
b8eb669351 | ||
|
|
b9d69a3258 | ||
|
|
e454f0c640 | ||
|
|
454c9ea377 | ||
|
|
4f258c8078 | ||
|
|
4b2e2d8eef | ||
|
|
1242d97a1c | ||
|
|
6225391afa | ||
|
|
b1a0e9a79d | ||
|
|
347bc8fb82 | ||
|
|
6bdf7e235a | ||
|
|
d7424d4d2b | ||
|
|
37561a842b | ||
|
|
e04e462737 | ||
|
|
ba262a1741 | ||
|
|
df11fa5a9d | ||
|
|
b6a52f90f6 | ||
|
|
531706e73b | ||
|
|
66a2406e0a | ||
|
|
6ec48abe77 | ||
|
|
9aecc1920a | ||
|
|
75cc86d2a2 | ||
|
|
8abf4707a1 | ||
|
|
b6cc6c10c3 | ||
|
|
5d16cf24bb | ||
|
|
05acfc6b51 | ||
|
|
2a46c9f5ce | ||
|
|
47585e5c9d | ||
|
|
114d9dd079 | ||
|
|
9aff4288b8 | ||
|
|
cb322b6fa0 | ||
|
|
6f1db2d079 | ||
|
|
07ac34dafe | ||
|
|
4638ed72f2 | ||
|
|
b04ee5f28f | ||
|
|
7756187dee | ||
|
|
6a10bd5406 | ||
|
|
134857b62f | ||
|
|
ade7bcff64 | ||
|
|
d599245369 | ||
|
|
70936254de | ||
|
|
9786a447ca | ||
|
|
564ee333aa | ||
|
|
4a2666a998 | ||
|
|
cadb9bb4a0 | ||
|
|
c2d20e422b | ||
|
|
3f8e64450a | ||
|
|
c5ae532fd5 | ||
|
|
fb1caf0c8f | ||
|
|
2ad1a6bae2 | ||
|
|
6de59a7f5b | ||
|
|
8d266c8a0b | ||
|
|
e1d58151cf | ||
|
|
7ba082c3f2 | ||
|
|
f27822b005 | ||
|
|
494e4f6516 | ||
|
|
0a9d115fd5 | ||
|
|
106be2fbaf | ||
|
|
ac9011a323 | ||
|
|
50112b70bd | ||
|
|
71d915f3ef | ||
|
|
e6c1b192ce | ||
|
|
788f7c9a96 | ||
|
|
e782d0ed2d | ||
|
|
a6aa7847c0 | ||
|
|
805a0e1544 | ||
|
|
94476384bd | ||
|
|
b1c01873b7 | ||
|
|
4a6a17e380 | ||
|
|
4de4577103 | ||
|
|
76bf3c40d0 | ||
|
|
d48ab143be | ||
|
|
fad37c9b46 | ||
|
|
ddb6ef9426 | ||
|
|
b5c9736918 | ||
|
|
30bc43bdb5 | ||
|
|
2dfa810431 | ||
|
|
8e0368180c | ||
|
|
c9fb9b9eea | ||
|
|
d2620297ca | ||
|
|
778e5636ce | ||
|
|
0d8b8fc267 | ||
|
|
cc46b29ba6 | ||
|
|
893f791bb2 | ||
|
|
f0b32ab6f2 | ||
|
|
ff6239bb89 | ||
|
|
d1eac50d15 | ||
|
|
9d3be21090 | ||
|
|
49fe40662f | ||
|
|
1aaf37f815 | ||
|
|
3606a834c0 | ||
|
|
08faaf1f46 | ||
|
|
7afd002b85 | ||
|
|
e51f6f0a66 | ||
|
|
b30ad60d99 | ||
|
|
5bf3ce0105 | ||
|
|
ae503a6945 | ||
|
|
dff5b4730e | ||
|
|
4d28be2610 | ||
|
|
58ad193581 | ||
|
|
9fb45610bf | ||
|
|
0d47b82c71 | ||
|
|
e32bc28510 | ||
|
|
9ed5f04f17 | ||
|
|
d390ce05d4 | ||
|
|
6b041cd540 | ||
|
|
c917066e0b | ||
|
|
bec86c0022 | ||
|
|
7b7ad7899a | ||
|
|
5ea5dc3164 | ||
|
|
68c1deb2aa | ||
|
|
10e4bc0bbb | ||
|
|
37eacb00c5 | ||
|
|
ef5b0ba1f7 | ||
|
|
ddfc6897d7 | ||
|
|
2bdda5795a | ||
|
|
26c492b370 | ||
|
|
b6a06f8d8d | ||
|
|
2427fb7619 | ||
|
|
3af99aca0a | ||
|
|
8b0d9e5a5c | ||
|
|
c0f8b5c701 | ||
|
|
f919ae49af | ||
|
|
5598ff3239 | ||
|
|
1519c9038a | ||
|
|
53578a8de9 | ||
|
|
40e421ee34 | ||
|
|
7534f9da1f | ||
|
|
3f332fee8e | ||
|
|
7be16a8906 | ||
|
|
86bace4d13 | ||
|
|
22746c3ca9 | ||
|
|
0bbb048c59 | ||
|
|
c1ba65783d | ||
|
|
51e3f47bac | ||
|
|
94ccc00f95 | ||
|
|
f45322905f | ||
|
|
c556dbb39c | ||
|
|
690e73eab1 | ||
|
|
6808620083 | ||
|
|
1095a33a44 | ||
|
|
43511b0a49 | ||
|
|
3d18a5acc5 | ||
|
|
2b57e1ec48 | ||
|
|
7cf91d923e | ||
|
|
de9afd3ece | ||
|
|
898f53428f | ||
|
|
3bd39de728 | ||
|
|
4ae3e4aa20 | ||
|
|
1bd7b717d0 | ||
|
|
fa9e1be4e2 | ||
|
|
15f82f100c | ||
|
|
cc18004c41 | ||
|
|
227ceccb8f | ||
|
|
83a5370bad | ||
|
|
2348d3c8f3 | ||
|
|
d7e649c44e | ||
|
|
b10fc8c720 | ||
|
|
4547b81d68 | ||
|
|
aacaf792ef | ||
|
|
ecf1fae8ff | ||
|
|
13d0cb725e | ||
|
|
33581b5be9 | ||
|
|
00fdf1f3d3 | ||
|
|
ca07edc707 | ||
|
|
d52a4ec081 | ||
|
|
a7a4838b96 | ||
|
|
6850cd6764 | ||
|
|
c860ad26e2 | ||
|
|
8423ef6371 | ||
|
|
a1b98308d4 | ||
|
|
1284f464c1 | ||
|
|
bc5b37cce5 | ||
|
|
82c398dee0 | ||
|
|
c4974bbf5a | ||
|
|
465a38163b | ||
|
|
b28c707cb5 | ||
|
|
eaebd04bbb | ||
|
|
da56c5b44b | ||
|
|
2b55ed078b | ||
|
|
4358058696 | ||
|
|
d49a8f8717 | ||
|
|
59a05a8c71 | ||
|
|
e0bd274e0d | ||
|
|
4747c3dc68 | ||
|
|
3c71a2cbf8 | ||
|
|
678fc3b40c | ||
|
|
ae4f1b8672 | ||
|
|
32803b1b67 | ||
|
|
4c69defb42 | ||
|
|
b7118be391 | ||
|
|
05b175b5ef | ||
|
|
dce9892f61 | ||
|
|
a74e59baf4 | ||
|
|
bc58fdaf39 | ||
|
|
92f30181d5 | ||
|
|
49db56856d | ||
|
|
87e1fb0b1e | ||
|
|
ad41b4e377 | ||
|
|
09e896dc26 | ||
|
|
7b8cb73115 | ||
|
|
e3c27678b6 | ||
|
|
96b0018adf | ||
|
|
8e58cb0dc6 | ||
|
|
937f034a1b | ||
|
|
536c309967 | ||
|
|
dea21c27c5 | ||
|
|
8b5187bff4 | ||
|
|
236557ab9e | ||
|
|
99b2adb646 |
No files matched your search
+20
-1
@@ -1,4 +1,23 @@
|
||||
[*.{cs,vb}]
|
||||
# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
||||
[*.cs]
|
||||
|
||||
# IDE0320: 使匿名函数成为静态函数
|
||||
csharp_prefer_static_anonymous_function = false
|
||||
# CS8618: 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 "required" 修饰符或声明为可为 null。
|
||||
dotnet_diagnostic.CS8618.severity = suggestion
|
||||
# IDE0305: 使用 [.. ] 简化集合初始化
|
||||
dotnet_diagnostic.IDE0305.severity = none
|
||||
# CS8524: switch 表达式不会处理其输入类型的某些值(它不是穷举),这包括未命名的枚举值。
|
||||
dotnet_diagnostic.CS8524.severity = suggestion
|
||||
|
||||
[*.{cs,vb}]
|
||||
|
||||
# IDE0028: 简化集合初始化
|
||||
dotnet_style_collection_initializer = true:silent
|
||||
# IDE0130: 命名空间与文件夹结构不匹配
|
||||
dotnet_diagnostic.IDE0130.severity = silent
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: ['https://afdian.com/a/LTCat']
|
||||
custom: ['https://meloong.com/afd/a/LTCat']
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "Minecraft 崩溃"
|
||||
description: "PCL 提示 “Minecraft 出现错误”,或游戏崩溃"
|
||||
labels: [· Bug, 新反馈]
|
||||
labels: [· Bug, 新提交]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: "yml-1"
|
||||
@@ -8,11 +8,11 @@ body:
|
||||
label: "检查项"
|
||||
description: "请逐个检查下列项目,并勾选确认。"
|
||||
options:
|
||||
- label: "**我所启动的游戏不是整合包,且未安装任何 Mod。** 安装 Mod 后的游戏崩溃基本不是 PCL 的原因,请 **不要** 提交反馈。你可以在论坛或社区找人求助,但这里并不是你求助的地方。"
|
||||
- label: "**我所启动的游戏不是整合包,且未安装任何 Mod。** 安装 Mod 后的崩溃基本不是 PCL 导致的,请 **不要** 提交反馈。你可以前往 [PCL 汉医堂](https://qm.qq.com/q/JJTzwEAmcw) 等民间 QQ 群求助,但这里不是求助的地方……"
|
||||
required: false
|
||||
- label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)"
|
||||
required: true
|
||||
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
|
||||
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "yml-2"
|
||||
@@ -36,4 +36,4 @@ body:
|
||||
description: "在 HMCL 中进入版本列表,点击版本右侧的三个点,选择 生成启动脚本。"
|
||||
placeholder: "先点击这个文本框,然后再将文件直接拖拽到文本框中以上传。"
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "下载或登录时卡住或失败"
|
||||
description: "下载游戏、Mod 或整合包,或登录正版账号时出现问题"
|
||||
labels: [· Bug, 新反馈]
|
||||
labels: [· Bug, 新提交]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: "yml-1"
|
||||
@@ -12,7 +12,7 @@ body:
|
||||
required: false
|
||||
- label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。"
|
||||
required: true
|
||||
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
|
||||
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "yml-2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "综合 Bug 反馈"
|
||||
description: "遇见了没有细分选项的其他 Bug"
|
||||
labels: [· Bug, 新反馈]
|
||||
labels: [· Bug, 新提交]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: "yml-1"
|
||||
@@ -8,7 +8,7 @@ body:
|
||||
label: "检查项"
|
||||
description: "请逐个检查下列项目,并勾选确认。"
|
||||
options:
|
||||
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
|
||||
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。"
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "yml-2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "优化建议"
|
||||
description: "对已有功能的小幅度优化或改进建议"
|
||||
labels: [· 优化, 新反馈]
|
||||
labels: [· 优化, 新提交]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: "yml-1"
|
||||
@@ -8,9 +8,9 @@ body:
|
||||
label: "检查项"
|
||||
description: "请逐个检查下列项目,并勾选确认。"
|
||||
options:
|
||||
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一建议未被提交过。"
|
||||
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一建议未被提交过。"
|
||||
required: true
|
||||
- label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。"
|
||||
- label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。"
|
||||
required: true
|
||||
- type: textarea
|
||||
id: "yml-2"
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 主页预设反馈
|
||||
url: https://github.com/Meloong-Git/PCL/discussions/categories/自定义主页
|
||||
about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈
|
||||
- name: 帮助文档反馈
|
||||
url: https://github.com/LTCatt/PCL2Help/issues
|
||||
about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈
|
||||
- name: 提问
|
||||
url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE
|
||||
url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE
|
||||
about: 我想问一些 PCL 相关的问题……
|
||||
- name: 讨论
|
||||
url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E8%AE%A8%E8%AE%BA
|
||||
url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E8%AE%A8%E8%AE%BA
|
||||
about: 我想讨论一些 PCL 相关的事情……
|
||||
@@ -1,6 +1,6 @@
|
||||
name: "新功能提案"
|
||||
description: "对已有功能的大幅度修改,或添加一个新内容或选项"
|
||||
labels: [· 新功能, 新反馈]
|
||||
labels: [· 新功能, 新提交]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: "yml-1"
|
||||
@@ -8,9 +8,9 @@ body:
|
||||
label: "检查项"
|
||||
description: "请逐个检查下列项目,并勾选确认。"
|
||||
options:
|
||||
- label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一提案未被提交过。"
|
||||
- label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一提案未被提交过。"
|
||||
required: true
|
||||
- label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。"
|
||||
- label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。"
|
||||
required: true
|
||||
- label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……"
|
||||
required: true
|
||||
|
||||
+35
-14
@@ -1,5 +1,4 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
@@ -12,37 +11,59 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Debug, Release, Snapshot, BETA, ReleaseUpdate]
|
||||
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [Debug, Release]
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-depth: 1
|
||||
submodules: true
|
||||
|
||||
- name: Set Describe
|
||||
shell: bash
|
||||
run: |
|
||||
Describe=`git describe --tags --always`
|
||||
echo "Describe=$Describe" >> $GITHUB_ENV
|
||||
describe=`git describe --tags --always`
|
||||
echo "describe=$describe" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
with:
|
||||
msbuild-architecture: x64
|
||||
|
||||
- name: Update Help
|
||||
run: |
|
||||
Set-Location ..
|
||||
git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1
|
||||
Set-Location PCL2Help
|
||||
Remove-Item -Path *.* -Recurse -Force
|
||||
Compress-Archive -Path .\* -DestinationPath .\Help.zip
|
||||
Copy-Item -Path .\Help.zip -Destination "${{ GITHUB.WORKSPACE }}\Plain Craft Launcher 2\Resources\Help.zip" -Force
|
||||
Set-Location ..
|
||||
Remove-Item PCL2Help -Recurse -Force
|
||||
Set-Location ${{ GITHUB.WORKSPACE }}
|
||||
|
||||
|
||||
- name: Replace
|
||||
run: |
|
||||
(gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")', 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "${{ secrets.CLIENT_ID }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
|
||||
(gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")', 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "${{ secrets.CURSEFORGE_API_KEY }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb"
|
||||
(gc "Plain Craft Launcher 2\Modules\Base\ModBase.vb") -replace 'Public Const CommitHash As String = ""', 'Public Const CommitHash As String = "${{ github.sha }}"' | Out-File "Plain Craft Launcher 2\Modules\Base\ModBase.vb"
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: dotnet restore PCLCS\PCLCS.csproj
|
||||
|
||||
- name: Build
|
||||
run: msbuild "Plain Craft Launcher 2\Plain Craft Launcher 2.vbproj" -p:Configuration=${{ matrix.configuration }}
|
||||
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.configuration }} ${{ env.Describe }}
|
||||
name: ${{ matrix.configuration }} ${{ env.describe }}
|
||||
path: Plain Craft Launcher 2\obj\${{ matrix.configuration }}\Plain Craft Launcher 2.exe
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "MeloongCore"]
|
||||
path = MeloongCore
|
||||
url = https://github.com/Meloong-Git/MeloongCore.git
|
||||
@@ -1,32 +1,50 @@
|
||||
本文件仅作为指引您合理使用本存储库内容的指南,不是法律文件或协议。
|
||||
《PCL 分发有限许可》
|
||||
|
||||
一、承诺与声明。
|
||||
详见 https://shimo.im/docs/rGrd8pY8xWkt6ryW#anchor-X0bo
|
||||
|
||||
开发者承诺,在您遵守本指南的情况下,不会追究您的责任,但最终裁量权仍由开发者保留。
|
||||
法律上,开发者仍保留所有权利(All Rights Reserved),且可能随时修改本指南,本指南并不是永久性保证。
|
||||
————————————————————————————————————
|
||||
|
||||
二、少量使用情形。
|
||||
《PCL 存储库合理使用指南》
|
||||
|
||||
如果您仅合理使用本存储库中的少量内容,或是参考实现思路,您必须:
|
||||
1、给出适当的署名,且提供指向本存储库的链接。
|
||||
2、不得以任何方式暗示开发者为您或您的使用作担保。
|
||||
本指南仅作为说明,不是法律文件或协议。
|
||||
除《PCL 分发有限许可》明确授予的权利外,开发者保留软件及其相关知识产权中的全部权利。但开发者承诺,在遵守本指南,且善意、合理使用的情况下,不会追究责任。
|
||||
|
||||
三、大量使用情形。
|
||||
一、轻度使用
|
||||
|
||||
如果您基于该存储库创作衍生作品,或合理使用了本存储库中的较大量内容,您必须:
|
||||
1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。
|
||||
2、不得以任何方式暗示开发者为您或您的使用作担保,且需要给出适当的说明,表示这是非官方内容。
|
||||
3、使用本指南文件作为协议,不得附加额外条款。
|
||||
4、该衍生作品不能被允许用于再次分发。包括但不限于用于制作整合包。
|
||||
5、该衍生作品的名称必须以 “PCL” 或 “Plain Craft Launcher” 开头,并在其后添加后缀,例如 “PCL 启动修复版”、“PCL PR Collection”。
|
||||
6、该衍生作品不得包含 PCL 的赞助功能,或是其他作用相似的功能。包括但不限于主题功能、回声洞投稿功能等。
|
||||
若您仅参考了本存储库中极小部分的内容,或您在制作与 PCL 的实质功能不存在任何重叠的软件,则为轻度使用。
|
||||
其常见情形例如:
|
||||
· 使用或参考存储库中极小部分的内容,来开发启动器或其他软件。
|
||||
· 虽参考了较大量的内容,但制作的软件与 PCL 的实质功能不存在任何重叠。
|
||||
|
||||
————————————
|
||||
若属于轻度使用,您需要:
|
||||
1、以适当的形式给出署名。
|
||||
2、不得将您的内容与 PCL 混淆,或暗示您的内容与 PCL 有关。
|
||||
例如:您的内容不得使用 Potato Craft Launcher、PCL 手机版 等会造成混淆或暗示这与 PCL 有关的名称。
|
||||
|
||||
二、重度使用
|
||||
|
||||
若不符合轻度使用的情形,即为重度使用。
|
||||
其常见情形例如:
|
||||
· 基于本存储库进行修改。
|
||||
· 参考存储库中较大量的内容,以制作或重新实现 PCL 的部分实质功能(例如启动或下载 Java 版 Minecraft,下载或管理 Mod 等)。
|
||||
|
||||
若属于重度使用,您需要:
|
||||
1、明确表示您的内容是第三方基于 PCL 独立进行二次创作的产物。
|
||||
2、您的内容的名称必须以 Plain Craft Launcher (PCL) 开头,并在其后添加足以表现其由第三方修改的后缀。
|
||||
允许的示例:PCL 某服务器魔改版、PCL PR Collection、PCL (XX Bug Fix)、PCL XXX修改版。
|
||||
禁止的示例:PCL 某服务器特供版(暗示由官方提供)、PCL 3、PCL 手机版、PCL++、PCL Pro(这像是官方的后续作品)。
|
||||
3、在关于页面的首位给出龙腾猫跃的署名及对应的赞助链接,公开源代码,且继续使用本文件作为指南(可参考 CC BY-SA 协议)。
|
||||
4、软件若有 Minecraft 启动功能,则应实现与 McLaunchPrecheck 函数一致(或更激进)的 Minecraft 正版购买劝导与赞助劝导。
|
||||
5、软件在 Windows 系统上运行时,不应有与 PCL 的赞助解锁功能类似的功能或表现。
|
||||
例如:界面的主要色相必须为蓝色(即使没有修改功能,改为其他色相也和需要赞助解锁的主题功能类似)。
|
||||
|
||||
————————————————————————————————————
|
||||
|
||||
说人话的备注:
|
||||
|
||||
因为没有法律专业人士检查,所以这不敢作为正式的法律协议,如果有专业人士愿意帮忙改一改我也很乐意……
|
||||
这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
|
||||
这些规则主要是为了阻止恶意的使用、倒卖和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
|
||||
|
||||
如果你只是想参考一下,或者用了一小部分代码,署个名就行啦。
|
||||
如果你整了个魔改版,或者用了一大堆代码,请仔细参考上面的第三条。
|
||||
如果你只是参考了一小段代码,署个名就行啦,不用担心。
|
||||
如果对具体细节有疑问,或是希望修改这些规则,欢迎在 https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE 发帖!
|
||||
|
||||
多谢大家啦!
|
||||
Submodule
+1
Submodule MeloongCore added at 98157d65c7.
@@ -0,0 +1,246 @@
|
||||
# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行
|
||||
root = true
|
||||
|
||||
# c# 文件
|
||||
[*.cs]
|
||||
|
||||
#### Core EditorConfig 选项 ####
|
||||
|
||||
# 缩进和间距
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# 新行首选项
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET 代码操作 ####
|
||||
|
||||
# 类型成员
|
||||
dotnet_hide_advanced_members = false
|
||||
dotnet_member_insertion_location = with_other_members_of_the_same_kind
|
||||
dotnet_property_generation_behavior = prefer_throwing_properties
|
||||
|
||||
# 符号搜索
|
||||
dotnet_search_reference_assemblies = true
|
||||
|
||||
#### .NET 编码约定 ####
|
||||
|
||||
# 组织 Using
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
file_header_template = unset
|
||||
|
||||
# this. 和 Me. 首选项
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# 语言关键字与 bcl 类型首选项
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# 括号首选项
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||
|
||||
# 修饰符首选项
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||
|
||||
# 表达式级首选项
|
||||
dotnet_prefer_system_hash_code = true
|
||||
dotnet_style_coalesce_expression = true
|
||||
dotnet_style_collection_initializer = true
|
||||
dotnet_style_explicit_tuple_names = true
|
||||
dotnet_style_namespace_match_folder = true
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_object_initializer = true
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true:suggestion
|
||||
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_style_prefer_inferred_tuple_names = true
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
|
||||
# 字段首选项
|
||||
dotnet_style_readonly_field = true
|
||||
|
||||
# 参数首选项
|
||||
dotnet_code_quality_unused_parameters = all
|
||||
|
||||
# 禁止显示首选项
|
||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
|
||||
# 新行首选项
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||
|
||||
#### c# 编码约定 ####
|
||||
|
||||
# var 首选项
|
||||
csharp_style_var_elsewhere = false
|
||||
csharp_style_var_for_built_in_types = false
|
||||
csharp_style_var_when_type_is_apparent = false
|
||||
|
||||
# Expression-bodied 成员
|
||||
csharp_style_expression_bodied_accessors = true
|
||||
csharp_style_expression_bodied_constructors = false
|
||||
csharp_style_expression_bodied_indexers = true
|
||||
csharp_style_expression_bodied_lambdas = true
|
||||
csharp_style_expression_bodied_local_functions = false
|
||||
csharp_style_expression_bodied_methods = false
|
||||
csharp_style_expression_bodied_operators = false
|
||||
csharp_style_expression_bodied_properties = true
|
||||
|
||||
# 模式匹配首选项
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_extended_property_pattern = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_prefer_pattern_matching = true:suggestion
|
||||
csharp_style_prefer_switch_expression = true
|
||||
|
||||
# Null 检查首选项
|
||||
csharp_style_conditional_delegate_call = true
|
||||
|
||||
# 修饰符首选项
|
||||
csharp_prefer_static_anonymous_function = true
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
|
||||
csharp_style_prefer_readonly_struct = true
|
||||
csharp_style_prefer_readonly_struct_member = true
|
||||
|
||||
# 代码块首选项
|
||||
csharp_prefer_braces = true
|
||||
csharp_prefer_simple_using_statement = true
|
||||
csharp_prefer_system_threading_lock = true
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
csharp_style_prefer_method_group_conversion = true
|
||||
csharp_style_prefer_primary_constructors = true
|
||||
csharp_style_prefer_top_level_statements = true
|
||||
|
||||
# 表达式级首选项
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_deconstructed_variable_declaration = true
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_style_prefer_implicitly_typed_lambda_expression = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_local_over_anonymous_function = true
|
||||
csharp_style_prefer_null_check_over_type_check = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_prefer_tuple_swap = true
|
||||
csharp_style_prefer_unbound_generic_type_in_nameof = true
|
||||
csharp_style_prefer_utf8_string_literals = true
|
||||
csharp_style_throw_expression = true
|
||||
csharp_style_unused_value_assignment_preference = discard_variable
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
|
||||
# "using" 指令首选项
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
|
||||
# 新行首选项
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
|
||||
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||
|
||||
#### C# 格式规则 ####
|
||||
|
||||
# 新行首选项
|
||||
csharp_new_line_before_catch = false
|
||||
csharp_new_line_before_else = false
|
||||
csharp_new_line_before_finally = false
|
||||
csharp_new_line_before_members_in_anonymous_types = false
|
||||
csharp_new_line_before_members_in_object_initializers = false
|
||||
csharp_new_line_before_open_brace = none
|
||||
csharp_new_line_between_query_expression_clauses = false
|
||||
|
||||
# 缩进首选项
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = false
|
||||
csharp_indent_labels = flush_left
|
||||
csharp_indent_switch_labels = false
|
||||
|
||||
# 空格键首选项
|
||||
csharp_space_after_cast = true
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = ignore
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# 包装首选项
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### 命名样式 ####
|
||||
|
||||
# 命名规则
|
||||
|
||||
dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始
|
||||
|
||||
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion
|
||||
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型
|
||||
dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
|
||||
|
||||
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion
|
||||
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员
|
||||
dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法
|
||||
|
||||
# 符号规范
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.类型.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.类型.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.非字段成员.required_modifiers =
|
||||
|
||||
# 命名样式
|
||||
|
||||
dotnet_naming_style.帕斯卡拼写法.required_prefix =
|
||||
dotnet_naming_style.帕斯卡拼写法.required_suffix =
|
||||
dotnet_naming_style.帕斯卡拼写法.word_separator =
|
||||
dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.以_i_开始.required_prefix = I
|
||||
dotnet_naming_style.以_i_开始.required_suffix =
|
||||
dotnet_naming_style.以_i_开始.word_separator =
|
||||
dotnet_naming_style.以_i_开始.capitalization = pascal_case
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace PCLCS;
|
||||
public static class Configs {
|
||||
|
||||
public static readonly DynamicConfigEntry<McInstallManifest> McInstallManifest = new("MinecraftInstallManifest", null);
|
||||
|
||||
// Java
|
||||
|
||||
/// <summary> 当前 Java 相关配置的版本。0 代表尚未从老的 Settings 系统中迁移。</summary>
|
||||
public static readonly ConfigEntry<int> JavaConfigVersion = new("JavaConfigVersion", 0);
|
||||
public static readonly ConfigEntry<ConcurrentList<Java>> JavaList = new("JavaList", []);
|
||||
public static readonly ConfigEntry<List<string>> JavaRemovedList = new("JavaRemovedList", []);
|
||||
public static readonly DynamicConfigEntry<bool> JavaMigrated = new("InstanceMigratedJava", false);
|
||||
public static readonly DynamicConfigEntry<Java> JavaForced = new("InstanceForcedJava", null);
|
||||
|
||||
// 提示
|
||||
|
||||
public static readonly ConfigEntry<bool> HintModifyInstanceWarning = new("HintModifyInstanceWarning", false);
|
||||
|
||||
static Configs() {
|
||||
/// 当 JavaForced 获取到一个不在 JavaList 中的值时,将其重置为 null。
|
||||
JavaForced.PreviewGet += (args, provider) => {
|
||||
if (args.Value is not null && !JavaList.Get()!.Contains(args.Value)) {
|
||||
args.Value = null;
|
||||
JavaForced.Reset(provider);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
namespace PCLCS;
|
||||
|
||||
/// <summary>
|
||||
/// 社区资源的类型。
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ResourceTypes {
|
||||
/// <summary>Mod。</summary>
|
||||
Mod = 1,
|
||||
/// <summary>整合包。</summary>
|
||||
ModPack = 2,
|
||||
/// <summary>资源包。</summary>
|
||||
ResourcePack = 4,
|
||||
/// <summary>光影包。</summary>
|
||||
Shader = 8,
|
||||
/// <summary>数据包。</summary>
|
||||
DataPack = 16,
|
||||
/// <summary>服务端插件。</summary>
|
||||
Plugin = 32,
|
||||
/// <summary>地图。</summary>
|
||||
Map = 64,
|
||||
/// <summary>同时包含数据包以及 Mod。</summary>
|
||||
ModOrDataPack = Mod | DataPack,
|
||||
/// <summary>允许任意种类,或种类未知。</summary>
|
||||
Any = Mod | ModPack | ResourcePack | Shader | DataPack | Plugin | Map,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 社区资源的来源平台。
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ResourcePlatforms {
|
||||
CurseForge = 1,
|
||||
Modrinth = 2,
|
||||
Any = CurseForge | Modrinth,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mod 加载器的类型。
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ModLoaders {
|
||||
None = 0,
|
||||
Forge = 1,
|
||||
LiteLoader = 2,
|
||||
Fabric = 4,
|
||||
NeoForge = 16,
|
||||
All = Forge | LiteLoader | Fabric | NeoForge,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在爱发电中的赞助等级。
|
||||
/// </summary>
|
||||
public enum DonationRank {
|
||||
None = 0,
|
||||
Rank6 = 6,
|
||||
Rank12 = 12,
|
||||
Rank23 = 23,
|
||||
Rank54 = 54,
|
||||
Rank98 = 98,
|
||||
}
|
||||
|
||||
public static class Versions {
|
||||
/// <summary>
|
||||
/// 土豆码版本号。
|
||||
/// </summary>
|
||||
public const char PotatoVersion = '1';
|
||||
/// <summary>
|
||||
/// 版本管理中 Mod 信息缓存的版本号。
|
||||
/// </summary>
|
||||
public const int LocalModCacheVersion = 21;
|
||||
/// <summary>
|
||||
/// Minecraft 本地实例信息缓存的版本号。
|
||||
/// </summary>
|
||||
public const int McInstanceCacheVersion = 39;
|
||||
/// <summary>
|
||||
/// Java 相关配置的版本号。
|
||||
/// </summary>
|
||||
public const int JavaConfigVersion = 1;
|
||||
/// <summary>
|
||||
/// Minecraft 安装清单的版本号。
|
||||
/// </summary>
|
||||
public const int McInstallManifestVersion = 1;
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
namespace PCLCS;
|
||||
|
||||
/// <summary>
|
||||
/// 单个 Java 的实例。
|
||||
/// </summary>
|
||||
[Serializable] public class Java(string folder, Version? version = null) : IEquatable<Java> {
|
||||
|
||||
/// <summary>
|
||||
/// java.exe 文件所在的文件夹路径,这通常是 bin 文件夹。以 \ 结尾。
|
||||
/// </summary>
|
||||
public readonly string Folder = PathUtils.AddSlashSuffix(PathUtils.ForCompare(folder));
|
||||
/// <summary>
|
||||
/// java.exe 文件的完整路径。
|
||||
/// </summary>
|
||||
[JsonIgnore] public string JavaExePath => Path.Combine(Folder, "java.exe");
|
||||
|
||||
/// <summary>
|
||||
/// Java 的版本号(例如 16.0.1),保证 <see cref="Version.Major"/> 为其主版本。
|
||||
/// <para/> 若为 null,则还需要调用 <see cref="CheckAsync"/> 以获取版本号。
|
||||
/// </summary>
|
||||
public Version? Version = version;
|
||||
/// <summary>Java 的默认字符编码。</summary>
|
||||
[JsonIgnore] public string? FileEncodingName;
|
||||
/// <summary>
|
||||
/// Java 根据系统环境确定的本地字符编码,仅 Java 17+ 可用。
|
||||
/// <para/>若系统开启了 UTF-8,部分 Java 会错误地返回 JDK,因此不能使用 <see cref="Encoding.Default"/>。
|
||||
/// </summary>
|
||||
[JsonIgnore] public string? NativeEncodingName;
|
||||
|
||||
/// <summary>
|
||||
/// 检查该 Java 是否存在问题,并获取其版本号。
|
||||
/// 返回其是否通过了检查。
|
||||
/// </summary>
|
||||
public async Task<bool> CheckAsync(CancellationToken cancellationToken = default) {
|
||||
if (Interlocked.Exchange(ref isChecked, 1) == 1 && available != null) return available!.Value; // 已检查过
|
||||
string? output = null;
|
||||
try {
|
||||
if (!FileUtils.Exists(JavaExePath)) throw new FileNotFoundException("未找到 java.exe 文件", JavaExePath);
|
||||
|
||||
// 运行 -version
|
||||
output = (await TaskUtils.RunProgramAsync(JavaExePath, "-XshowSettings:properties -version", 15000, cancellationToken: cancellationToken).NoCapture()).Output.Lower();
|
||||
if (output == "") throw new InvalidOperationException("尝试运行该 Java 失败");
|
||||
Logger.Trace($"Java 检查输出:{JavaExePath}{Environment.NewLine}{output}");
|
||||
if (output.Contains("/lib/ext exists")) throw new InvalidOperationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试");
|
||||
if (output.Contains("a fatal error") || output.Contains("error: ")) throw new InvalidOperationException("无法运行该 Java,该 Java 或系统存在问题");
|
||||
|
||||
// 获取版本号
|
||||
var versionString = (output.RegexSeek(@"(?<=version "")[^""]+") ?? output.RegexSeek("(?<=openjdk )[0-9]+"))?.Replace("_", ".").Replace("+", ".").BeforeFirst("-");
|
||||
if (string.IsNullOrEmpty(versionString)) throw new InvalidOperationException($"未找到该 Java 的版本号{(output.Length < 500 ? $"\n原始输出为:\n{output}" : "")}");
|
||||
if (versionString.StartsWithF("1.")) versionString = versionString!.AfterFirst("."); // 去除开头的 1.
|
||||
var segments = versionString!.Split(".", true).ToList();
|
||||
while (segments.Count < 4) segments.Add("0");
|
||||
Version = new Version(segments.Take(4).Join("."));
|
||||
if (Version.Major is <= 4 or >= 100) throw new InvalidOperationException($"分析详细信息失败,获取的版本为 {Version}{(output.Length < 500 ? $"\n原始输出为:\n{output}" : "")}");
|
||||
|
||||
// 获取 Java 的相关属性
|
||||
if (!output.Contains("64-bit")) throw new InvalidOperationException("该 Java 为 32 位版本,请安装 64 位的 Java");
|
||||
string? GetProperty(string name) {
|
||||
string prefix = name + " = ";
|
||||
string? line = output.Split('\n').Select(l => l.Trim()).FirstOrDefault(l => l.StartsWithF(prefix));
|
||||
return line?[prefix.Length..].Trim();
|
||||
}
|
||||
FileEncodingName = GetProperty("file.encoding");
|
||||
NativeEncodingName = GetProperty("native.encoding") ?? FileEncodingName; // 旧版本没有 native.encoding
|
||||
|
||||
Logger.Info($"检查 Java 成功:{this}");
|
||||
available = true;
|
||||
} catch (Exception ex) {
|
||||
if (ex.IsCanceled()) {
|
||||
isChecked = 0;
|
||||
throw;
|
||||
}
|
||||
Logger.Warn($"检查 Java 失败({JavaExePath}),其输出为:\n{output ?? "无程序输出"}");
|
||||
available = false;
|
||||
}
|
||||
return available!.Value;
|
||||
}
|
||||
|
||||
public void InvalidateChecked() {
|
||||
isChecked = 0;
|
||||
available = null;
|
||||
}
|
||||
|
||||
[JsonIgnore] private int isChecked = 0;
|
||||
/// <summary>若已进行过检查,表示该检查是否成功。若未检查,则为 null。</summary>
|
||||
[JsonIgnore] internal bool? available = null;
|
||||
|
||||
// 相同检查
|
||||
public bool Equals(Java? other)
|
||||
=> other is not null && PathUtils.IsSame(Folder, other.Folder);
|
||||
public override bool Equals(object? obj) => obj is Java other && Equals(other);
|
||||
public override int GetHashCode() => StringComparer.CurrentCultureIgnoreCase.GetHashCode(PathUtils.ForCompare(Folder));
|
||||
|
||||
/// <summary>用户友好的描述文本。</summary>
|
||||
public override string ToString()
|
||||
=> $"Java {Version?.Major.ToString() ?? "尚未检查"} ({Version}):{Folder}";
|
||||
}
|
||||
|
||||
public static class JavaUtils {
|
||||
|
||||
/// <summary>
|
||||
/// 检查所有指定的 Java 是否存在问题,并获取其版本号。
|
||||
/// </summary>
|
||||
/// <returns>所有通过检查的 Java。</returns>
|
||||
public static async Task<List<Java>> CheckAllAsync(IEnumerable<Java> javas, CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
|
||||
await TaskUtils.WhenAll(javas.Where(j => j.available == null).Select(
|
||||
async java => await java.CheckAsync(cancellationToken).NoCapture()
|
||||
), progress).NoCapture();
|
||||
return javas.Where(j => j.available == true).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Java 列表进行排序。
|
||||
/// 这同时会检查所有未检查的 Java。
|
||||
/// </summary>
|
||||
public static async Task<List<Java>> SortAsync(List<Java> javas, CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
|
||||
javas = await JavaUtils.CheckAllAsync(javas, cancellationToken, progress).NoCapture();
|
||||
javas.Sort((left, right) => {
|
||||
// 优先使用带完整 Java 的文件夹列表中的 Java
|
||||
var isLeftInCandidateFolder = JavaUtils.CandidateFolders.Value.Any(folder => PathUtils.IsParentOf(folder, left.Folder));
|
||||
var isRightInCandidateFolder = JavaUtils.CandidateFolders.Value.Any(folder => PathUtils.IsParentOf(folder, right.Folder));
|
||||
if (isLeftInCandidateFolder != isRightInCandidateFolder) return isLeftInCandidateFolder ? -1 : 1;
|
||||
// 其次优先使用主版本号接近 21 的 Java
|
||||
var leftDistance = left.Version is null ? int.MaxValue : Math.Abs(left.Version.Major - 21);
|
||||
var rightDistance = right.Version is null ? int.MaxValue : Math.Abs(right.Version.Major - 21);
|
||||
return leftDistance.CompareTo(rightDistance);
|
||||
});
|
||||
return javas;
|
||||
}
|
||||
|
||||
#region 搜索
|
||||
|
||||
/// <summary>
|
||||
/// 存在完整 Java 的文件夹列表。
|
||||
/// </summary>
|
||||
internal static readonly Lazy<List<string>> CandidateFolders = new(() => {
|
||||
List<string> folders = [
|
||||
Paths.AppData + @".minecraft\runtime\", // 这也是 PCL 下载 Java 的路径
|
||||
Paths.AppData + @".hmcl\java\",
|
||||
Paths.AppData + @"ATLauncher\runtimes\minecraft\",
|
||||
Paths.AppData + @"ModrinthApp\meta\java_versions\",
|
||||
Paths.AppData + @"PrismLauncher\java\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\curseforge\minecraft\Install\runtime\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.jdks\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + @"\.sdkman\candidates\java\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\.ftba\bin\runtime\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Packages\Microsoft.4297127D64EC6_8wekyb3d8bbwe\LocalCache\Local\runtime\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + @"\Minecraft Launcher\runtime\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + @"\Minecraft\runtime\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\Curse\Minecraft\Install\runtime\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Java\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Eclipse Adoptium\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Amazon Corretto\",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Zulu\",
|
||||
..DirectoryUtils.EnumerateDirectories(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\Microsoft\", searchPattern: "jdk-*"),
|
||||
// 环境变量
|
||||
..(Environment.GetEnvironmentVariable("JDK_HOME") + ";" + Environment.GetEnvironmentVariable("JAVA_HOME")).
|
||||
Split(";", true).Select(path => path.Trim(' ', '"'))
|
||||
];
|
||||
return folders.Select(f => {
|
||||
try {
|
||||
return PathUtils.ForCompare(f);
|
||||
} catch (Exception ex) {
|
||||
Logger.Warn(ex, $"Java 候选文件夹路径无效({f})");
|
||||
return null;
|
||||
}
|
||||
}).Where(f => f is not null).Cast<string>().ToList();
|
||||
});
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 从常见文件夹和环境变量中搜索 Java,并将搜索结果写入设置。
|
||||
/// </summary>
|
||||
public static async Task RefreshListAsync(CancellationToken c = default, ProgressProvider? p = null) {
|
||||
// 搜索
|
||||
List<Java> javaList = await SearchFoldersAsync(true, CandidateFolders.Value, c, p?.SplitTo(0.5)).NoCapture();
|
||||
// 将新发现的 Java 排在最前,原有列表保留既有顺序排在后面
|
||||
var oldJavaList = Configs.JavaList.Get()!;
|
||||
oldJavaList = await JavaUtils.CheckAllAsync(oldJavaList, c, p?.SplitTo(0.9)).NoCapture(); // 检查原有列表中的 Java
|
||||
javaList.RemoveAll(oldJavaList); // 此时 javaList 中只剩下新发现的 Java
|
||||
javaList = await JavaUtils.SortAsync(javaList, c).NoCapture(); // 将所有新发现的 Java 按优先级排序
|
||||
javaList.AddRange(oldJavaList);
|
||||
javaList = javaList.Distinct().ToList();
|
||||
// 删掉已被移除的 Java
|
||||
javaList.RemoveIf(j => Configs.JavaRemovedList.Get()!.Contains(j.Folder, StringComparer.OrdinalIgnoreCase));
|
||||
// 写入设置
|
||||
Configs.JavaList.Set([..javaList]);
|
||||
Logger.Info($"Java 搜索完成,发现 {javaList.Count} 个 Java");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 并行搜索所有指定的文件夹及其子文件夹中的 Java,并检查其有效性。
|
||||
/// </summary>
|
||||
public static async Task<List<Java>> SearchFoldersAsync(bool includeSubDirectories, IEnumerable<string> folders, CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
|
||||
// 并行搜索
|
||||
ConcurrentBag<(string Folder, bool HasReparsePoint, bool IsSpecialPath)> results = [];
|
||||
await TaskUtils.ForEachAsync(folders.Distinct(StringComparer.OrdinalIgnoreCase), 4, async (folder, cp) => {
|
||||
await Task.Run(() => {
|
||||
try {
|
||||
foreach (var file in DirectoryUtils.EnumerateFiles(folder, includeSubDirectories, "java.exe")) {
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string targetFolder = PathUtils.AddSlashSuffix(PathUtils.ForCompare(PathUtils.RemoveLastPart(file)));
|
||||
// 判断文件夹是否包含重解析点(例如符号链接)
|
||||
static bool HasReparsePoint(FileSystemInfo info) {
|
||||
if (PathUtils.IsParentOf(Paths.AppData + @".minecraft\runtime\", info.FullName)) return false; // 不筛查 PCL 的下载路径(#8928)
|
||||
do {
|
||||
if (info.Attributes.HasFlagF(FileAttributes.ReparsePoint)) return true;
|
||||
info = info is FileInfo fileInfo ? fileInfo.Directory : ((DirectoryInfo) info).Parent;
|
||||
} while (info is not null);
|
||||
return false;
|
||||
}
|
||||
// 结果
|
||||
var hasReparsePoint = HasReparsePoint(FileUtils.GetInfo(Path.Combine(targetFolder, "java.exe")));
|
||||
var isSpecialPath = targetFolder.ContainsIgnoreCase("java8path_target_") || targetFolder.ContainsIgnoreCase("javapath_target_") || targetFolder.ContainsIgnoreCase("javatmp") || targetFolder.ContainsIgnoreCase("system32");
|
||||
results.Add((targetFolder, hasReparsePoint, isSpecialPath));
|
||||
}
|
||||
} catch (UnauthorizedAccessException) {
|
||||
Logger.Info($"快速查找 Java 时没有权限({folder})");
|
||||
} catch (Exception ex) {
|
||||
ex.ThrowIfCanceled();
|
||||
Logger.Warn(ex, $"快速查找 Java 时出错({folder})");
|
||||
}
|
||||
}, cancellationToken).NoCapture();
|
||||
}, cancellationToken, progress?.SplitTo(0.2)).NoCapture();
|
||||
if (!results.Any()) return [];
|
||||
|
||||
// 筛除重解析点
|
||||
if (results.All(r => r.HasReparsePoint)) {
|
||||
Logger.Warn("找到的所有 Java 都包含重解析点,保留全部结果");
|
||||
} else if (results.Any(r => r.HasReparsePoint)) {
|
||||
Logger.Warn($"找到的以下 Java 包含重解析点,将它们从列表中排除:{results.Where(r => r.HasReparsePoint).Select(r => r.Folder).Join('、')}");
|
||||
results = [..results.Where(r => !r.HasReparsePoint)];
|
||||
}
|
||||
// 筛除特殊路径
|
||||
if (results.All(r => r.IsSpecialPath)) {
|
||||
Logger.Warn("找到的所有 Java 都包含特殊路径,保留全部结果");
|
||||
} else if (results.Any(r => r.IsSpecialPath)) {
|
||||
Logger.Warn($"找到的以下 Java 包含特殊路径,将它们从列表中排除:{results.Where(r => r.IsSpecialPath).Select(r => r.Folder).Join('、')}");
|
||||
results = [..results.Where(r => !r.IsSpecialPath)];
|
||||
}
|
||||
|
||||
// 检查并返回
|
||||
List<Java> javaList = results.Select(r => r.Folder).Distinct(StringComparer.OrdinalIgnoreCase).Select(folder => new Java(folder)).ToList();
|
||||
javaList = (await JavaUtils.CheckAllAsync(javaList, cancellationToken, progress?.SplitTo(1)).NoCapture()).ToList();
|
||||
javaList = await JavaUtils.SortAsync(javaList, cancellationToken).NoCapture();
|
||||
Logger.Info($"快速查找 Java 完成,共有 {javaList.Count} 个候选:{javaList.Join('、')}");
|
||||
return javaList;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
namespace PCLCS;
|
||||
|
||||
public static class LaunchUtils {
|
||||
|
||||
/// <summary>
|
||||
/// 释放补丁文件并返回完整文件路径。
|
||||
/// </summary>
|
||||
/// <param name="patchFilePath">补丁文件路径
|
||||
/// <param name="outputDirectory">补丁文件的释放目录。</param>
|
||||
public static string ExtractPatch(string patchFilePath, string outputDirectory) {
|
||||
Logger.Info($"选定的资源 {patchFilePath} 输出路径:{outputDirectory}");
|
||||
lock (ExtractPatchLock) { // 避免 OptiFine 和 Forge 安装时同时释放导致冲突
|
||||
try {
|
||||
FileUtils.ExtractResources(patchFilePath, Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath)), typeof(LaunchUtils));
|
||||
} catch (Exception ex) {
|
||||
if (!FileUtils.Exists(outputDirectory)) throw new FileNotFoundException($"释放 {patchFilePath} 失败", ex);
|
||||
Logger.Warn(ex, $"{patchFilePath} 文件重新释放失败,将尝试更换文件名重新生成");
|
||||
patchFilePath = Path.Combine(outputDirectory, $"{PathUtils.GetFileNameWithoutExtension(patchFilePath)}2.{PathUtils.GetExtension(patchFilePath)}");
|
||||
try {
|
||||
FileUtils.ExtractResources(patchFilePath, Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath)), typeof(LaunchUtils));
|
||||
} catch (Exception ex2) {
|
||||
throw new FileNotFoundException($"释放 {patchFilePath} 最终尝试失败", ex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Path.Combine(outputDirectory, PathUtils.GetLastPart(patchFilePath));
|
||||
}
|
||||
private static readonly object ExtractPatchLock = new();
|
||||
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace PCLCS;
|
||||
|
||||
public static class McUtils {
|
||||
|
||||
/// <summary>
|
||||
/// 比较两个版本名;左侧较新返回正数,相同返回 0,右侧较新返回负数。
|
||||
/// 无法比较两个预发布版的大小。
|
||||
/// 支持的格式:未知版本, 26.1-snapshot-1,1.13.2, 1.7.10-pre4, 1.8_pre, 1.14 Pre-Release 2, 1.14.4 C6
|
||||
/// </summary>
|
||||
public static int CompareVersion(string left, string right) {
|
||||
if (left == right) return 0;
|
||||
if (left == "未知版本" || right == "未知版本") return left == "未知版本" ? 1 : -1;
|
||||
left = left.Lower();
|
||||
right = right.Lower();
|
||||
var leftParts = left.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList();
|
||||
var rightParts = right.Replace("快照", "snapshot").Replace("预览版", "pre").RegexSearch("[a-z]+|[0-9]+").ToList();
|
||||
for (int i = 0; ; i++) {
|
||||
if (i >= leftParts.Count && i >= rightParts.Count) return string.Compare(left, right, StringComparison.Ordinal); // 两边均缺失,感觉是一个东西
|
||||
string leftValue = i < leftParts.Count ? leftParts[i] : "0";
|
||||
string rightValue = i < rightParts.Count ? rightParts[i] : "0";
|
||||
if (leftValue == rightValue) continue;
|
||||
int leftNumber = leftValue switch {
|
||||
"rc" => -1,
|
||||
"pre" => -2,
|
||||
"snapshot" => -3,
|
||||
"experimental" => -4,
|
||||
_ => int.TryParse(leftValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int leftResult) ? leftResult : 0
|
||||
};
|
||||
int rightNumber = rightValue switch {
|
||||
"rc" => -1,
|
||||
"pre" => -2,
|
||||
"snapshot" => -3,
|
||||
"experimental" => -4,
|
||||
_ => int.TryParse(rightValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out int rightResult) ? rightResult : 0
|
||||
};
|
||||
if (leftNumber == 0 && rightNumber == 0) return string.Compare(leftValue, rightValue, StringComparison.Ordinal);
|
||||
if (leftNumber != rightNumber) return leftNumber.CompareTo(rightNumber);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 比较两个版本名;左侧不旧于右侧返回 True。
|
||||
/// </summary>
|
||||
public static bool CompareVersionGE(string left, string right) => CompareVersion(left, right) >= 0;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
namespace PCLCS;
|
||||
|
||||
/// <summary>
|
||||
/// MC 版本安装清单及 PCL 管理文件清单。
|
||||
/// </summary>
|
||||
/// <param name="SchemaVersion"> 清单格式版本;无版本的旧清单为 0。</param>
|
||||
/// <param name="Components"> 安装目标及受管理组件版本。</param>
|
||||
/// <param name="LastModifiedUtc"> 最后修改时间(UTC)。</param>
|
||||
/// <param name="ManagedFiles"> 受管理文件;没有文件时为空列表。</param>
|
||||
public record McInstallManifest(int SchemaVersion, McComponents Components, DateTime LastModifiedUtc, IReadOnlyList<McInstallManifest.ManagedFile> ManagedFiles) {
|
||||
/// <summary>
|
||||
/// 清理以 Mod 形式安装的、已删除或修改的组件的记录,并更新清单。
|
||||
/// </summary>
|
||||
public McInstallManifest RefreshModComponents(string gameFolder) {
|
||||
List<ManagedFile> invalidFiles = [];
|
||||
foreach (ManagedFile entry in ManagedFiles) {
|
||||
if (entry.Component is null) continue; // 不检查非组件文件
|
||||
FileInfo file = new(Path.Combine(gameFolder, entry.RelativePath));
|
||||
if (!file.Exists || file.LastWriteTimeUtc != entry.LastWriteTimeUtc.ToUniversalTime()) invalidFiles.Add(entry);
|
||||
}
|
||||
if (!invalidFiles.Any()) return this;
|
||||
return this with {
|
||||
Components = Components with {
|
||||
OptiFineEntry = invalidFiles.Any(file => file.Component == "OptiFine") ? null : Components.OptiFineEntry,
|
||||
FabricApiVersion = invalidFiles.Any(file => file.Component == "FabricApi") ? null : Components.FabricApiVersion,
|
||||
OptiFabricVersion = invalidFiles.Any(file => file.Component == "OptiFabric") ? null : Components.OptiFabricVersion
|
||||
},
|
||||
ManagedFiles = ManagedFiles.Where(file => !invalidFiles.Contains(file)).ToList(),
|
||||
LastModifiedUtc = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由 PCL 管理并记录的版本内文件。
|
||||
/// </summary>
|
||||
/// <param name="RelativePath"> 相对于游戏目录的文件路径。确保路径为全小写且可比较。</param>
|
||||
/// <param name="Sha1"> 小写的 SHA1。</param>
|
||||
/// <param name="LastWriteTimeUtc"> 目标文件的最后修改时间(UTC)。</param>
|
||||
/// <param name="Component"> 安装器组件名;为空时表示文件由整合包管理。</param>
|
||||
public record ManagedFile(string RelativePath, string Sha1, DateTime LastWriteTimeUtc,
|
||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Component = null);
|
||||
}
|
||||
|
||||
/// <param name="MinecraftVersion"> 目标 Minecraft 版本。</param>
|
||||
/// <param name="ModLoaderVersions"> 目标中各个 Mod Loader 的版本;原版为空字典。</param>
|
||||
/// <param name="OptiFineEntry"> OptiFine 条目信息,不安装时为 <see langword="null"/>。</param>
|
||||
/// <param name="FabricApiVersion"> 由 PCL 管理的 Fabric API 版本;不安装时为 <see langword="null"/>。</param>
|
||||
/// <param name="OptiFabricVersion"> 由 PCL 管理的 OptiFabric 版本;不安装时为 <see langword="null"/>。</param>
|
||||
[method: JsonConstructor]
|
||||
public record McComponents(string MinecraftVersion, Dictionary<ModLoaders, string> ModLoaderVersions,
|
||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] JObject? OptiFineEntry = null,
|
||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? FabricApiVersion = null,
|
||||
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? OptiFabricVersion = null) {
|
||||
[JsonIgnore]
|
||||
public string? OptiFineVersion => OptiFineEntry?["InstanceName"]?.Value<string>()?.AfterLast("-OptiFine_");
|
||||
public McComponents(string minecraftVersion, JObject? optiFineEntry, string? forgeVersion,
|
||||
string? neoForgeVersion, string? fabricVersion, string? fabricApiVersion, string? optiFabricVersion, string? liteLoaderVersion)
|
||||
: this(minecraftVersion, [], optiFineEntry, fabricApiVersion, optiFabricVersion) {
|
||||
if (forgeVersion is not null) ModLoaderVersions[ModLoaders.Forge] = forgeVersion;
|
||||
if (neoForgeVersion is not null) ModLoaderVersions[ModLoaders.NeoForge] = neoForgeVersion;
|
||||
if (fabricVersion is not null) ModLoaderVersions[ModLoaders.Fabric] = fabricVersion;
|
||||
if (liteLoaderVersion is not null) ModLoaderVersions[ModLoaders.LiteLoader] = liteLoaderVersion;
|
||||
}
|
||||
public bool IsEquivalentTo(McComponents other) =>
|
||||
MinecraftVersion == other.MinecraftVersion &&
|
||||
OptiFineVersion == other.OptiFineVersion &&
|
||||
FabricApiVersion == other.FabricApiVersion &&
|
||||
OptiFabricVersion == other.OptiFabricVersion &&
|
||||
ModLoaderVersions.Count == other.ModLoaderVersions.Count &&
|
||||
ModLoaderVersions.All(entry => other.ModLoaderVersions.TryGetValue(entry.Key, out string? version) && version == entry.Value);
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace PCLCS;
|
||||
|
||||
/// <summary>
|
||||
/// Minecraft 版本号和组件信息
|
||||
/// </summary>
|
||||
public sealed class McVersion {
|
||||
/// <summary>
|
||||
/// 原版版本名,如 26.1、26.1-snapshot-1、1.12.2、16w01a。
|
||||
/// </summary>
|
||||
public string VanillaName {
|
||||
get => _vanillaName;
|
||||
set {
|
||||
_vanillaName = value ?? "Unknown";
|
||||
Vanilla = NameToVersion(_vanillaName);
|
||||
}
|
||||
}
|
||||
private string _vanillaName = "Unknown";
|
||||
/// <summary>
|
||||
/// 可比较的三段式原版版本号。
|
||||
/// 旧格式会将次版本号移到主版本号,例如 1.20.1 会转换为 20.0.1,若没有版本号,例如旧快照,则为 9999.0.0。
|
||||
/// </summary>
|
||||
public Version Vanilla = new(9999, 0, 0);
|
||||
/// <summary> 原版版本号是否可靠(不是通过猜测获取)。</summary>
|
||||
public bool Reliable;
|
||||
/// <summary> 原版版本号是否有效。</summary>
|
||||
public bool Valid => Vanilla.Major < 1000;
|
||||
/// <summary> 可供比较的原版 Drop 序数,例如 26.3.2 为 263,1.21.5 为 210;若没有版本号,例如旧快照,则直接指定为 209。</summary>
|
||||
public int Drop => Valid ? Vanilla.Major * 10 + Vanilla.Minor : 209;
|
||||
/// <summary> 已安装的 Mod Loader,可能有多个。未安装时为 <see cref="ModLoaders.None"/>。</summary>
|
||||
public ModLoaders ModLoaders => ModLoaderVersions.Keys.Aggregate(ModLoaders.None, (result, loader) => result | loader);
|
||||
/// <summary> 已安装的各个 Mod Loader 及其版本;无法确定精确版本时为“未知版本”。</summary>
|
||||
public Dictionary<ModLoaders, string> ModLoaderVersions { get; } = [];
|
||||
/// <summary> OptiFine 版本名,如 C8、C9_pre10;未识别到时为 null。</summary>
|
||||
public string? OptiFine;
|
||||
/// <summary> OptiFine 是否以 Mod 形式安装。</summary>
|
||||
public bool OptiFineAsMod;
|
||||
/// <summary> 版本的发布时间。</summary>
|
||||
public DateTime ReleaseTime = new(1970, 1, 1, 15, 0, 0);
|
||||
/// <summary> 是否为愚人节版本。</summary>
|
||||
public bool IsFool;
|
||||
/// <summary> 是否为快照、预发布版、发布候选或实验性版本。</summary>
|
||||
public bool IsSnapshot;
|
||||
/// <summary> 是否识别到已安装的 OptiFine。</summary>
|
||||
public bool HasOptiFine => OptiFine is not null;
|
||||
/// <summary>
|
||||
/// 可供比较的 OptiFine 版本序数。
|
||||
/// </summary>
|
||||
public int OptiFineCode {
|
||||
get {
|
||||
if (OptiFine is null or "未知版本" || OptiFine.Length == 0) return 0;
|
||||
// 字母编号,如 G2 中的 G(7)
|
||||
int result = char.ToUpperInvariant(OptiFine[0]) - 'A' + 1;
|
||||
// 末尾数字,如 C5 beta4 中的 5
|
||||
result = result * 100 + (int.TryParse(OptiFine.Substring(1).RegexSeek(@"\d+"), out int mainVersion) ? mainVersion : 0);
|
||||
// 测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x)
|
||||
result *= 100;
|
||||
string? preview = OptiFine.RegexSeek(@"(?i)(pre|beta)\d*");
|
||||
if (preview is null) return result + 99;
|
||||
int sequence = int.TryParse(preview.RegexSeek(@"\d+"), out int value) ? value : 1; // 为 pre 或 beta 结尾,视作 1
|
||||
return result + (preview.StartsWithF("pre", true) ? 50 : 0) + sequence;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 可供比较的类 Forge 版本序数。
|
||||
/// </summary>
|
||||
public int ForgelikeCode {
|
||||
get {
|
||||
if (!ModLoaderVersions.TryGetValue(ModLoaders.Forge, out string? version)) {
|
||||
ModLoaderVersions.TryGetValue(ModLoaders.NeoForge, out version);
|
||||
}
|
||||
if (version is null or "未知版本") return 0;
|
||||
// 提取所有数字段,以兼容 beta、alpha 和 snapshot 等非数字后缀。
|
||||
int[] segments = version.RegexSearch(@"\d+")
|
||||
.Select(segment => int.TryParse(segment, out int value) ? value : 0).ToArray();
|
||||
return segments.Length switch {
|
||||
>= 4 => segments[0] * 1000000 + segments[1] * 10000 + segments[3],
|
||||
3 => segments[0] * 1000000 + segments[1] * 10000 + segments[2],
|
||||
2 => segments[0] * 1000000 + segments[1] * 10000,
|
||||
1 => segments[0] * 1000000,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认 Minecraft 版本。
|
||||
/// </summary>
|
||||
/// <param name="vanillaName">原版版本名。</param>
|
||||
/// <param name="reliable">版本名是否来自可靠依据。</param>
|
||||
public McVersion(string vanillaName, bool reliable = true) {
|
||||
Reliable = reliable;
|
||||
VanillaName = vanillaName;
|
||||
IsFool = IsFoolName(VanillaName);
|
||||
IsSnapshot = IsSnapshotName(VanillaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 Components 创建 Minecraft 版本信息。
|
||||
/// </summary>
|
||||
public McVersion(McComponents components) : this(components.MinecraftVersion) {
|
||||
OptiFine = components.OptiFineVersion?.AfterLast("HD_").AfterFirst("_");
|
||||
foreach (var loader in components.ModLoaderVersions) ModLoaderVersions[loader.Key] = loader.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 JSON 识别 Minecraft 版本和已安装组件。
|
||||
/// </summary>
|
||||
/// <param name="instanceName">实例文件夹名。</param>
|
||||
/// <param name="json">展开的版本 JSON。</param>
|
||||
/// <param name="jarVersionJson">版本 JAR 中的 version.json。</param>
|
||||
public McVersion(string instanceName, JObject json, JObject? jarVersionJson = null) {
|
||||
string jsonText = json.ToString(Formatting.None);
|
||||
DateTime releaseTime = new(1970, 1, 1, 15, 0, 0); // 未知版本也可能显示为 1970 年
|
||||
try {
|
||||
if (json["releaseTime"] is JToken releaseTimeToken) releaseTime = releaseTimeToken.ToObject<DateTime>();
|
||||
} catch {
|
||||
}
|
||||
bool reliable = true;
|
||||
// 获取游戏版本
|
||||
string vanillaName = (GetVanillaName(instanceName, json, jarVersionJson, releaseTime, ref reliable) ?? "Unknown").Replace("_unobfuscated", "").Replace(" Unobfuscated", "");
|
||||
Reliable = reliable;
|
||||
VanillaName = vanillaName;
|
||||
ReleaseTime = releaseTime;
|
||||
// 确定版本分类
|
||||
string jsonType = json.Value<string>("type") ?? "";
|
||||
// OptiFine
|
||||
OptiFine = jsonText.ContainsIgnoreCase("optifine") ? jsonText.RegexSeek(@"(?<=HD_U_)[^\"":/]+") ?? "未知版本": null;
|
||||
// 愚人节与快照版本
|
||||
DateTime fixedReleaseTime = releaseTime.ToUniversalTime().AddHours(2);
|
||||
IsFool = jsonType == "fool" || IsFoolName(vanillaName) || (fixedReleaseTime.Month == 4 && fixedReleaseTime.Day == 1 && jsonType == "snapshot");
|
||||
IsSnapshot = IsSnapshotName(vanillaName) || instanceName.ContainsIgnoreCase("combat") || jsonType is "snapshot" or "pending" or "fool";
|
||||
if (!Reliable) Logger.Info($"无法完全确认 MC 版本号的版本:{VanillaName}");
|
||||
// LiteLoader
|
||||
if (jsonText.ContainsIgnoreCase("liteloader")) {
|
||||
ModLoaderVersions[ModLoaders.LiteLoader] = jsonText.RegexSeek(@"(?<=com\.mumfrey:liteloader:)[^\"":/]+") ?? "未知版本";
|
||||
}
|
||||
// Fabric、Forge、NeoForge
|
||||
if (jsonText.ContainsIgnoreCase("net.fabricmc:fabric-loader") || jsonText.ContainsIgnoreCase("org.quiltmc:quilt-loader")) {
|
||||
ModLoaderVersions[ModLoaders.Fabric] = jsonText.RegexSeek(@"(?<=(net\.fabricmc:fabric-loader:|org\.quiltmc:quilt-loader:))[0-9\.]+(\+build.[0-9]+)?")?
|
||||
.Replace("+build", "") ?? "未知版本";
|
||||
} else if (jsonText.ContainsIgnoreCase("net.neoforge")) {
|
||||
// 1.20.1 JSON 范例:"--fml.forgeVersion", "47.1.99"
|
||||
// 1.20.2+ JSON 范例:"--fml.neoForgeVersion", "20.6.119-beta"
|
||||
ModLoaderVersions[ModLoaders.NeoForge] = FindArgumentValue(json, "--fml.neoForgeVersion") ?? FindArgumentValue(json, "--fml.forgeVersion") ?? "未知版本";
|
||||
} else if (jsonText.ContainsIgnoreCase("minecraftforge")) {
|
||||
ModLoaderVersions[ModLoaders.Forge] = jsonText.RegexSeek(@"(?<=forge:[0-9\.]+(?:_pre[0-9]*)?-)[0-9\.]+")
|
||||
?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:minecraftforge:)[0-9\.]+")
|
||||
?? jsonText.RegexSeek(@"(?<=net\.minecraftforge:fmlloader:[0-9\.]+-)[0-9\.]+") ?? "未知版本";
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetVanillaName(string instanceName, JObject json, JObject? jarVersionJson, DateTime releaseTime, ref bool reliable) {
|
||||
// 获取发布时间并判断是否为老版本
|
||||
if (releaseTime.Year is > 2000 and < 2013) return "Old";
|
||||
// 实验性快照
|
||||
if (json.Value<string>("type") == "pending") return "pending";
|
||||
// 从 PCL 下载的版本信息中获取版本号
|
||||
if (json["clientVersion"] is JToken clientVersion) return clientVersion.ToString();
|
||||
// 从 HMCL 下载的版本信息中获取版本号
|
||||
if (json["patches"] is JArray patches) {
|
||||
JToken? gameVersion = patches.OfType<JObject>().FirstOrDefault(patch => patch.Value<string>("id") == "game")?["version"];
|
||||
if (gameVersion is not null) return gameVersion.ToString();
|
||||
}
|
||||
// 从 Forge / NeoForge Arguments 中获取版本号
|
||||
if (FindArgumentValue(json, "--fml.mcVersion") is { Length: > 0 } fmlVersion) return fmlVersion;
|
||||
// 从继承版本中获取版本号
|
||||
string inherit = json["inheritsFrom"]?.ToString() ?? "";
|
||||
if (inherit.Length > 0) {
|
||||
string jar = json["jar"]?.ToString() ?? ""; // LiteLoader 优先使用 Jar
|
||||
return jar.Length > 0 ? jar : inherit;
|
||||
}
|
||||
// 从下载地址中获取版本号
|
||||
string? download = json["downloads"]?.ToString(Formatting.None).RegexSeek(@"(?<=launcher.mojang.com/mc/game/)[^/]*");
|
||||
if (download is not null) return download;
|
||||
// 从 Forge/OptiFine/Fabric 版本中获取版本号
|
||||
string librariesText = json["libraries"]!.ToString();
|
||||
string[] libraryPatterns = [
|
||||
@"(?<=net.minecraftforge:forge:)[0-9]{1,2}.[0-9+.]+",
|
||||
@"(?<=net.minecraftforge:fmlloader:)[0-9]{1,2}.[0-9+.]+",
|
||||
@"(?<=optifine:OptiFine:)[0-9]{1,2}.[0-9+.]+",
|
||||
@"(?<=((fabricmc)|(quiltmc)):intermediary:)[^\""]*"
|
||||
];
|
||||
foreach (string pattern in libraryPatterns) {
|
||||
if (librariesText.RegexSeek(pattern) is string match) return match;
|
||||
}
|
||||
// 从 jar 项中获取版本号
|
||||
if (json["jar"] is JToken jarToken) return jarToken.ToString();
|
||||
// 从 jar 文件的 version.json 中获取版本号
|
||||
if (jarVersionJson?["name"] is JToken jarNameToken && jarNameToken.ToString() is { Length: < 32 } jarName) { // 因为 wiki 说这玩意儿可能是个 hash,虽然我没发现
|
||||
Logger.Info($"从版本 jar 中的 version.json 获取到版本号:{jarName}");
|
||||
return jarName;
|
||||
}
|
||||
string VersionPattern = @"(([1-9][0-9]w[0-9]{2}[a-g])|((1|[2-9][0-9])\.[0-9]+(\.[0-9]+)?(-(pre|rc|snapshot-?)[1-9]*| Pre-Release( [1-9])?)?))(_unobfuscated)?";
|
||||
// 从 JSON 的 ID 中获取
|
||||
if (json["id"]!.ToString().RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string idMatch) return idMatch;
|
||||
// 非准确的版本判断警告
|
||||
Logger.Info($"无法完全确认 MC 版本号的版本:{instanceName}");
|
||||
reliable = false;
|
||||
// 从文件夹名中获取
|
||||
if (instanceName.RegexSeek(VersionPattern, RegexOptions.IgnoreCase) is string folderMatch) return folderMatch;
|
||||
// 从 JSON 出现的版本号中获取
|
||||
JObject withoutLibraries = (JObject) json.DeepClone();
|
||||
withoutLibraries.Remove("libraries");
|
||||
return withoutLibraries.ToString(Formatting.None).RegexSeek(VersionPattern, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private static bool IsFoolName(string name) {
|
||||
name = name.Lower();
|
||||
return name.StartsWithF("2.0") || name is "15w14a" or "1.rv-pre1" or
|
||||
"3d shareware v1.34" or "20w14∞" or "22w13oneblockatatime" or "23w13a_or_b" or
|
||||
"24w14potato" or "25w14craftmine" or "26w14a" || name.StartsWithF("20w14inf");
|
||||
}
|
||||
|
||||
private static bool IsSnapshotName(string name) =>
|
||||
new[] { "w", "snapshot", "rc", "pre", "experimental", "-" }.Any(name.ContainsIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 读取 arguments 中指定参数的字符串值。
|
||||
/// </summary>
|
||||
private static string? FindArgumentValue(JObject json, string name) {
|
||||
JArray? arguments = json["arguments"]?["game"] as JArray;
|
||||
if (arguments is null) return null;
|
||||
for (int i = 0; i + 1 < arguments.Count; i++) {
|
||||
if (arguments[i]?.ToString() == name) return arguments[i + 1]?.ToString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断版本字符串是否符合 Minecraft 原版格式,例如 1.x、26.x。
|
||||
/// </summary>
|
||||
public static bool IsStandardFormat(string? version) {
|
||||
if (version is null) return false;
|
||||
if (Regex.IsMatch(version, @"^1\.\d")) return true;
|
||||
return double.TryParse(version.RegexSeek(@"^[2-9]\d\.\d+"), NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture, out double value) && value >= 26;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将版本名转换为 Drop 序数;无法转换时返回 209。
|
||||
/// </summary>
|
||||
public static int VersionToDrop(string? vanillaName) {
|
||||
if (vanillaName is null) return 209;
|
||||
Version version = NameToVersion(vanillaName);
|
||||
return version.Major >= 1000 ? 209 : version.Major * 10 + version.Minor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Drop 序数转换为版本字符串。
|
||||
/// </summary>
|
||||
public static string DropToVersion(int drop) {
|
||||
if (drop >= 250) return $"{drop / 10}.{drop % 10}";
|
||||
if (drop != 209) return $"1.{drop / 10}";
|
||||
Logger.Warn(new Exception("尝试将旧快照版 Drop 序数转换为版本字符串,这不应该发生!"));
|
||||
return "1.20";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将版本名转换为可比较的三段式原版版本号。
|
||||
/// 对老版本格式,例如 1.20.3,会被转换为 20.0.3。
|
||||
/// 对愚人节版本,返回其基于的正式版版本号。
|
||||
/// 若没有版本号,例如旧快照,则为 9999.0.0。
|
||||
/// </summary>
|
||||
public static Version NameToVersion(string? name) {
|
||||
if (name is null) return new Version(9999, 0, 0);
|
||||
name = name.Lower().Replace("_unobfuscated", "").Replace(" unobfuscated", "");
|
||||
// 愚人节版本
|
||||
if (name.StartsWithF("2.0")) return NameToVersion("1.5.1");
|
||||
if (name == "15w14a") return NameToVersion("1.8.3");
|
||||
if (name.ContainsIgnoreCase(".rv-pre")) return NameToVersion("1.9.2");
|
||||
if (name.ContainsIgnoreCase("shareware")) return NameToVersion("1.13.2");
|
||||
if (name.StartsWithF("20w14") && name != "20w14a") return NameToVersion("1.15.2");
|
||||
if (name.ContainsIgnoreCase("oneblockatatime")) return NameToVersion("1.18.2");
|
||||
if (name.ContainsIgnoreCase("23w13a") && name != "20w13a") return NameToVersion("1.19.4");
|
||||
if (name == "24w14potato") return NameToVersion("1.20.4");
|
||||
if (name == "25w14craftmine") return NameToVersion("1.21.4");
|
||||
if (name == "26w14a") return NameToVersion("26.1.1");
|
||||
// 其余版本
|
||||
static int ParseSegment(string[] segments, int index) {
|
||||
if (index >= segments.Length) return 0;
|
||||
// 只读取该段开头连续的数字,忽略其后的预览版标记。
|
||||
return int.TryParse(segments[index].RegexSeek(@"^\d+"), out int value) ? value : 0;
|
||||
}
|
||||
string[] segments = name.Split([' ', '_', '-', '.'], StringSplitOptions.None);
|
||||
if (name.StartsWithF("1.")) {
|
||||
return new Version(ParseSegment(segments, 1), 0, ParseSegment(segments, 2));
|
||||
} else if (name.RegexCheck(@"^[2-9][0-9]\.")) {
|
||||
return new Version(ParseSegment(segments, 0), ParseSegment(segments, 1), ParseSegment(segments, 2));
|
||||
} else {
|
||||
return new Version(9999, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
{
|
||||
"f3a3c9d792bdb48595405d52c02cc7baf1db4ecb": "OptiFine_1.7.2_HD_U_E3.jar",
|
||||
"657169c421ce280f80c73b05ab71ad582d2b37fd": "OptiFine_1.7.2_HD_U_E4.jar",
|
||||
"99f20d449912cd860b409e4679c54dd8da43d55e": "OptiFine_1.7.2_HD_U_E7.jar",
|
||||
"8253f6ed418a2a61a612cc9e69fd1fea1d11fa7d": "OptiFine_1.7.2_HD_U_E8.jar",
|
||||
"379e52fc02cd75562d201b0c505269b6109b2136": "OptiFine_1.7.2_HD_U_F3.jar",
|
||||
"fb04b91e47b5eaa172d2b76c843e488dba8f909b": "OptiFine_1.7.2_HD_U_F7.jar",
|
||||
"b4b6c2c99f5734e4267030a47f2eaad2796eb2a7": "OptiFine_1.7.10_HD_U_D3.jar",
|
||||
"a0b533bc97b24fe771f5b08536c6339d04f92bae": "OptiFine_1.7.10_HD_U_D4.jar",
|
||||
"5e9327dd05a1e762c23c3c0227445d90f193922f": "OptiFine_1.7.10_HD_U_D6.jar",
|
||||
"7a7d2adf21038d241dd375faa7316fb2c74d34d3": "OptiFine_1.7.10_HD_U_D7.jar",
|
||||
"2345677be44dd56a0ae5b90e747a590c3cc00ff8": "OptiFine_1.7.10_HD_U_D8.jar",
|
||||
"23f133173428aee712eb11f200b461e3114bde31": "OptiFine_1.7.10_HD_U_E3.jar",
|
||||
"2130d7266224da6a7325051e37444f7e7c19277c": "OptiFine_1.7.10_HD_U_E7.jar",
|
||||
"55c9c475ecd0f62cc1776ce52be04c5937112936": "OptiFine_1.8.0_HD_U_H5.jar",
|
||||
"bb2c2997cdc66cfb3c311a3dc065752a4845c1a8": "OptiFine_1.8.0_HD_U_H6.jar",
|
||||
"692b0b7e878c3416f21b4347fab4516f3cc86dfc": "OptiFine_1.8.0_HD_U_H7.jar",
|
||||
"5464962b66b9b0dacb3d787e6a71d2365ae50bca": "OptiFine_1.8.0_HD_U_H8.jar",
|
||||
"b7718b03111bec04036fd3352ce374eea273171c": "OptiFine_1.8.0_HD_U_I3.jar",
|
||||
"0d1dcf64b4d104a71e8f65a1362a163ed81a9030": "OptiFine_1.8.0_HD_U_I7.jar",
|
||||
"e5e5eb03748af0fa160b502a28359c0144e254d2": "OptiFine_1.8.8_HD_U_H5.jar",
|
||||
"9294bc4562a143c35ece03a6b3d3a000109def5f": "OptiFine_1.8.8_HD_U_H6.jar",
|
||||
"1c06c011904ab22952a96f73d4b575a5c265d207": "OptiFine_1.8.8_HD_U_H7.jar",
|
||||
"3984e4dd222bb62cb7e28e4d3092029cc7534fd1": "OptiFine_1.8.8_HD_U_H8.jar",
|
||||
"0dc33a9761ceac19ca0bbb1ea6813c0160d43aa1": "OptiFine_1.8.8_HD_U_I3.jar",
|
||||
"30cbf4d61052cbfb736a78117da333df23c129a3": "OptiFine_1.8.8_HD_U_I7.jar",
|
||||
"d8240cfa7387b994fdf2a64bdc8a8860857adc18": "OptiFine_1.8.9_HD_U_H5.jar",
|
||||
"2636689b31810896cf80493f6fc4d8c1783048ea": "OptiFine_1.8.9_HD_U_H6.jar",
|
||||
"e4544c9210bc056abffa1986a33c4e72623d4f31": "OptiFine_1.8.9_HD_U_H7.jar",
|
||||
"4b73738913453bcbca969d3e77f05ae0cf5b8a7e": "OptiFine_1.8.9_HD_U_H8.jar",
|
||||
"d5cd8b902077075bbf1d865cfd5b3362a6863ace": "OptiFine_1.8.9_HD_U_I3.jar",
|
||||
"58bec8f8766918dcad2b8412f1fbb6f30cc9319f": "OptiFine_1.8.9_HD_U_I7.jar",
|
||||
"dcab4849a24ccad5a6a3496c67bc5a88411d2a35": "OptiFine_1.8.9_HD_U_L5.jar",
|
||||
"d362d58a28f5373b141b9e426e8e160638bfafcd": "OptiFine_1.8.9_HD_U_M5.jar",
|
||||
"1f9dcc9735e94ebb5195c4014300ea462eb12878": "preview_OptiFine_1.8.9_HD_U_L6_pre1.jar",
|
||||
"6e0b3b86e51c2647c98e277475ee7e68f32a0d0e": "preview_OptiFine_1.8.9_HD_U_M5_pre2.jar",
|
||||
"7287fcd1a415ab87eca5e89457cc3507aa3fa579": "preview_OptiFine_1.8.9_HD_U_M6_pre1.jar",
|
||||
"0bb792be16ec78d8b62586cc4856950befb3391b": "preview_OptiFine_1.8.9_HD_U_M6_pre2.jar",
|
||||
"fd87c53197312614c34669feb7608f74a30da712": "OptiFine_1.9.0_HD_U_B1.jar",
|
||||
"f99941037f3633926ffebb3eda67f296a701c019": "OptiFine_1.9.0_HD_U_B2.jar",
|
||||
"34c47106cda1371ecf13e61aed4c1b53a3a53163": "OptiFine_1.9.0_HD_U_B3.jar",
|
||||
"ebc7258e13344097588c73a0b48457083069e1df": "OptiFine_1.9.0_HD_U_B5.jar",
|
||||
"999df92e8904f9268e549135edbec322d332ef62": "OptiFine_1.9.0_HD_U_D7.jar",
|
||||
"0fe031e95f9c01481c9376d9a1df68f9e9857db8": "OptiFine_1.9.0_HD_U_D8.jar",
|
||||
"f4bc2eb5e039e842eb07181ea564eba487513228": "OptiFine_1.9.0_HD_U_E3.jar",
|
||||
"78870a94ec3c4db8aebc4c29d4d39bdda2bf05da": "OptiFine_1.9.0_HD_U_E7.jar",
|
||||
"d3d55bb37e76d1932cb3ad9dde4b9127993f856d": "OptiFine_1.9.0_HD_U_H5.jar",
|
||||
"b5faec3b84f42dd396512a3ce7c1b57c0b972b70": "OptiFine_1.9.0_HD_U_I5.jar",
|
||||
"92742ecac143b1d28c215ca145bd9775a01c5085": "preview_OptiFine_1.9.0_HD_U_I5_pre1.jar",
|
||||
"c87029938376017deba9108322bcb8424d64e92c": "OptiFine_1.9.2_HD_U_B1.jar",
|
||||
"d3e5e1fa480e66603ca037d74966a95d25968d51": "OptiFine_1.9.2_HD_U_B2.jar",
|
||||
"fbd545f921d098235a28f87cda55a0504e831954": "OptiFine_1.9.2_HD_U_B3.jar",
|
||||
"1ae8e6daeeccb6ee48618e8ccdc0b6276d47944f": "OptiFine_1.9.2_HD_U_D7.jar",
|
||||
"3350c829b29d170e9ac19809a3ae45cb099277b5": "OptiFine_1.9.2_HD_U_D8.jar",
|
||||
"c032b2ae0b6087077efef8ae89b612e0d4ea81b7": "OptiFine_1.9.2_HD_U_E3.jar",
|
||||
"3d8cbc19bcad9e9ecc98f607e10f65673a09b482": "OptiFine_1.9.4_HD_U_B4.jar",
|
||||
"b6b9e66b34ddcda5294b016602fb17d741ff6f54": "OptiFine_1.9.4_HD_U_B5.jar",
|
||||
"060ddd5a0b7b7ac5586de04ae53cf923ae5c7290": "OptiFine_1.9.4_HD_U_B6.jar",
|
||||
"2c39611a89dc583cdb403f402c2efd2bd8c6da63": "OptiFine_1.9.4_HD_U_D7.jar",
|
||||
"4b26926208de270225eb9d3af24a4ff3841238f5": "OptiFine_1.9.4_HD_U_D8.jar",
|
||||
"012f82fce41aee5e6e56e551d450a07b2b361646": "OptiFine_1.9.4_HD_U_E3.jar",
|
||||
"cf9c075fa71dab34cdf1c376a76bd08295c69ce6": "OptiFine_1.9.4_HD_U_E7.jar",
|
||||
"b3a76b92e0e9594f6fa135f1156b9cc13712db20": "OptiFine_1.9.4_HD_U_H5.jar",
|
||||
"68a206db0d425dd85f6eba33e52122b01e844c46": "OptiFine_1.9.4_HD_U_I5.jar",
|
||||
"2e48d20605340cfad844a6587d3848e328fd4399": "preview_OptiFine_1.9.4_HD_U_I5_pre1.jar",
|
||||
"d7a1e5f40e28fa3283b82bf3af7d3f433b91df08": "OptiFine_1.10_HD_U_B6.jar",
|
||||
"717a02b21136e022cdb4d7d6ec6a4125f4ed2312": "OptiFine_1.10_HD_U_B7.jar",
|
||||
"0b5d1719592a57cf414643c5b56f4bb3c23a5b94": "OptiFine_1.10_HD_U_C1.jar",
|
||||
"236747126edcf0c97fc6a20fa7f6491accbf0108": "OptiFine_1.10_HD_U_D7.jar",
|
||||
"c5fd78710e821f06f871994032e951065c15c40d": "OptiFine_1.10_HD_U_D8.jar",
|
||||
"1bea8b0bae04ca4b25c6824a6f23a5442d5aa926": "OptiFine_1.10_HD_U_E3.jar",
|
||||
"7a7195348ea583458b657573b5fcc72e4adc8308": "OptiFine_1.10_HD_U_E7.jar",
|
||||
"3bcc46f718711cd6fe3db49de4cc810b75420de8": "OptiFine_1.10_HD_U_H5.jar",
|
||||
"a0070afe9c1c0ced4bcfcda85359e07917d569bd": "OptiFine_1.10_HD_U_I5.jar",
|
||||
"f9a36eb0246ad6ba303e670082be2c2515c50dcd": "preview_OptiFine_1.10_HD_U_I5_pre1.jar",
|
||||
"3ea5dae651b6d64900562d1cde5070ebd9db2936": "OptiFine_1.10.2_HD_U_C1.jar",
|
||||
"41968442e8d1e30e9a32e7a5594caca22312d9d6": "OptiFine_1.10.2_HD_U_C2.jar",
|
||||
"9c8bc96d3125199e96f5dbfda685ea5dd92c253e": "OptiFine_1.10.2_HD_U_C3.jar",
|
||||
"7ec4ccc213b482936baaffb9f1937f7147796893": "OptiFine_1.10.2_HD_U_D1.jar",
|
||||
"3ee5aecc8e11567fb46de0b94e23cf1428f4c9fc": "OptiFine_1.10.2_HD_U_D2.jar",
|
||||
"98059ed7e93d0fc058203569ccfe965b022fb18f": "OptiFine_1.10.2_HD_U_D3.jar",
|
||||
"c3165d0a0207d72e18f4533e7aa5ca30e96819a9": "OptiFine_1.10.2_HD_U_D4.jar",
|
||||
"347359aef422d64711b470f4cf0803cb072da0fa": "OptiFine_1.10.2_HD_U_D6.jar",
|
||||
"4ddf7c45e00839031f5793451541bca002f9257f": "OptiFine_1.10.2_HD_U_D7.jar",
|
||||
"5c3685e279c664a818c41ca213b2bc9bfc4340b0": "OptiFine_1.10.2_HD_U_D8.jar",
|
||||
"129581af3d7d73b506b42d822cb2d3fce68c8e2e": "OptiFine_1.10.2_HD_U_E3.jar",
|
||||
"f052d269109154d851cc7a8e766c78802a76e9b2": "OptiFine_1.10.2_HD_U_E7.jar",
|
||||
"f285cfe6753c5d987e4cb0940da05e9513334d98": "OptiFine_1.10.2_HD_U_H5.jar",
|
||||
"12fd8dd33150f9e4436f23bac08265305f949e20": "OptiFine_1.10.2_HD_U_I5.jar",
|
||||
"adac69b25bb95242422a635965c7f17503c694be": "preview_OptiFine_1.10.2_HD_U_I5_pre1.jar",
|
||||
"06031e4c828b66facefdfa32cd29c585909d9e45": "OptiFine_1.11_HD_U_B1.jar",
|
||||
"449aef14cee17cf1c79d4bad73b69107202d3c1c": "OptiFine_1.11_HD_U_B2.jar",
|
||||
"7663e1ee572c742e96a71a355f0775baf7cbc6ef": "OptiFine_1.11_HD_U_B3.jar",
|
||||
"bde9d34f84a030734c7b9d09eda700c7c828eb96": "OptiFine_1.11_HD_U_B5.jar",
|
||||
"579cfb75b86f3404c5f6e8f694c51b09b7c9b261": "OptiFine_1.11_HD_U_B6.jar",
|
||||
"350af22b31dbc27d1b928e7144582fd361deb193": "OptiFine_1.11_HD_U_B7.jar",
|
||||
"236161183cc5cd5faf84dd27824cc3eaa107ed5f": "OptiFine_1.11_HD_U_B8.jar",
|
||||
"9ca32d42c7e5ad9de9665ffee9ddd41dddd33b66": "OptiFine_1.11_HD_U_C3.jar",
|
||||
"2e45de9e9fb385f24c6eecb1a64537de78778105": "OptiFine_1.11_HD_U_C7.jar",
|
||||
"32df58fec7148e75b599b18691f80e69b7f9cd1b": "OptiFine_1.11_HD_U_F5.jar",
|
||||
"71057cb7a3d0971c3b56e1fbbc333304a72e1352": "OptiFine_1.11_HD_U_G5.jar",
|
||||
"c2ee1e1019ab26bee56447f4df8a2639d2029925": "preview_OptiFine_1.11_HD_U_G5_pre1.jar",
|
||||
"083a002215e2174c0d40af20d2f3844ea349a354": "OptiFine_1.11.2_HD_U_B5.jar",
|
||||
"eb92ce00048faa95b184d121fa667074532fb9db": "OptiFine_1.11.2_HD_U_B6.jar",
|
||||
"7449a2db44e86303d588b526dc73127c16b0e94f": "OptiFine_1.11.2_HD_U_B7.jar",
|
||||
"3e1006c935fe27d83459e77d336460ac5b5c81b4": "OptiFine_1.11.2_HD_U_B8.jar",
|
||||
"049e7e134e8c7364ab61810d84289394e17921de": "OptiFine_1.11.2_HD_U_B9.jar",
|
||||
"4871f2edf7ded30c374aa770e9ea4adcc0b786d0": "OptiFine_1.11.2_HD_U_C1.jar",
|
||||
"e7a54d9e203a1b951f8373ab6dcdedb348e536b1": "OptiFine_1.11.2_HD_U_C2.jar",
|
||||
"8d7fd0facc2299f3dad9dc7c19cc13f72a780343": "OptiFine_1.11.2_HD_U_C3.jar",
|
||||
"20db26eaafb1c4ebd99e1dc2fffe4a62456bf60e": "OptiFine_1.11.2_HD_U_C7.jar",
|
||||
"e4efcf05086389fe18d6fe62eb8ab7512a07323c": "OptiFine_1.11.2_HD_U_F5.jar",
|
||||
"65c425ab628d1748b489dee8619271402428509e": "OptiFine_1.11.2_HD_U_G5.jar",
|
||||
"f253807a4689e44da030acd5c2187d2184885e87": "preview_OptiFine_1.11.2_HD_U_G5_pre1.jar",
|
||||
"1fdacab6f3b0df1f46142acf9c427db2a653d680": "OptiFine_1.12_HD_U_C2.jar",
|
||||
"a62a02c8437a0dccaf2ea89ab8392fd40dc92eb1": "OptiFine_1.12_HD_U_C3.jar",
|
||||
"36d6f6f64048f68dd248a4d3c43a6f5e51fbb9c5": "OptiFine_1.12_HD_U_C4.jar",
|
||||
"fd8e0e3ba43568e5ca0ce31c87373806d269b7e5": "OptiFine_1.12_HD_U_C5.jar",
|
||||
"25ba634e5105e6d1e615dad0046905b68313cc9f": "OptiFine_1.12_HD_U_C7.jar",
|
||||
"b9cd2775fc49bf009f82d3ce36235b1e702d46bb": "OptiFine_1.12_HD_U_F5.jar",
|
||||
"3b8369d33c3257cb247c22b13a41b7db0924e948": "OptiFine_1.12_HD_U_G5.jar",
|
||||
"aeee885ccf821c34d86537184389873d8c312ae1": "preview_OptiFine_1.12_HD_U_G5_pre1.jar",
|
||||
"c754771bf6a7371a7084459505c244cbec61a7cf": "OptiFine_1.12.1_HD_U_C5.jar",
|
||||
"4362a9d38542b5f4ba07782fc4d07949f4fede08": "OptiFine_1.12.1_HD_U_C6.jar",
|
||||
"38785968beeb796cde55f91995c6cf8bfbc3b062": "OptiFine_1.12.1_HD_U_C7.jar",
|
||||
"e6f9e6b573119c052062192695fed4ef388db9c1": "OptiFine_1.12.1_HD_U_F5.jar",
|
||||
"3c9cd74810cad75de5091633a1e0c7d3d76ea09c": "OptiFine_1.12.1_HD_U_G5.jar",
|
||||
"aaaaff3ae13a0cdffa2475632cf6303b9f86e0b0": "preview_OptiFine_1.12.1_HD_U_G5_pre1.jar",
|
||||
"a9d21f084a485bc12fce8c9bea1660c6d4b08855": "OptiFine_1.12.2_HD_U_C5.jar",
|
||||
"2159a0a754828d63446fca7b6f8fa57b710c6048": "OptiFine_1.12.2_HD_U_C6.jar",
|
||||
"8ed8009fc82b43909190882927f335d5250a0e19": "OptiFine_1.12.2_HD_U_C7.jar",
|
||||
"a55768a83db4ad0599f68d9bf3fa8041e2ae3852": "OptiFine_1.12.2_HD_U_C8.jar",
|
||||
"e0236d21a2a164f2af1cfff8f9f325f53c9125f2": "OptiFine_1.12.2_HD_U_C9.jar",
|
||||
"b91a0d82c24eef95be0af92993b14e94b842f166": "OptiFine_1.12.2_HD_U_D1.jar",
|
||||
"04c28238506dc3561addb6efb87c9878d5b82ffe": "OptiFine_1.12.2_HD_U_D2.jar",
|
||||
"b784000951d41f54d67685a0a275d9f61f810514": "OptiFine_1.12.2_HD_U_D3.jar",
|
||||
"343f814410401ebdbb3314deb17c27cbab67bfac": "OptiFine_1.12.2_HD_U_E1.jar",
|
||||
"5c87706b6d14061fed248506d8e3f95b7824ad18": "OptiFine_1.12.2_HD_U_E2.jar",
|
||||
"e805d4be5c2a3343488c573145606e90bb13816d": "OptiFine_1.12.2_HD_U_E3.jar",
|
||||
"0e8d45666e80829c9ab74527628dace9187fbfb5": "OptiFine_1.12.2_HD_U_F4.jar",
|
||||
"84ada0f48bcfb16c37c49283627597bb74d935f6": "OptiFine_1.12.2_HD_U_F5.jar",
|
||||
"ca3aea3a09ce215906c346fe190907fe0347b0c4": "OptiFine_1.12.2_HD_U_G5.jar",
|
||||
"23a8be042f354ec97c99672906c3e61fdf54ca2c": "preview_OptiFine_1.12.2_HD_U_F6_pre1.jar",
|
||||
"b71f993a364c7945081cc6cde9a20d29b4e43f1f": "preview_OptiFine_1.12.2_HD_U_G5_pre1.jar",
|
||||
"ab2118b1ed23444d6decf081e0295e4c679af6c2": "preview_OptiFine_1.12.2_HD_U_G6_pre1.jar",
|
||||
"25498de48dd9b7ed2ba5b83464950ad132e61c1f": "preview_OptiFine_1.14_HD_U_F1_pre6.jar",
|
||||
"396ce7f0e22aae3543134baabd61ad547334c0aa": "OptiFine_1.14.2_HD_U_F1.jar",
|
||||
"c8c6da3e2ae32d020d9001d4489aad702680f50f": "preview_OptiFine_1.14.2_HD_U_F1_pre15.jar",
|
||||
"52420ef34128a8b15d7c8f5ed8f7ebc94b708efd": "OptiFine_1.14.3_HD_U_F1.jar",
|
||||
"887d648f26f7ba17ea34c0c2dcff4096f7a226b1": "OptiFine_1.14.3_HD_U_F2.jar",
|
||||
"ad8a8f60931f48e2873238d1460268bb4111f6c9": "OptiFine_1.14.4_HD_U_F2.jar",
|
||||
"c9710081fb7012ddb56838ac54653ac2258a2404": "OptiFine_1.14.4_HD_U_F3.jar",
|
||||
"c09f4d1ad66df8e3039334023422f1bc8cc211b8": "OptiFine_1.14.4_HD_U_F4.jar",
|
||||
"3408258990309de773cc5a8ab71cb4137718a82b": "OptiFine_1.14.4_HD_U_F5.jar",
|
||||
"117c01a5431244a8b6a3ac393374dac9624f9be6": "OptiFine_1.14.4_HD_U_G5.jar",
|
||||
"ac602fe6a39d2fb4cadfe08e87af0b0daa2d8ac9": "preview_OptiFine_1.14.4_HD_U_G5_pre1.jar",
|
||||
"630a32be9b711b5a479507159d5d2598189fed33": "OptiFine_1.15.2_HD_U_G6.jar",
|
||||
"8339858b33056ec8975f6c0c9e1694a11f0d209a": "preview_OptiFine_1.15.2_HD_U_G1_pre1.jar",
|
||||
"6cdf96d972b5f70c4575ff875a031c4931985686": "preview_OptiFine_1.15.2_HD_U_G1_pre10.jar",
|
||||
"489d2284f2d50bd7c7cf4c216a766503d37e1f3c": "preview_OptiFine_1.15.2_HD_U_G1_pre12.jar",
|
||||
"c66d412c4cb46c6f2c76c224dc45a62d30ee8b43": "preview_OptiFine_1.15.2_HD_U_G1_pre13.jar",
|
||||
"582eb1d6470cc133db563bd1c2c8bc2068215806": "preview_OptiFine_1.15.2_HD_U_G1_pre18.jar",
|
||||
"64d738862683c54a22e1856b2c13379599ca79ce": "preview_OptiFine_1.15.2_HD_U_G1_pre19.jar",
|
||||
"3da9d466c847c50a73dc634dabc6a57f3b24e2f7": "preview_OptiFine_1.15.2_HD_U_G1_pre2.jar",
|
||||
"e6583246a85dba26a478dd4925c298bb5b3f277a": "preview_OptiFine_1.15.2_HD_U_G1_pre20.jar",
|
||||
"84c5d50e26aaa02733fa15a8c13ab845fbdea97f": "preview_OptiFine_1.15.2_HD_U_G1_pre21.jar",
|
||||
"c55e449386eeca34df5b428d85927242fc841438": "preview_OptiFine_1.15.2_HD_U_G1_pre22.jar",
|
||||
"459e17c77059671ef5daa9ab794400e8ae25095d": "preview_OptiFine_1.15.2_HD_U_G1_pre23.jar",
|
||||
"2c6e4f61b1ceea92b76e0be40bc672e5b6d58efe": "preview_OptiFine_1.15.2_HD_U_G1_pre24.jar",
|
||||
"4ea53e3d202239c5ffa2affa809e36998194f1bc": "preview_OptiFine_1.15.2_HD_U_G1_pre26.jar",
|
||||
"29861ba50021b08200ef33f77c776f9f5b7135f7": "preview_OptiFine_1.15.2_HD_U_G1_pre27.jar",
|
||||
"454dc0e03dab9d30a2ccce5823b7a0b309f5c859": "preview_OptiFine_1.15.2_HD_U_G1_pre28.jar",
|
||||
"3c3623d75dc47722149edfb93fdca121c32e8987": "preview_OptiFine_1.15.2_HD_U_G1_pre29.jar",
|
||||
"942e23fa0169c693fb1b645fae4db9217c6b5916": "preview_OptiFine_1.15.2_HD_U_G1_pre3.jar",
|
||||
"bdbd92e5252c0afc0f22d6d5840f96762e5d01ea": "preview_OptiFine_1.15.2_HD_U_G1_pre30.jar",
|
||||
"5daa9461c5762bdd647665f49d2e0ef0eb348910": "preview_OptiFine_1.15.2_HD_U_G1_pre4.jar",
|
||||
"0029ab50050d19f4009f0a7d708982dde736f022": "preview_OptiFine_1.15.2_HD_U_G1_pre5.jar",
|
||||
"646b279927cab6036e8f295efd5bda268acd85cf": "preview_OptiFine_1.15.2_HD_U_G1_pre7.jar",
|
||||
"f11e79a7bc91a5fd985cb756445fc88e8c2dd31b": "preview_OptiFine_1.15.2_HD_U_G1_pre9.jar",
|
||||
"1e9869b63a89a8ab81a58e1473dd2c2291fbd943": "preview_OptiFine_1.15.2_HD_U_G6_pre5.jar",
|
||||
"87533b8ac0dfeb11241041f197f8f95c0ffa8c08": "OptiFine_1.16.1_HD_U_G2.jar",
|
||||
"3e610c9bd89ae2a54cc06de6dfb1b00cd45e9895": "preview_OptiFine_1.16.1_HD_U_G2_pre1.jar",
|
||||
"df817e1ab0e577be1dfb6c20a9b628d3d725c780": "preview_OptiFine_1.16.1_HD_U_G2_pre10.jar",
|
||||
"dbca331e7abbe64d6aa360e6a93beec2935d24e3": "preview_OptiFine_1.16.1_HD_U_G2_pre11.jar",
|
||||
"6fef880338cabe32443557762ca43fb36fe01f7c": "preview_OptiFine_1.16.1_HD_U_G2_pre12.jar",
|
||||
"ea7bea6511f3b20aa672544d28dbc6d1a2ee8b41": "preview_OptiFine_1.16.1_HD_U_G2_pre13.jar",
|
||||
"efdc788f59e1c1bcde3b64430322d8990ce7c7d6": "preview_OptiFine_1.16.1_HD_U_G2_pre14.jar",
|
||||
"388100110b4dc8ef28eaa2f3a4ae2f33a8beb5ae": "preview_OptiFine_1.16.1_HD_U_G2_pre15.jar",
|
||||
"63f5f097ce2e678d69767981b09b133ab8eac4f5": "preview_OptiFine_1.16.1_HD_U_G2_pre16.jar",
|
||||
"c56031c3f6e5dcad536115e9f6a1fb5710b72391": "preview_OptiFine_1.16.1_HD_U_G2_pre2.jar",
|
||||
"da269cdb109b6441ebed179a8d9f974590006cbb": "preview_OptiFine_1.16.1_HD_U_G2_pre3.jar",
|
||||
"ad9b5d4e0bdd5697d4f6a6b9c30a64977bb8834e": "preview_OptiFine_1.16.1_HD_U_G2_pre4.jar",
|
||||
"f6d5ea6cedff1f80bed298f9b456de2448b1f268": "preview_OptiFine_1.16.1_HD_U_G2_pre5.jar",
|
||||
"f57a4269d404d4025de4d864b88510504e785e19": "preview_OptiFine_1.16.1_HD_U_G2_pre6.jar",
|
||||
"28b46592e2f510a246a8bf62a2b12e808094167c": "preview_OptiFine_1.16.1_HD_U_G2_pre7.jar",
|
||||
"26207daec6acc66250116e4f73295c9a031b491a": "preview_OptiFine_1.16.1_HD_U_G2_pre8.jar",
|
||||
"6902e902866b765ff8e31716980c2067d491e004": "preview_OptiFine_1.16.1_HD_U_G2_pre9.jar",
|
||||
"dde4274c696063496323731ad7b58e0f6d7265af": "OptiFine_1.16.2_HD_U_G3.jar",
|
||||
"5b5627a814e8c95aa7ac27fa4cf2cc0b8bd4519b": "OptiFine_1.16.2_HD_U_G5.jar",
|
||||
"5dafc10061020c519ba4abe4982a7dc80eae30f6": "preview_OptiFine_1.16.2_HD_U_G3_pre1.jar",
|
||||
"0c0d9828984da34f9d5967027c244bb2c83a4396": "preview_OptiFine_1.16.2_HD_U_G3_pre2.jar",
|
||||
"7c791e4348808132b8c7b59e6350ae2ee25a1918": "preview_OptiFine_1.16.2_HD_U_G3_pre3.jar",
|
||||
"9a30a2cbc81095d38b4dc17e7d30a4b84e10e235": "preview_OptiFine_1.16.2_HD_U_G3_pre4.jar",
|
||||
"ec1a4ce97e29c9ee2ac259d98e35bc386035a66d": "preview_OptiFine_1.16.2_HD_U_G3_pre5.jar",
|
||||
"e03b0cdfbbcf01b1371b3e1090dee64e60a759d8": "preview_OptiFine_1.16.2_HD_U_G3_pre6.jar",
|
||||
"40ae5fbb4e3724d6e5525d2688445c09ecfe5d39": "preview_OptiFine_1.16.2_HD_U_G3_pre7.jar",
|
||||
"dc83d55f74722e1e6f503817e00e5ba2e6cb4abb": "OptiFine_1.16.3_HD_U_G3.jar",
|
||||
"89d9472ee507946caa227e226524e5260ed944db": "OptiFine_1.16.3_HD_U_G4.jar",
|
||||
"6a8f57ca84628da47b011e608ff449cde7ec13ed": "OptiFine_1.16.3_HD_U_G5.jar",
|
||||
"f53bad69adaac9e523577dcb1c65b158802f3dee": "preview_OptiFine_1.16.3_HD_U_G3_pre2.jar",
|
||||
"5b970eeae2ba07316b3e3a53da1d71de87c9f9f2": "preview_OptiFine_1.16.3_HD_U_G4_pre1.jar",
|
||||
"599d9de6b656077b03fbebc9478924c548a6e9e0": "OptiFine_1.16.4_HD_U_G5.jar",
|
||||
"7cce72db1f77c6178b549fa430e241eadb318f69": "OptiFine_1.16.4_HD_U_G6.jar",
|
||||
"f49c042fa099907eafcbe22d7035d84f8b5715f1": "OptiFine_1.16.4_HD_U_G7.jar",
|
||||
"f0b1eb59acdae57e10bb928046ee26012ef220ad": "preview_OptiFine_1.16.4_HD_U_G6_pre12.jar",
|
||||
"ded7894eebda787b7bd5013aa8c66577f2ba03fc": "preview_OptiFine_1.16.4_HD_U_G7_pre1.jar",
|
||||
"57cfcec3dd99b9f0950d0d6305bc95ecd8563212": "OptiFine_1.16.5_HD_U_G6.jar",
|
||||
"706fb33784045d69e7ac5730426d813a39511dcd": "OptiFine_1.16.5_HD_U_G7.jar",
|
||||
"137363842c5bfdebb72356a4079faeef3157eb3c": "OptiFine_1.16.5_HD_U_G8.jar",
|
||||
"ad0f1577ce02a703185e46ee9e24fdeb65479fd5": "preview_OptiFine_1.16.5_HD_U_G7_pre10.jar",
|
||||
"11770876367980da546c68bc95c4f794582907f2": "preview_OptiFine_1.16.5_HD_U_G7_pre11.jar",
|
||||
"c5b32d2bad3bd54c31ddb29869a55fe44409cfe2": "preview_OptiFine_1.16.5_HD_U_G7_pre2.jar",
|
||||
"9fc9cc09e72a3060d577f62f3535820056b0c2d4": "preview_OptiFine_1.16.5_HD_U_G7_pre4.jar",
|
||||
"577fae32b60ac8d1b91e3fa645dc004d4a9e513e": "preview_OptiFine_1.16.5_HD_U_G7_pre5.jar",
|
||||
"4bccf7f371ac83523796b4706569cc5077c46aea": "preview_OptiFine_1.16.5_HD_U_G7_pre6.jar",
|
||||
"97b8d413a909354e2e0f42943cf292941dfe4c38": "preview_OptiFine_1.16.5_HD_U_G7_pre7.jar",
|
||||
"9c48a804ee990e526000c7061bde9aa6f75d62a6": "preview_OptiFine_1.16.5_HD_U_G7_pre8.jar",
|
||||
"9a9b69a52a4514f5d25c9f47127a822e93458fa3": "preview_OptiFine_1.16.5_HD_U_G7_pre9.jar",
|
||||
"d16a450ec4205d02319480799008d8177ba05d69": "preview_OptiFine_1.16.5_HD_U_G8_pre1.jar",
|
||||
"539c215aa0e8b8f696b325e89f3f9a3658ab32c8": "preview_OptiFine_1.16.5_HD_U_G8_pre10.jar",
|
||||
"311ed81a27d24047b6ade33f46ac21c962897016": "preview_OptiFine_1.16.5_HD_U_G8_pre11.jar",
|
||||
"4bc50f85809a299d30c3e3e73cfd87cad61cf7e6": "preview_OptiFine_1.16.5_HD_U_G8_pre12.jar",
|
||||
"7de1b8e9f7cd6d7ca941d90121e50206aba80c68": "preview_OptiFine_1.16.5_HD_U_G8_pre13.jar",
|
||||
"a4e021a75aab7ed1eb83ff7b45182d497aa930f2": "preview_OptiFine_1.16.5_HD_U_G8_pre14.jar",
|
||||
"0ddcc4f47e10b2d671ebbf9147eb16cf8f1bb92a": "preview_OptiFine_1.16.5_HD_U_G8_pre2.jar",
|
||||
"b8600be4fde6f8cc3c28173cc9bde6923956ff42": "preview_OptiFine_1.16.5_HD_U_G8_pre3.jar",
|
||||
"d46b10098309929d9a6583323f059e65ba40a5e5": "preview_OptiFine_1.16.5_HD_U_G8_pre4.jar",
|
||||
"e835424b36fdae3d5f46c6bf096852848127f1e7": "preview_OptiFine_1.16.5_HD_U_G8_pre5.jar",
|
||||
"5a84983221331597f58e9636d41f1fb0c9e3de61": "preview_OptiFine_1.16.5_HD_U_G8_pre6.jar",
|
||||
"d03ce2209d787539e5e51f80a7cbffc69a49ce9f": "preview_OptiFine_1.16.5_HD_U_G8_pre7.jar",
|
||||
"1c646dc058aa32898917589858d900407883ba3f": "preview_OptiFine_1.16.5_HD_U_G8_pre8.jar",
|
||||
"9bf369aa609e5612247473326dc298e6e6c9fecf": "preview_OptiFine_1.16.5_HD_U_G8_pre9.jar",
|
||||
"b88b05278fbbf400cb5ec0cc26e0c5b7306e0c1e": "preview_OptiFine_1.17_HD_U_G9_pre16.jar",
|
||||
"a6beac3091d17a4ba420116c75c89faeaee48539": "preview_OptiFine_1.17_HD_U_G9_pre17.jar",
|
||||
"2839c5e74f64500561ee3e8605c51d9f06727c22": "preview_OptiFine_1.17_HD_U_G9_pre18.jar",
|
||||
"455243ff55177189fff140ba7f4c4777cc977ef6": "preview_OptiFine_1.17_HD_U_G9_pre19.jar",
|
||||
"bcc390e34bd529a86e6ae0e25d50417f46ba482f": "preview_OptiFine_1.17_HD_U_G9_pre20.jar",
|
||||
"d30c23d745df54fd6ea2374e9c0b4392f0a78199": "preview_OptiFine_1.17_HD_U_G9_pre21.jar",
|
||||
"075e7f8473b6785c3c6bbeeb41a3728f6ff3c3ba": "preview_OptiFine_1.17_HD_U_G9_pre22.jar",
|
||||
"2ffaee36c717ae918cc93661df739740636900c9": "preview_OptiFine_1.17_HD_U_G9_pre23.jar",
|
||||
"4385493e7ce4c6f4a3945f1d96246bce0b151c7e": "preview_OptiFine_1.17_HD_U_G9_pre24.jar",
|
||||
"bcc33e67f134861eb30b1f0b9a829183c8afdebf": "preview_OptiFine_1.17_HD_U_G9_pre25.jar",
|
||||
"6a8e34605cc828091027d9f011b804d916808af4": "preview_OptiFine_1.17_HD_U_G9_pre26.jar",
|
||||
"4714bdceaefc15e0af9d5c72de8c05cf3051d760": "OptiFine_1.17.1_HD_U_G9.jar",
|
||||
"cc64084e73d6423baf7ccbdd9f3760dd56f2fce5": "OptiFine_1.17.1_HD_U_H1.jar",
|
||||
"1ee73bd882385a7abb9896b9469586741a6850a8": "preview_OptiFine_1.17.1_HD_U_G9_pre26.jar",
|
||||
"537c1fbee9ebe25b49a8e6fb93db162d217f1c83": "preview_OptiFine_1.17.1_HD_U_G9_pre27.jar",
|
||||
"68e1124f4b0376773d8d581da78f9bb6fc76486e": "preview_OptiFine_1.17.1_HD_U_G9_pre28.jar",
|
||||
"3f80a562b39439831491c650c65ecb762e70d5f7": "preview_OptiFine_1.17.1_HD_U_G9_pre29.jar",
|
||||
"9751c69568a61ce5b2a602b4dcf77c1cea57c709": "preview_OptiFine_1.17.1_HD_U_G9_pre30.jar",
|
||||
"162fa1b4a0d5b89dad6030b5fd5903a3f24d39c7": "preview_OptiFine_1.17.1_HD_U_G9_pre31.jar",
|
||||
"c4f3a33d892948927cecfaa60b284217325557c5": "preview_OptiFine_1.17.1_HD_U_G9_pre32.jar",
|
||||
"12dbbc8ccd8dc02806768fb3fbe4a2d8c7c11301": "preview_OptiFine_1.17.1_HD_U_G9_pre33.jar",
|
||||
"f74d1dfcb24c43e633076c63dd4492b29fca14b3": "preview_OptiFine_1.17.1_HD_U_G9_pre34.jar",
|
||||
"42e55681fb106475ef13d5c5ef7113fb631392d4": "preview_OptiFine_1.17.1_HD_U_G9_pre35.jar",
|
||||
"245ddb68349f5f81d067f4d9402d62db3d439d50": "preview_OptiFine_1.17.1_HD_U_H1_pre1.jar",
|
||||
"0ac033f5ba4089311af55172281e73df143b9ac4": "preview_OptiFine_1.17.1_HD_U_H1_pre2.jar",
|
||||
"68a3727bcdb0eac0986ff4e883d93fef406eb1c3": "preview_OptiFine_1.17.1_HD_U_H1_pre3.jar",
|
||||
"a6534877558f0438bda604dc43093d61c6de88ae": "preview_OptiFine_1.17.1_HD_U_H1_pre4.jar",
|
||||
"3e18db17b8695feb0a188ecdcb81b561d9d7030c": "preview_OptiFine_1.17.1_HD_U_H1_pre5.jar",
|
||||
"c2e0c4391a671cd06931f7b6e965085c04c58762": "preview_OptiFine_1.17.1_HD_U_H1_pre6.jar",
|
||||
"1ffb7325df5bf01b1d99bbbed8006a477b861394": "preview_OptiFine_1.17.1_HD_U_H1_pre7.jar",
|
||||
"f2fa727e67b4795ddea3cc37cc5ced42e9c3b0c5": "preview_OptiFine_1.17.1_HD_U_H1_pre8.jar",
|
||||
"aee167aff9937a068d514c85e3f8c766db99ed43": "preview_OptiFine_1.17.1_HD_U_H1_pre9.jar",
|
||||
"59709e09632c80ab80ffcc4b5bf380d073bf467f": "preview_OptiFine_1.17.1_HD_U_H2_pre1.jar",
|
||||
"52629bc5319ec203edd42de1776cb8d67a0e6b1b": "OptiFine_1.18_HD_U_H3.jar",
|
||||
"e5077b4be03956118613162b5dbcf90406b27747": "preview_OptiFine_1.18_HD_U_H3_pre7.jar",
|
||||
"326c160481cc849bccb821939992c101a8fe163b": "OptiFine_1.18.1_HD_U_H4.jar",
|
||||
"1679654fdf49fd1216b090d51d3d8ec906094619": "OptiFine_1.18.1_HD_U_H5.jar",
|
||||
"c86534ee821a78efc741c9cb407eec76625e1bab": "OptiFine_1.18.1_HD_U_H6.jar",
|
||||
"34c98382131efa1394cff06528238dc44d255794": "preview_OptiFine_1.18.1_HD_U_H4_pre1.jar",
|
||||
"d6cd0a2d0243ecad107220bff639eb46277762ea": "preview_OptiFine_1.18.1_HD_U_H5_pre1.jar",
|
||||
"e24d766864f9238b94be5b3ad65608d7eb521fad": "preview_OptiFine_1.18.1_HD_U_H5_pre2.jar",
|
||||
"bc1b87a6379acf5a5dca2a56196080ace49e4864": "preview_OptiFine_1.18.1_HD_U_H5_pre3.jar",
|
||||
"555a6b8e420bd63a04d9f338d61f3333b4ccf5d9": "preview_OptiFine_1.18.1_HD_U_H5_pre4.jar",
|
||||
"1d02694393f10992cbbd11a802568b0890b4dc81": "preview_OptiFine_1.18.1_HD_U_H5_pre5.jar",
|
||||
"e3a22adc6931dcd7513f26afa1adecd62a35641b": "preview_OptiFine_1.18.1_HD_U_H5_pre6.jar",
|
||||
"445a8cb8a1d565d8a1fabc0ba984c5936edbf561": "OptiFine_1.18.2_HD_U_H6.jar",
|
||||
"8d83d4d5e325887e000a462029d2bf7a6e77eebc": "OptiFine_1.18.2_HD_U_H7.jar",
|
||||
"2a461188167c1da6b1cfd520d85a656f45c74d70": "OptiFine_1.18.2_HD_U_H9.jar",
|
||||
"e591777f272df533b62260a6dbe0e6cde62066f6": "preview_OptiFine_1.18.2_HD_U_H6_pre1.jar",
|
||||
"f7c727c99e7f58ef9b86cc96fb894f628391016c": "preview_OptiFine_1.18.2_HD_U_H7_pre1.jar",
|
||||
"cb51529f94e0eff5b9da92c8f70b1d498662a115": "preview_OptiFine_1.18.2_HD_U_H7_pre2.jar",
|
||||
"ceea3c6612d2ec36c6ef1ca4a9355a141df9089f": "preview_OptiFine_1.18.2_HD_U_H7_pre3.jar",
|
||||
"596c3a399889c3e2ef1d8fee994e8cf5a91e457e": "preview_OptiFine_1.18.2_HD_U_H8_pre1.jar",
|
||||
"85f545aeb9d232ae970b0efa24e833f9c5d0fe33": "preview_OptiFine_1.18.2_HD_U_H9_pre1.jar",
|
||||
"b9bbabb8f31f2b04ca1a1a8645df1be701f705bd": "preview_OptiFine_1.18.2_HD_U_H9_pre2.jar",
|
||||
"c2bf213b4f60b6a29e402e3eaf219a4c85a5d1e6": "preview_OptiFine_1.18.2_HD_U_H9_pre3.jar",
|
||||
"64c8c35313d25a64a8a8677cb851a2c83556ddc8": "OptiFine_1.19_HD_U_H8.jar",
|
||||
"c668eaf9b6ce4e64f26a29fb27e830229ab4bef3": "OptiFine_1.19_HD_U_H9.jar",
|
||||
"cda9130e0d36f0d6e06950ab393a8929b502a440": "preview_OptiFine_1.19_HD_U_H8_pre1.jar",
|
||||
"cd1ae81f544672df776dc4426269771d2e82d0b2": "preview_OptiFine_1.19_HD_U_H8_pre10.jar",
|
||||
"cb3ad2a9b4c23675ca569058f55dd174eed67a38": "preview_OptiFine_1.19_HD_U_H8_pre2.jar",
|
||||
"2f7139ea0888ed18006e3a39b7475e88a3fd545d": "preview_OptiFine_1.19_HD_U_H8_pre3.jar",
|
||||
"85cd70bb714e65935abf351811889a47a648bb96": "preview_OptiFine_1.19_HD_U_H8_pre4.jar",
|
||||
"c65cbae4c1cfb5b9a5a573e4b83424df2d81b172": "preview_OptiFine_1.19_HD_U_H8_pre5.jar",
|
||||
"10cd57890ebf449f5c258cff1c60d8dcefcc6cf6": "preview_OptiFine_1.19_HD_U_H8_pre6.jar",
|
||||
"eb03e60c2fa9904a47eec1ef4a2c8804ee6cc0b8": "preview_OptiFine_1.19_HD_U_H8_pre7.jar",
|
||||
"40d0b514168161fdefc91594f0d3f774f831d7de": "preview_OptiFine_1.19_HD_U_H8_pre8.jar",
|
||||
"8e5bedd1c1a3641caad0743c4629db51c11f8feb": "preview_OptiFine_1.19_HD_U_H9_pre1.jar",
|
||||
"7745216a810f0d617ed17a520fda44e12456492f": "preview_OptiFine_1.19_HD_U_H9_pre2.jar",
|
||||
"6d6ed920eb99836e76549a7803af85881fcdee34": "preview_OptiFine_1.19_HD_U_H9_pre3.jar",
|
||||
"7604e63ca74876b4e8ea9c3a36dbe80111c776b1": "preview_OptiFine_1.19_HD_U_H9_pre4.jar",
|
||||
"b42dcce86c88ad4bcfa52a9d297afa800477aa78": "OptiFine_1.19.1_HD_U_H9.jar",
|
||||
"648427a02940b3c53b3678cd5df1ff6997bd1e6c": "preview_OptiFine_1.19.1_HD_U_H9_pre1.jar",
|
||||
"7cc221a6df6831b62c3b676b5f958e82a4487fd8": "OptiFine_1.19.2_HD_U_H9.jar",
|
||||
"f304c56c7ab9119ce4269f2b019cd4668f48376d": "OptiFine_1.19.2_HD_U_I1.jar",
|
||||
"708c1ecc148a0bd7d341e8f5e005e670903650f7": "OptiFine_1.19.2_HD_U_I2.jar",
|
||||
"e0e924990bd12cc499fc89439f0b2c50b6e147fa": "preview_OptiFine_1.19.2_HD_U_I1_pre1.jar",
|
||||
"465b229481c0a285195a168bf6581549a5cd7373": "preview_OptiFine_1.19.2_HD_U_I1_pre2.jar",
|
||||
"af4398d0ce0628bcc0204e61c157e3c8951b9ad5": "preview_OptiFine_1.19.2_HD_U_I1_pre3.jar",
|
||||
"35ef18f953bbea9a4cba6e14d0731d56f888114f": "preview_OptiFine_1.19.2_HD_U_I1_pre4.jar",
|
||||
"b24d8725a8d06e1c190d1d2b3bc51c899f74b36d": "OptiFine_1.19.3_HD_U_I3.jar",
|
||||
"f5ce615d9886e377079fd13cc4700a51d5a9c7b6": "preview_OptiFine_1.19.3_HD_U_I2_pre1.jar",
|
||||
"10e6d97ea2c97cf0575a7c587fb11e1ddb73aa71": "preview_OptiFine_1.19.3_HD_U_I2_pre2.jar",
|
||||
"89718cd608225d7c23f7e180d011daa4a47ce6bd": "preview_OptiFine_1.19.3_HD_U_I2_pre3.jar",
|
||||
"204baf0fd59432054aebd2f83c83fe15fdd3e611": "preview_OptiFine_1.19.3_HD_U_I2_pre4.jar",
|
||||
"90a52b3adc94a1b9a70f03f4c4ea14dfdf3424d8": "preview_OptiFine_1.19.3_HD_U_I2_pre5.jar",
|
||||
"050d787936cc239ea8db0aa250d2ed72a44fa71d": "OptiFine_1.19.4_HD_U_I3.jar",
|
||||
"253e9d29c6826c134fbdeea074d1d91c3410225c": "OptiFine_1.19.4_HD_U_I4.jar",
|
||||
"068b998d6c5db5b3bb7ad6a0a7d0c6aefc4dc191": "preview_OptiFine_1.19.4_HD_U_I3_pre3.jar",
|
||||
"a8b9156811c4745dbd612ebbb1df6f93d2fa734a": "preview_OptiFine_1.19.4_HD_U_I3_pre4.jar",
|
||||
"6f2793e6236a27c06a74e8a1876ea6b29be038a7": "preview_OptiFine_1.19.4_HD_U_I3_pre5.jar",
|
||||
"9ad82ef9f32fb5a0e7e7e0c7e8906c9f319a9705": "preview_OptiFine_1.19.4_HD_U_I3_pre6.jar",
|
||||
"cf02ee8b5cf6922fbc28d6ffc8183949fff55d49": "preview_OptiFine_1.19.4_HD_U_I3_pre7.jar",
|
||||
"702e0e9987209f66a5b4f825e7ac658fd49dd4b1": "preview_OptiFine_1.19.4_HD_U_I3_pre8.jar",
|
||||
"04c8eaeadf24c42929500812a8f3f5fe634f0d1f": "preview_OptiFine_1.19.4_HD_U_I3_pre9.jar",
|
||||
"968784e051c2c44f10a4ab448c979572977969c0": "preview_OptiFine_1.19.4_HD_U_I4_pre1.jar",
|
||||
"abe4b1dbda7d95eb47953ef8aa11ed8ed1d8e238": "preview_OptiFine_1.20_HD_U_I5_pre3.jar",
|
||||
"6612ce97d846018126f91cdbae23f0eff6bd2bab": "preview_OptiFine_1.20_HD_U_I5_pre4.jar",
|
||||
"f37d62b6a071f6cd3d6bdfa378b56f7f5b9ec7c6": "preview_OptiFine_1.20_HD_U_I5_pre5.jar",
|
||||
"c44bd276edd10fa910d929a4e7f6b8c7ffe6c23f": "OptiFine_1.20.1_HD_U_I5.jar",
|
||||
"227914ab1037762eae44a2e66a568b87d7a02365": "OptiFine_1.20.1_HD_U_I6.jar",
|
||||
"7484cf60bd8e019c40fa448daa294bd31f52962b": "preview_OptiFine_1.20.1_HD_U_I5_pre4.jar",
|
||||
"953b1b4f15f56329bc4ade851846ef004ebbd5d0": "preview_OptiFine_1.20.1_HD_U_I5_pre5.jar",
|
||||
"ebbdb3076f8291d7f8e10cbec3b1d3c49a0a701d": "preview_OptiFine_1.20.1_HD_U_I5_pre6.jar",
|
||||
"b831d59eeb63122893f87a62ec3e5b3f1cc5586a": "preview_OptiFine_1.20.1_HD_U_I5_pre7.jar",
|
||||
"dc93d991b07b2c5c6d2cd50a553599900c256264": "preview_OptiFine_1.20.1_HD_U_I5_pre8.jar",
|
||||
"040a73427541170bccf162c04f4129b3909b8da1": "preview_OptiFine_1.20.1_HD_U_I5_pre9.jar",
|
||||
"9f5fd75ede7770ad35383443b0a203f026cef5db": "preview_OptiFine_1.20.1_HD_U_I6_pre1.jar",
|
||||
"85d5566b440036b926c5dd306108e48e7fb9b6e8": "preview_OptiFine_1.20.1_HD_U_I6_pre2.jar",
|
||||
"d09c110358117336472414720bf961a581690dc5": "preview_OptiFine_1.20.1_HD_U_I6_pre3.jar",
|
||||
"3365577f5161f9a1bbfefb15675bb5ece27600e4": "preview_OptiFine_1.20.1_HD_U_I6_pre4.jar",
|
||||
"f317c701ae8c533fc34809dbce914bcced4e9a03": "preview_OptiFine_1.20.1_HD_U_I6_pre5.jar",
|
||||
"445b32b14a2c2802d5a0832a708712d2fabafbc2": "preview_OptiFine_1.20.1_HD_U_I6_pre6.jar",
|
||||
"9ea6b1389e88b81797f54b9d8d1e870d3fe8532c": "preview_OptiFine_1.20.2_HD_U_I7_pre1.jar",
|
||||
"f2475e8d5746477d1d2972cd5aae62a5d9c04264": "OptiFine_1.20.4_HD_U_I7.jar",
|
||||
"fec28a2fa152744bc2b47b3c5db3217974aa57ee": "preview_OptiFine_1.20.4_HD_U_I7_pre2.jar",
|
||||
"4068365a57f4b154f4ebbc670894dc9038cf3819": "preview_OptiFine_1.20.4_HD_U_I7_pre3.jar",
|
||||
"2481e0bed7610bfdfe26dea5a6bd8f01537c4ea8": "preview_OptiFine_1.20.4_HD_U_I7_pre4.jar",
|
||||
"150990ba068cfb55bbf9e525c5ab84db0b8903e1": "preview_OptiFine_1.20.4_HD_U_I7_pre5.jar",
|
||||
"fcf0f0f278d1901e4341d5ec8022bede03572430": "preview_OptiFine_1.20.4_HD_U_I7_pre6.jar",
|
||||
"1c3395efa928758505b808291162a2514523b399": "preview_OptiFine_1.20.4_HD_U_I7_pre7.jar",
|
||||
"4bcfec24e5d917ee96e0de6ef095bfc08782217b": "preview_OptiFine_1.20.4_HD_U_I8_pre1.jar",
|
||||
"c931df21309800f1d1ece59b318e6b3e39c98176": "preview_OptiFine_1.20.4_HD_U_I8_pre2.jar",
|
||||
"83f5ad147536405d86bf4ee47a7f7970e1fa8329": "preview_OptiFine_1.20.4_HD_U_I8_pre3.jar",
|
||||
"7c6669319db18c9622d896da2a061479be88acf5": "preview_OptiFine_1.20.4_HD_U_I8_pre4.jar",
|
||||
"bda7bbdd5e2d8c73148372e8c3b34d7c2485a4e8": "preview_OptiFine_1.20.6_HD_U_J1_pre18.jar",
|
||||
"24a20f949a719480523669669a4e5e7339df6186": "OptiFine_1.21.1_HD_U_J1.jar",
|
||||
"6bc74af8f733864539404a3f087aeb6f0cadc6b1": "preview_OptiFine_1.21.1_HD_U_J1_pre10.jar",
|
||||
"4813a6cde6b0f5b20223d486ba7adf0816236a77": "preview_OptiFine_1.21.1_HD_U_J1_pre11.jar",
|
||||
"c99ea98d8537f99b7470f1bcfa81c7ce405fd97e": "preview_OptiFine_1.21.1_HD_U_J1_pre12.jar",
|
||||
"f2f55e6d61a4b90bdaa31eb520f5af78f8a1833b": "preview_OptiFine_1.21.1_HD_U_J1_pre13.jar",
|
||||
"53a15b75ea9d20b644b0d3e9c7280ba172ade053": "preview_OptiFine_1.21.1_HD_U_J1_pre14.jar",
|
||||
"da5575e1138fc2a4c28779cfc5e7d260253ccbdb": "preview_OptiFine_1.21.1_HD_U_J1_pre15.jar",
|
||||
"0930876341beadf7014f57e9ebdd44be445dacbe": "OptiFine_1.21.3_HD_U_J2.jar",
|
||||
"350f63ec3e59d21227f803d4078883884e482660": "preview_OptiFine_1.21.3_HD_U_J2_pre10.jar",
|
||||
"2c48a4210acd25082c220f9601f17d1cc7769098": "preview_OptiFine_1.21.3_HD_U_J2_pre11.jar",
|
||||
"8f0a097b1f7c9d065396bac9230eab62412a8b54": "preview_OptiFine_1.21.3_HD_U_J2_pre12.jar",
|
||||
"f97df5638fcda7f4380d9284b329569d4e1554aa": "preview_OptiFine_1.21.3_HD_U_J2_pre8.jar",
|
||||
"77df50552deea51687592b60c6d24561ee11aa74": "preview_OptiFine_1.21.3_HD_U_J2_pre9.jar",
|
||||
"bcc6a7a140320566a985b1499a59fcb085255d14": "OptiFine_1.21.4_HD_U_J3.jar",
|
||||
"e7bff4575b7311e11d7111506e855b4ad910481f": "preview_OptiFine_1.21.4_HD_U_J3_pre10.jar",
|
||||
"52b2b6defdb8d1c189afb42125079f1b76ff72e2": "preview_OptiFine_1.21.4_HD_U_J3_pre11.jar",
|
||||
"708f70aa5f70919a40d02ba75fc30d2d60c8e7cd": "preview_OptiFine_1.21.4_HD_U_J3_pre12.jar",
|
||||
"86b796325d03d4571809a155c54cf6cba7c7482b": "preview_OptiFine_1.21.4_HD_U_J3_pre13.jar",
|
||||
"618719a6a69eb674ea50c82cffe3d4e3c2f67abf": "preview_OptiFine_1.21.4_HD_U_J3_pre14.jar",
|
||||
"98dd84b0581fcf7a27aa96238acb615b5b719825": "preview_OptiFine_1.21.4_HD_U_J3_pre15.jar",
|
||||
"5c8573ae921082a069a9e7269500178f0c580c97": "preview_OptiFine_1.21.4_HD_U_J3_pre4.jar",
|
||||
"17d51e1bfc94dde95368ab91aa0bb56e89c706bf": "preview_OptiFine_1.21.4_HD_U_J3_pre5.jar",
|
||||
"1a4311606ac94d682800714793ea887c3276b18d": "preview_OptiFine_1.21.4_HD_U_J3_pre6.jar",
|
||||
"12bf1a1b5607b5c62405a8c6ab4e1bd02bef2c8d": "preview_OptiFine_1.21.4_HD_U_J3_pre7.jar",
|
||||
"f1f56403dc575fd975a000bfb133060285cacd6f": "preview_OptiFine_1.21.4_HD_U_J3_pre8.jar",
|
||||
"9584de0e8204f5c855b60741d58af967dfc599a8": "preview_OptiFine_1.21.4_HD_U_J3_pre9.jar",
|
||||
"0b97631e166ace9346744de33b7772df7bf5fbeb": "preview_OptiFine_1.21.4_HD_U_J4_pre1.jar",
|
||||
"a776ec6c072529d4b731e0e6929a8aee56d04e76": "preview_OptiFine_1.21.4_HD_U_J4_pre2.jar",
|
||||
"2a3fdfcbfc9c572b22c7d15c6129b8ea58ea1712": "preview_OptiFine_1.21.8_HD_U_J6_pre13.jar",
|
||||
"ba7ca56c9b33d2fc7f7d22e20ce2765e4d257299": "preview_OptiFine_1.21.8_HD_U_J6_pre14.jar",
|
||||
"097d3acd315b0682a4565ab9cbb4cdc1cc17d81f": "preview_OptiFine_1.21.8_HD_U_J6_pre15.jar",
|
||||
"d7effca0de34c9442f06776848679a684f1f166a": "preview_OptiFine_1.21.8_HD_U_J6_pre16.jar",
|
||||
"696ed52cdfd0ac2eafab8e98575d146b400de530": "preview_OptiFine_1.21.10_HD_U_J7_pre10.jar",
|
||||
"be7bc81f0c9ce15316f76a059b0a690f62c03a5d": "preview_OptiFine_1.21.10_HD_U_J7_pre11.jar",
|
||||
"731e698abe94bbd90cda628f80b4ce6ac5af8188": "preview_OptiFine_1.21.10_HD_U_J7_pre6.jar",
|
||||
"63f189a4b7ff5b53e5bc56391f5274eab8555def": "preview_OptiFine_1.21.10_HD_U_J7_pre7.jar",
|
||||
"7ccfc0085ccdef3f4ec21918a059ddd05171711a": "preview_OptiFine_1.21.10_HD_U_J7_pre8.jar",
|
||||
"aad53ad3346944a12ed088ffbba98cf9eccc474c": "preview_OptiFine_1.21.10_HD_U_J7_pre9.jar",
|
||||
"2800efeb56565f23494d372e7edac29333113e11": "OptiFine_1.21.11_HD_U_J8.jar",
|
||||
"cee35b83c75da33dc07ffbb344386cae5cdc5c5b": "OptiFine_1.21.11_HD_U_J9.jar",
|
||||
"687046282c370e690ec4b4aa6d90a6b42ada3ee1": "preview_OptiFine_1.21.11_HD_U_J8_pre10.jar",
|
||||
"c09b05637610072bc33023d1c901e2726b7d16ce": "preview_OptiFine_1.21.11_HD_U_J8_pre11.jar",
|
||||
"88682b40f08f462c4daae8e6b25ea99b1ed5cd14": "preview_OptiFine_1.21.11_HD_U_J8_pre12.jar",
|
||||
"1c69de725c249d487df676470c71a7c05ece943c": "preview_OptiFine_1.21.11_HD_U_J8_pre3.jar",
|
||||
"921b68e014539d75b77a47ca2117fc9e09ace4ed": "preview_OptiFine_1.21.11_HD_U_J8_pre4.jar",
|
||||
"2fee496a35eeebf39fbb43017b12d752f5c37dee": "preview_OptiFine_1.21.11_HD_U_J8_pre5.jar",
|
||||
"e5b5da810d43c02dccedfd99f83ae8ad63e49966": "preview_OptiFine_1.21.11_HD_U_J8_pre6.jar",
|
||||
"8309638bab4d99258354b4d13c3cd82ff7c9645b": "preview_OptiFine_1.21.11_HD_U_J8_pre7.jar",
|
||||
"b3d208ec7b5015842fcc77b3399b04b24e0b54cb": "preview_OptiFine_1.21.11_HD_U_J8_pre8.jar",
|
||||
"c4ac7401bb5f668e4e92121501d1537cdbe7343c": "preview_OptiFine_1.21.11_HD_U_J8_pre9.jar",
|
||||
"fd96031b5fc4c41ad71581eea53eb122022fff5b": "preview_OptiFine_1.21.11_HD_U_J9_pre1.jar",
|
||||
"a159a217aba02cd7897394a51bd346c3b658b9f9": "preview_OptiFine_1.21.11_HD_U_J9_pre2.jar",
|
||||
"2a784437076b539ea1a3c1a37ec98ff21bc3952c": "preview_OptiFine_1.21.11_HD_U_J9_pre3.jar",
|
||||
"ee32ce4b406e290a0e99685afd09dc44cad03ddd": "preview_OptiFine_1.21.11_HD_U_J9_pre4.jar"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<OutDir>bin\</OutDir>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<Configurations>Debug;Release;Snapshot</Configurations>
|
||||
<Deterministic>true</Deterministic>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="PolySharp" Version="1.15.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="System.Diagnostics.CodeAnalysis" />
|
||||
<Using Include="System.Collections" />
|
||||
<Using Include="System.Collections.Generic" />
|
||||
<Using Include="System.Collections.Concurrent" />
|
||||
<Using Include="System.Text" />
|
||||
<Using Include="System.Text.RegularExpressions" />
|
||||
<Using Include="System.Net" />
|
||||
<Using Include="System.Runtime.CompilerServices" />
|
||||
<Using Include="System.Runtime.InteropServices" />
|
||||
<Using Include="Newtonsoft.Json" />
|
||||
<Using Include="Newtonsoft.Json.Linq" />
|
||||
<Using Include="MeloongCore" />
|
||||
<Using Include="MeloongCore.Extensions" />
|
||||
<Using Include="MeloongCore.Wpf" />
|
||||
<Using Include="MeloongCore.Wpf.Extensions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MeloongCore\Shared\MeloongCore.csproj" />
|
||||
<ProjectReference Include="..\MeloongCore\Wpf\MeloongCore.Wpf.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>$(DefineConstants);SNAPSHOT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'" />
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Snapshot|AnyCPU'">
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- 内嵌的资源 -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Launch\JavaWrapper.jar" LogicalName="Launch\JavaWrapper.jar" />
|
||||
<EmbeddedResource Include="Launch\LwjglUnsafeAgent.jar" LogicalName="Launch\LwjglUnsafeAgent.jar" />
|
||||
<EmbeddedResource Include="Resource\WikiEntries.txt" LogicalName="Resource\WikiEntries.txt" />
|
||||
<EmbeddedResource Include="Minecraft\OptiFineHashes.json" LogicalName="Minecraft\OptiFineHashes.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\Plain Craft Launcher 2\Resources\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace PCLCS;
|
||||
public static class Resource {
|
||||
|
||||
/// <summary>
|
||||
/// 将 CurseForge 的 ModLoaderType 映射为 <see cref="ModLoaders"/> 枚举。
|
||||
/// </summary>
|
||||
public static ModLoaders FromCurseForgeModLoaderType(int modLoaderType) => modLoaderType switch {
|
||||
// https://docs.curseforge.com/rest-api/#tocS_ModLoaderType
|
||||
1 => ModLoaders.Forge,
|
||||
3 => ModLoaders.LiteLoader,
|
||||
4 => ModLoaders.Fabric,
|
||||
6 => ModLoaders.NeoForge,
|
||||
_ => ModLoaders.None,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 将 <see cref="ModLoaders"/> 映射为 CurseForge 的单一 ModLoaderType。
|
||||
/// </summary>
|
||||
public static int ToCurseForgeModLoaderType(ModLoaders loaders) {
|
||||
if (loaders == ModLoaders.None) return 0;
|
||||
if (!loaders.Flags().IsSingle()) throw new ArgumentException($"传入的枚举值 {loaders} 并非单个 Mod Loader", nameof(loaders));
|
||||
return loaders switch {
|
||||
ModLoaders.Forge => 1,
|
||||
ModLoaders.LiteLoader => 3,
|
||||
ModLoaders.Fabric => 4,
|
||||
ModLoaders.NeoForge => 6,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,83 @@
|
||||
namespace PCLCS;
|
||||
|
||||
/// <summary>
|
||||
/// MC 百科条目。
|
||||
/// <para/>使用 <see cref="WikiEntry.All"/> 获取所有条目。
|
||||
/// </summary>
|
||||
public class WikiEntry {
|
||||
|
||||
/// <summary>
|
||||
/// 在 MC 百科中的对应 ID。
|
||||
/// </summary>
|
||||
public int Id;
|
||||
/// <summary>
|
||||
/// 中文译名。
|
||||
/// <c>null</c> 代表没有翻译。
|
||||
/// </summary>
|
||||
public string? ChineseName;
|
||||
/// <summary>
|
||||
/// 各个 Mod 平台的 Slug(例如 advanced-solar-panels)。
|
||||
/// 若没有对应键则为不在该平台上。
|
||||
/// </summary>
|
||||
public Dictionary<ResourcePlatforms, string> Slugs = [];
|
||||
/// <summary>
|
||||
/// MC 百科的浏览量逆序排行,1 代表浏览量最低。
|
||||
/// </summary>
|
||||
public int Popularity;
|
||||
|
||||
/// <summary>
|
||||
/// 内置数据库中的所有 MC 百科条目。
|
||||
/// </summary>
|
||||
public static readonly Lazy<List<WikiEntry>> All = new(() => {
|
||||
var dataLines = FileUtils.ReadAsLines(@"Resource\WikiEntries.txt", type: typeof(WikiEntry)).ToList();
|
||||
|
||||
// 读取最后一行的浏览量
|
||||
Queue<int> popularities = [];
|
||||
for (int i = 0; i < dataLines[^1].Length; i += 3) { // 将每 3 个字符切割成一个元素
|
||||
popularities.Enqueue(int.Parse(dataLines[^1].Substring(i, 3).ConvertRadix(86, 10)));
|
||||
}
|
||||
dataLines.RemoveAt(dataLines.Count - 1);
|
||||
|
||||
// 解析每一行
|
||||
List<WikiEntry> results = [];
|
||||
var lineNumber = 0;
|
||||
foreach (var lineData in dataLines) {
|
||||
lineNumber++;
|
||||
if (lineData == "") continue;
|
||||
|
||||
var popularity = popularities.Dequeue();
|
||||
foreach (var entryData in lineData.Split('¨')) {
|
||||
var entry = new WikiEntry();
|
||||
var parts = entryData.Split('|');
|
||||
var slugs = parts[0];
|
||||
if (slugs.StartsWithF("@")) {
|
||||
entry.Slugs.Add(ResourcePlatforms.Modrinth, slugs.Replace("@", ""));
|
||||
} else if (slugs.EndsWithF("@")) {
|
||||
entry.Slugs.Add(ResourcePlatforms.CurseForge, slugs.TrimEnd('@'));
|
||||
entry.Slugs.Add(ResourcePlatforms.Modrinth, entry.Slugs[ResourcePlatforms.CurseForge]);
|
||||
} else if (slugs.Contains("@")) {
|
||||
var splitSlugs = slugs.Split('@');
|
||||
entry.Slugs.Add(ResourcePlatforms.CurseForge, splitSlugs[0]);
|
||||
entry.Slugs.Add(ResourcePlatforms.Modrinth, splitSlugs[1]);
|
||||
} else {
|
||||
entry.Slugs.Add(ResourcePlatforms.CurseForge, slugs);
|
||||
}
|
||||
|
||||
entry.Id = lineNumber;
|
||||
entry.Popularity = popularity;
|
||||
if (parts.Length >= 2) {
|
||||
entry.ChineseName = parts[^1]; // 最后一项
|
||||
if (entry.ChineseName.Contains("*")) { // 处理 *
|
||||
var englishName = entry.Slugs.Values.First().Replace("-", " ").Capitalize();
|
||||
entry.ChineseName = entry.ChineseName.Replace("*", $" ({englishName})");
|
||||
}
|
||||
}
|
||||
results.Add(entry);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
});
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Slugs.GetOrDefault(ResourcePlatforms.CurseForge)}&{Slugs.GetOrDefault(ResourcePlatforms.Modrinth)}|{Id}|{ChineseName},浏览量 {Popularity}";
|
||||
}
|
||||
@@ -5,22 +5,49 @@ VisualStudioVersion = 17.2.32616.157
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Plain Craft Launcher 2", "Plain Craft Launcher 2\Plain Craft Launcher 2.vbproj", "{4499F2D2-18EF-4238-B1F0-7BD547AFF925}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCLCS", "PCLCS\PCLCS.csproj", "{32DE4AAF-F4D6-42C5-9604-19236DE96C69}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeloongCore", "MeloongCore\Shared\MeloongCore.csproj", "{D22BBF3C-2177-0208-4E29-8466AA41AC6F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeloongCore.Wpf", "MeloongCore\Wpf\MeloongCore.Wpf.csproj", "{2DFFE2A0-1101-816B-D240-F644B2D74A68}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Beta|Any CPU = Beta|Any CPU
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
ReleaseUpdate|Any CPU = ReleaseUpdate|Any CPU
|
||||
Snapshot|Any CPU = Snapshot|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Beta|Any CPU.ActiveCfg = BETA|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Beta|Any CPU.Build.0 = BETA|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.ReleaseUpdate|Any CPU.ActiveCfg = ReleaseUpdate|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.ReleaseUpdate|Any CPU.Build.0 = ReleaseUpdate|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
|
||||
{4499F2D2-18EF-4238-B1F0-7BD547AFF925}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
|
||||
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
|
||||
{32DE4AAF-F4D6-42C5-9604-19236DE96C69}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
|
||||
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Snapshot|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AFB035DA-CB49-49CE-B96F-581BF8368CCF}.Snapshot|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
|
||||
{D22BBF3C-2177-0208-4E29-8466AA41AC6F}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
|
||||
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Snapshot|Any CPU.ActiveCfg = Snapshot|Any CPU
|
||||
{2DFFE2A0-1101-816B-D240-F644B2D74A68}.Snapshot|Any CPU.Build.0 = Snapshot|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.net>
|
||||
<settings>
|
||||
<httpWebRequest useUnsafeHeaderParsing="true"/>
|
||||
</settings>
|
||||
</system.net>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
|
||||
<system.net>
|
||||
<settings>
|
||||
<httpWebRequest useUnsafeHeaderParsing="true"/>
|
||||
</settings>
|
||||
</system.net>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2"/>
|
||||
</startup>
|
||||
<runtime>
|
||||
<AppContextSwitchOverrides value="Switch.System.Net.DontEnableSchUseStrongCrypto=false" />
|
||||
<generatePublisherEvidence enabled="false"/>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<runtime>
|
||||
<generatePublisherEvidence enabled="false"/>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="13.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -1,18 +1,22 @@
|
||||
<Application x:Class="Application"
|
||||
<Application x:Class="Application"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="clr-namespace:PCL.Controls.Behaviors"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
xmlns:core="clr-namespace:MeloongCore;assembly=MeloongCore"
|
||||
xmlns:corewpf="clr-namespace:MeloongCore.Wpf;assembly=MeloongCore.Wpf"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
StartupUri="FormMain.xaml"
|
||||
ShutdownMode="OnExplicitShutdown">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<!-- 本地化 -->
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Language/zh_CN.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Language/zh_CN.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Converter -->
|
||||
<corewpf:AdditionConverter x:Key="AdditionConverter"/>
|
||||
<corewpf:MultiplicationConverter x:Key="MultiplicationConverter"/>
|
||||
<corewpf:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
|
||||
<corewpf:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
|
||||
<!-- 颜色表 -->
|
||||
<SolidColorBrush x:Key="ColorBrush1">#343d4a</SolidColorBrush>
|
||||
@@ -59,7 +63,7 @@
|
||||
<SolidColorBrush x:Key="ColorBrushWhite" Color="#ffffff" />
|
||||
<SolidColorBrush x:Key="ColorBrushTransparent" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ColorBrushSemiTransparent">#01eaf2fe</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="ColorBrushBackgroundTransparentSidebar" Color="#D2FFFFFF" />
|
||||
<SolidColorBrush x:Key="ColorBrushBackgroundTransparentSidebar" Color="#F1FFFFFF" />
|
||||
|
||||
<!-- Window -->
|
||||
<Style TargetType="{x:Type Window}" x:Key="MyWindow">
|
||||
@@ -128,33 +132,6 @@
|
||||
<Setter Property="FontFamily" Value="Resources/#PCL English, Microsoft YaHei UI" />
|
||||
</Style>
|
||||
|
||||
<!-- ToolTip -->
|
||||
<Style TargetType="ToolTip">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Border Loaded="TooltipLoaded" Unloaded="TooltipUnloaded" BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1.5" CornerRadius="4" MaxWidth="500" Background="#E5FFFFFF" Margin="0,0,4,4" SnapsToDevicePixels="True">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Opacity="0.3" BlurRadius="4" ShadowDepth="2" Color="{DynamicResource ColorObject1}" />
|
||||
</Border.Effect>
|
||||
<TextBlock Text="{TemplateBinding ContentControl.Content}" TextWrapping="Wrap" Foreground="{DynamicResource ColorBrush1}" Margin="7,5" FontSize="12"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="ExtendTextToolTip" TargetType="ToolTip">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Border Loaded="TooltipLoaded" Unloaded="TooltipUnloaded" BorderBrush="{DynamicResource ColorBrush1}" BorderThickness="1" CornerRadius="3" Background="White" Margin="0,0,4,4" SnapsToDevicePixels="True">
|
||||
<TextBlock Text="{TemplateBinding ContentControl.Content}" TextWrapping="Wrap" Foreground="{DynamicResource ColorBrush1}" Margin="7,5" FontSize="12"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ScrollViewer -->
|
||||
<Style x:Key="MyThumb" TargetType="Thumb">
|
||||
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
|
||||
@@ -202,7 +179,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="local:MyScrollViewer">
|
||||
<Style TargetType="local:MyScrollViewer" x:Name="StyleMyScrollViewer">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="CanContentScroll" Value="False" />
|
||||
<Setter Property="PanningMode" Value="VerticalOnly" />
|
||||
@@ -218,6 +195,7 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="local:PageResource" BasedOn="{StaticResource {x:Type local:MyScrollViewer}}" />
|
||||
<Style TargetType="ScrollViewer" x:Key="ScrollViewerFullMargin">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="CanContentScroll" Value="False" />
|
||||
@@ -281,25 +259,26 @@
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ColorBrushBg0}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TextBox">
|
||||
<Grid SnapsToDevicePixels="True" ToolTip="{TemplateBinding ToolTip}">
|
||||
<ControlTemplate TargetType="local:MyTextBox">
|
||||
<Grid SnapsToDevicePixels="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MinHeight="28" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border x:Name="border" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" CornerRadius="3">
|
||||
<Border x:Name="border" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<ScrollViewer x:Name="PART_ContentHost" Style="{StaticResource ScrollViewerNoMargin}" RenderOptions.ClearTypeHint="Enabled" BorderThickness="0"
|
||||
Margin="5,0,0,0" Cursor="IBeam" />
|
||||
<!-- 由于未知原因,TextView 会有 2,0 的 Margin -->
|
||||
</Border>
|
||||
<Border Margin="{TemplateBinding Padding}" IsHitTestVisible="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" BorderThickness="0">
|
||||
<TextBlock x:Name="labHint" Foreground="{DynamicResource ColorBrush1}" Opacity="0.25" Margin="7,0,0,0" Padding="{TemplateBinding BorderThickness}" FontSize="{TemplateBinding FontSize}" FontFamily="Resources/#PCL English, Microsoft YaHei UI" />
|
||||
<TextBlock x:Name="labHint" Foreground="{StaticResource ColorBrushGray4}" Margin="7,0,0,0" Padding="{TemplateBinding BorderThickness}" FontSize="{TemplateBinding FontSize}" FontFamily="Resources/#PCL English, Microsoft YaHei UI" />
|
||||
</Border>
|
||||
<TextBlock Grid.Row="1" x:Name="labWrong" Height="0" Foreground="{TemplateBinding BorderBrush}" Padding="0,4,0,0" />
|
||||
<TextBlock Grid.Row="1" x:Name="labWrong" Height="0" Foreground="{TemplateBinding BorderBrush}" Padding="0,4,0,0" Margin="0,0,0,-3" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
|
||||
</Style>
|
||||
<Style TargetType="PasswordBox">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
@@ -341,6 +320,7 @@
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="behaviors:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
|
||||
</Style>
|
||||
|
||||
<!-- ComboBox -->
|
||||
@@ -360,7 +340,7 @@
|
||||
<Setter Property="Background" Value="{StaticResource ColorBrushHalfWhite}" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MaxDropDownHeight" Value="160" />
|
||||
<Setter Property="MaxDropDownHeight" Value="320" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
@@ -466,11 +446,6 @@
|
||||
</Style>
|
||||
<Style TargetType="ComboBoxItem" x:Key="MyComboBoxItem">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="ToolTipService.Placement" Value="Relative" />
|
||||
<Setter Property="ToolTipService.VerticalOffset" Value="-2" />
|
||||
<Setter Property="ToolTipService.HorizontalOffset" Value="200" />
|
||||
<Setter Property="ToolTipService.InitialShowDelay" Value="150" />
|
||||
<Setter Property="ToolTipService.BetweenShowDelay" Value="150" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="Background" Value="{StaticResource ColorBrushTransparent}" />
|
||||
<Setter Property="Template">
|
||||
|
||||
@@ -1,37 +1,60 @@
|
||||
Imports System.Net
|
||||
Imports System.Reflection
|
||||
Imports System.Windows.Threading
|
||||
|
||||
Public Class Application
|
||||
|
||||
#If DEBUG Then
|
||||
''' <summary>
|
||||
''' 用于开始程序时的一些测试。
|
||||
''' </summary>
|
||||
Private Sub Test()
|
||||
Try
|
||||
ModDevelop.Start()
|
||||
Catch ex As Exception
|
||||
Log(ex, "开发者模式测试出错", LogLevel.Msgbox)
|
||||
End Try
|
||||
End Sub
|
||||
#End If
|
||||
|
||||
'开始
|
||||
Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
|
||||
Try
|
||||
SecretOnApplicationStart()
|
||||
'核心初始化
|
||||
MeloongCore.Main.Init("PCL")
|
||||
MeloongCore.Wpf.Main.Init()
|
||||
If Not (e.Args.Contains("--update") OrElse e.Args.Contains("--gpu") OrElse e.Args.Contains("--memory")) Then '若是通过命令行调用,则不检查单例、不轮换日志(#9137)
|
||||
WaitingMutex(e)
|
||||
Logger.Instance = New PclLogger With {.logFolder = Paths.BaseThenName, .MinLevel = If(ModeDebug, LogLevel.Trace, LogLevel.Info)}
|
||||
End If
|
||||
'提升主线程优先级
|
||||
Thread.CurrentThread.Priority = ThreadPriority.Highest
|
||||
'执行开发版测试
|
||||
If BuildType = BuildTypes.Debug Then
|
||||
Try
|
||||
ModDevelop.Start()
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex, "开发者模式测试出错", LogBehavior.Alert)
|
||||
End Try
|
||||
End If
|
||||
'检查 .NET Framework 版本
|
||||
Try
|
||||
If CInt(RegistryUtils.TryRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full", "Release", 999999)) < 528040 Then OldEnvironmentAssert()
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, "检查 .NET Framework 版本失败")
|
||||
End Try
|
||||
'修复 WPF 字体加载(#3467)
|
||||
Try
|
||||
Dim _unused As New FormattedText("", Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Fonts.SystemTypefaces.First, 96, New MyColor, DPI)
|
||||
Catch ex As UriFormatException
|
||||
Environment.SetEnvironmentVariable("windir", Environment.GetEnvironmentVariable("SystemRoot"), EnvironmentVariableTarget.User)
|
||||
Dim _unused As New FormattedText("", Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Fonts.SystemTypefaces.First, 96, New MyColor, DPI)
|
||||
End Try
|
||||
'检查参数调用
|
||||
If e.Args.Length > 0 Then
|
||||
If e.Args(0) = "--update" Then
|
||||
'自动更新
|
||||
UpdateReplace(e.Args(1), e.Args(2).Trim(""""), e.Args(3).Trim(""""), e.Args(4))
|
||||
Environment.Exit(Result.Cancel)
|
||||
ElseIf e.Args(0).StartsWithF("--memory") Then
|
||||
Environment.Exit(ProcessReturnValues.TaskDone)
|
||||
ElseIf e.Args(0) = "--gpu" Then
|
||||
'调整显卡设置
|
||||
Try
|
||||
SetGPUPreference(e.Args(1).Trim(""""))
|
||||
Environment.Exit(ProcessReturnValues.TaskDone)
|
||||
Catch ex As Exception
|
||||
Environment.Exit(ProcessReturnValues.Fail)
|
||||
End Try
|
||||
ElseIf e.Args(0) = "--memory" Then
|
||||
'内存优化
|
||||
Dim Ram = My.Computer.Info.AvailablePhysicalMemory
|
||||
Try
|
||||
PageOtherTest.MemoryOptimizeInternal()
|
||||
PageOtherTest.MemoryOptimizeInternal(False)
|
||||
Catch ex As Exception
|
||||
MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败")
|
||||
Environment.Exit(-1)
|
||||
@@ -41,203 +64,215 @@ Public Class Application
|
||||
Else
|
||||
Environment.Exit((My.Computer.Info.AvailablePhysicalMemory - Ram) / 1024) '返回清理的内存量(K)
|
||||
End If
|
||||
#If DEBUG Then
|
||||
'制作更新包
|
||||
ElseIf e.Args(0) = "--edit1" Then
|
||||
ExeEdit(e.Args(1), True)
|
||||
Environment.Exit(Result.Cancel)
|
||||
ElseIf e.Args(0) = "--edit2" Then
|
||||
ExeEdit(e.Args(1), False)
|
||||
Environment.Exit(Result.Cancel)
|
||||
#End If
|
||||
End If
|
||||
End If
|
||||
'初始化文件结构
|
||||
Directory.CreateDirectory(Path & "PCL\Pictures")
|
||||
Directory.CreateDirectory(Path & "PCL\Musics")
|
||||
Try
|
||||
Directory.CreateDirectory(PathTemp)
|
||||
If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限")
|
||||
DirectoryUtils.Create(Paths.Base & "PCL\Pictures\")
|
||||
DirectoryUtils.Create(Paths.Base & "PCL\Musics\")
|
||||
CheckPermissionWithException(Paths.Base & "PCL\")
|
||||
Catch ex As Exception
|
||||
If PathTemp = IO.Path.GetTempPath() & "PCL\" Then
|
||||
MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
|
||||
MsgBox($"PCL 没有对当前文件夹的权限({Paths.Base}PCL\),请尝试:" & vbCrLf &
|
||||
"1. 将 PCL 移动到其他文件夹" & If(PathUtils.GetDiskName(Paths.Base) = "C"c, ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf &
|
||||
"2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf &
|
||||
"3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。",
|
||||
MsgBoxStyle.Critical, "运行环境错误")
|
||||
Environment.[Exit](ProcessReturnValues.Cancel)
|
||||
End Try
|
||||
RetryCacheCheck:
|
||||
Try
|
||||
DirectoryUtils.Create(PathTemp)
|
||||
CheckPermissionWithException(PathTemp)
|
||||
Catch ex As Exception
|
||||
If PathUtils.IsSame(PathTemp, Path.GetTempPath() & "PCL\") Then
|
||||
MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
|
||||
Else
|
||||
MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用")
|
||||
Setup.Set("SystemSystemCache", "")
|
||||
PathTemp = IO.Path.GetTempPath() & "PCL\"
|
||||
MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
|
||||
Settings.Set("SystemSystemCache", "")
|
||||
PathTemp = Path.GetTempPath() & "PCL\"
|
||||
GoTo RetryCacheCheck
|
||||
End If
|
||||
End Try
|
||||
Directory.CreateDirectory(PathTemp & "Cache")
|
||||
Directory.CreateDirectory(PathTemp & "Download")
|
||||
Directory.CreateDirectory(PathAppdata)
|
||||
'检测单例
|
||||
#If Not DEBUG Then
|
||||
Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ")
|
||||
If WindowHwnd = IntPtr.Zero Then FindWindow(Nothing, "Plain Craft Launcher 2 ")
|
||||
If WindowHwnd <> IntPtr.Zero Then
|
||||
'将已有的 PCL 窗口拖出来
|
||||
ShowWindowToTop(WindowHwnd)
|
||||
'播放提示音并退出
|
||||
Beep()
|
||||
Environment.[Exit](Result.Cancel)
|
||||
End If
|
||||
#End If
|
||||
DirectoryUtils.Create(PathTemp & "Cache\")
|
||||
DirectoryUtils.Create(Paths.AppDataThenName)
|
||||
'设置 ToolTipService 默认值
|
||||
ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(300))
|
||||
ToolTipService.BetweenShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(400))
|
||||
ToolTipService.ShowDurationProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(9999999))
|
||||
ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom))
|
||||
ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0))
|
||||
ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0))
|
||||
ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(100))
|
||||
'设置网络配置默认值
|
||||
ServicePointManager.Expect100Continue = False
|
||||
ServicePointManager.DefaultConnectionLimit = 10000
|
||||
ServicePointManager.UseNagleAlgorithm = False
|
||||
ServicePointManager.EnableDnsRoundRobin = True
|
||||
ServicePointManager.ReusePort = True
|
||||
If Environment.OSVersion.Version < New Version(6, 2) Then 'Windows 7 回退(#8519)
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
|
||||
End If
|
||||
'设置初始窗口
|
||||
If Setup.Get("UiLauncherLogo") Then
|
||||
If Settings.Get(Of Boolean)("UiLauncherLogo") Then
|
||||
FrmStart = New SplashScreen("Images\icon.ico")
|
||||
FrmStart.Show(False, True)
|
||||
End If
|
||||
'动态 DLL 调用
|
||||
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve
|
||||
'日志初始化
|
||||
LogStart()
|
||||
'添加日志
|
||||
Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode})")
|
||||
Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
|
||||
Log($"[Start] 程序路径:{PathWithName}")
|
||||
Log($"[Start] 系统编码:{Encoding.Default} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
|
||||
Log($"[Start] 管理员权限:{IsAdmin()}")
|
||||
'检测压缩包运行
|
||||
If Path.Contains(IO.Path.GetTempPath()) OrElse Path.Contains("AppData\Local\Temp\") Then
|
||||
MyMsgBox("PCL 正在临时文件夹运行,设置、游戏存档等很可能无法保存,且部分功能会无法使用或出错。" & vbCrLf & "请将 PCL 从压缩文件中解压,或是更换文件夹后再继续使用!", "环境警告", "我知道了", IsWarn:=True)
|
||||
'基础信息
|
||||
Logger.Info($"程序版本:{VersionDisplay} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
|
||||
If BuildType = BuildTypes.Snapshot Then
|
||||
Logger.Info($"识别码:{Identify}{If(ThemeCheckOne(9), ",已解锁反馈主题", ",未解锁反馈主题")}")
|
||||
Else
|
||||
Logger.Info($"识别码:{Identify}")
|
||||
End If
|
||||
Logger.Info($"程序路径:{PathExe}")
|
||||
Logger.Info($"系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
|
||||
Logger.Info($"管理员权限:{WindowsUtils.HasAdminRole()}")
|
||||
'检测异常环境
|
||||
If Paths.Base.Contains(Path.GetTempPath()) OrElse Paths.Base.Contains("AppData\Local\Temp\") Then
|
||||
MyMsgBox("请将 PCL 从压缩包中解压后再使用!" & vbCrLf & "如果不会解压,可以在网上寻找教程。", "需要解压!", "我知道了", IsWarn:=True, ForceWait:=True)
|
||||
FormMain.EndProgramForce(ProcessReturnValues.Cancel)
|
||||
End If
|
||||
If Not Environment.Is64BitOperatingSystem Then
|
||||
MyMsgBox("PCL 和新版 Minecraft 均不再支持 32 位系统,请重装为 64 位系统后再进行游戏!", "环境警告", "我知道了", IsWarn:=True, ForceWait:=True)
|
||||
FormMain.EndProgramForce(ProcessReturnValues.Cancel)
|
||||
End If
|
||||
'设置初始化
|
||||
Setup.Load("SystemDebugMode")
|
||||
Setup.Load("SystemDebugAnim")
|
||||
Setup.Load("ToolDownloadThread")
|
||||
Setup.Load("ToolDownloadCert")
|
||||
'网络配置初始化
|
||||
ServicePointManager.Expect100Continue = True
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
|
||||
ServicePointManager.DefaultConnectionLimit = 1024
|
||||
'计时
|
||||
Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
|
||||
ApplicationStartTick = GetTimeTick()
|
||||
'执行测试
|
||||
#If DEBUG Then
|
||||
Test()
|
||||
#End If
|
||||
Logger.Info($"第一阶段加载用时:{GetTimeMs() - ApplicationStartTick} ms")
|
||||
ApplicationStartTick = GetTimeMs()
|
||||
AniControlEnabled += 1
|
||||
Catch ex As Exception
|
||||
Dim FilePath As String = Nothing
|
||||
Try
|
||||
FilePath = PathWithName
|
||||
FilePath = PathExe
|
||||
Catch
|
||||
End Try
|
||||
MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
|
||||
FormMain.EndProgramForce(Result.Exception)
|
||||
MsgBox(ex.GetDisplay(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
|
||||
FormMain.EndProgramForce(ProcessReturnValues.Exception)
|
||||
End Try
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 要求程序以单例运行。
|
||||
''' 如果已在运行,则将其窗口拖出来并播放提示音,然后结束当前进程。
|
||||
''' </summary>
|
||||
Private Shared Sub WaitingMutex(e As StartupEventArgs)
|
||||
If BuildType = BuildTypes.Debug Then Return
|
||||
Try
|
||||
Dim MutexCreatedNew As Boolean
|
||||
PclMutex = New Mutex(True, "PCL_SingletonMutex", MutexCreatedNew)
|
||||
If MutexCreatedNew Then Return
|
||||
Logger.Warn("已有一个 PCL 实例正在运行")
|
||||
'等待已有的 PCL 退出
|
||||
If e.Args.Length > 0 AndAlso e.Args(0) = "--wait" Then
|
||||
Try
|
||||
If PclMutex.WaitOne(10000) Then Return '等待至多 10 秒
|
||||
Catch Ignored As AbandonedMutexException
|
||||
Return '已有实例异常退出,继续启动
|
||||
End Try
|
||||
End If
|
||||
'将已有的 PCL 窗口拖出来
|
||||
Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ")
|
||||
If WindowHwnd = IntPtr.Zero Then WindowHwnd = FindWindow(Nothing, "Plain Craft Launcher 2 ")
|
||||
If WindowHwnd <> IntPtr.Zero Then ShowWindowToTop(WindowHwnd)
|
||||
'播放提示音并退出
|
||||
Beep()
|
||||
Environment.[Exit](ProcessReturnValues.Cancel)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, "单例检查失败")
|
||||
End Try
|
||||
End Sub
|
||||
Private Shared PclMutex As Mutex
|
||||
|
||||
'结束
|
||||
Private Sub Application_SessionEnding(sender As Object, e As SessionEndingCancelEventArgs) Handles Me.SessionEnding
|
||||
FrmMain.EndProgram(False)
|
||||
FrmMain?.EndProgram(False)
|
||||
End Sub
|
||||
|
||||
'异常
|
||||
Private IsCritErrored As Boolean = False
|
||||
Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
|
||||
On Error Resume Next
|
||||
e.Handled = True
|
||||
If IsProgramEnded Then Exit Sub
|
||||
If IsCritErrored Then
|
||||
'在汇报错误后继续引发错误,知道这次压不住了
|
||||
FormMain.EndProgramForce(Result.Exception)
|
||||
Exit Sub
|
||||
'触发页面的 Dispatcher
|
||||
If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then
|
||||
CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
|
||||
If e.Handled Then Return
|
||||
End If
|
||||
IsCritErrored = True
|
||||
Dim ExceptionString As String = GetExceptionDetail(e.Exception, True)
|
||||
If ExceptionString.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse
|
||||
ExceptionString.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse
|
||||
ExceptionString.Contains(".NET Framework") OrElse ' “自动错误判断” 的结果分析
|
||||
ExceptionString.Contains("未能加载文件或程序集") Then
|
||||
OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer")
|
||||
MsgBox("你的 .NET Framework 版本过低或损坏,请在打开的网页中重新下载并安装 .NET Framework 4.6.2 后重试!", MsgBoxStyle.Information, "运行环境错误")
|
||||
FormMain.EndProgramForce(Result.Cancel)
|
||||
If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then
|
||||
CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
|
||||
If e.Handled Then Return
|
||||
End If
|
||||
'正常处理
|
||||
e.Handled = True
|
||||
If IsProgramEnding Then Return
|
||||
FeedbackInfo()
|
||||
Dim Detail As String = e.Exception.GetDisplay(True)
|
||||
If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse
|
||||
Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析
|
||||
OldEnvironmentAssert(e.Exception)
|
||||
Else
|
||||
FeedbackInfo()
|
||||
Log(e.Exception, "程序出现未知错误", LogLevel.Assert, "锟斤拷烫烫烫")
|
||||
Logger.Error(e.Exception, "程序出现未知错误", LogBehavior.AlertThenCrash)
|
||||
End If
|
||||
End Sub
|
||||
Private Shared Sub OldEnvironmentAssert(Optional ex As Exception = Nothing)
|
||||
OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net48-offline-installer")
|
||||
If ex Is Nothing Then
|
||||
Logger.Error($"你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.8!{vbCrLf}若安装失败,可以先卸载高版本的 .NET Framework 然后再试。", LogBehavior.AlertThenCrash)
|
||||
Else
|
||||
Logger.Error(ex, $"你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.8!{vbCrLf}若安装失败,可以先卸载高版本的 .NET Framework 然后再试。", LogBehavior.AlertThenCrash)
|
||||
End If
|
||||
End Sub
|
||||
|
||||
'动态 DLL 调用
|
||||
Private Shared AssemblyNAudio As Assembly
|
||||
Private Shared AssemblyJson As Assembly
|
||||
Private Shared AssemblyDialog As Assembly
|
||||
Private Shared ReadOnly AssemblyNAudioLock As New Object
|
||||
Private Shared ReadOnly AssemblyJsonLock As New Object
|
||||
Private Shared ReadOnly AssemblyDialogLock As New Object
|
||||
Public Shared Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
|
||||
If args.Name.StartsWithF("NAudio") Then
|
||||
SyncLock AssemblyNAudioLock
|
||||
If AssemblyNAudio Is Nothing Then
|
||||
Log("[Start] 加载 DLL:NAudio")
|
||||
AssemblyNAudio = Assembly.Load(GetResources("NAudio"))
|
||||
End If
|
||||
Return AssemblyNAudio
|
||||
End SyncLock
|
||||
ElseIf args.Name.StartsWithF("Newtonsoft.Json") Then
|
||||
SyncLock AssemblyJsonLock
|
||||
If AssemblyJson Is Nothing Then
|
||||
Log("[Start] 加载 DLL:Json")
|
||||
AssemblyJson = Assembly.Load(GetResources("Json"))
|
||||
End If
|
||||
Return AssemblyJson
|
||||
End SyncLock
|
||||
ElseIf args.Name.StartsWithF("Ookii.Dialogs.Wpf") Then
|
||||
SyncLock AssemblyDialogLock
|
||||
If AssemblyDialog Is Nothing Then
|
||||
Log("[Start] 加载 DLL:Dialogs")
|
||||
AssemblyDialog = Assembly.Load(GetResources("Dialogs"))
|
||||
End If
|
||||
Return AssemblyDialog
|
||||
End SyncLock
|
||||
Else
|
||||
Return Nothing
|
||||
End If
|
||||
End Function
|
||||
|
||||
'切换窗口
|
||||
'动态 DLL 加载
|
||||
Private Sub New() '这里必须尽早调用,且不能使用任何库,否则加载 MeloongCore 就会导致崩溃
|
||||
Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "ThrottleDebounce", "System.Net.Http.Formatting", "Microsoft.Win32.Registry", "PCLCS", "MeloongCore.Wpf", "MeloongCore"}
|
||||
Static LoadedAssemblies As New ConcurrentDictionary(Of String, Lazy(Of Assembly))(StringComparer.Ordinal) '缓存
|
||||
AddHandler AppDomain.CurrentDomain.AssemblyResolve,
|
||||
Function(sender As Object, Args As ResolveEventArgs) As Assembly
|
||||
Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWith(p, StringComparison.Ordinal))
|
||||
If Prefix Is Nothing Then Return Nothing
|
||||
Dim LazyAssembly = LoadedAssemblies.GetOrAdd(Prefix, Function(p) New Lazy(Of Assembly)(
|
||||
Function()
|
||||
Dim LoadedAssembly = Assembly.Load(DirectCast(My.Resources.ResourceManager.GetObject(p), Byte()))
|
||||
If p = "Imazen.WebP" Then ExtractLibwebp() 'WebP 特判
|
||||
Return LoadedAssembly
|
||||
End Function, LazyThreadSafetyMode.ExecutionAndPublication))
|
||||
Return LazyAssembly.Value
|
||||
End Function
|
||||
End Sub
|
||||
Private Shared Sub ExtractLibwebp() '这个方法会调用 ModBase,进而调用 MeloongCore,所以不能放在 New 里
|
||||
SetDllDirectory(PathUtils.RemoveSlashSuffix(PathPure.Value))
|
||||
Try
|
||||
ExtractResources(PathPure.Value & "libwebp.dll", "libwebp64")
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, "写入 libwebp.dll 失败") '防止同时加载多个图片时,同时写入文件导致文件占用,进而导致崩溃
|
||||
End Try
|
||||
End Sub
|
||||
Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
|
||||
|
||||
'控件模板事件
|
||||
Private Sub MyIconButton_Click(sender As Object, e As EventArgs)
|
||||
Select Case Setup.Get("LoginType")
|
||||
Select Case Settings.Get(Of McLoginType)("LoginType")
|
||||
Case McLoginType.Ms
|
||||
'微软
|
||||
Dim MsJson As JObject = GetJson(Setup.Get("LoginMsJson"))
|
||||
Dim MsJson As JObject = Settings.Get(Of String)("LoginMsJson").DeserializeJson()
|
||||
MsJson.Remove(sender.Tag)
|
||||
Setup.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
|
||||
Settings.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
|
||||
If FrmLoginMs.ComboAccounts.SelectedItem Is sender.Parent Then FrmLoginMs.ComboAccounts.SelectedIndex = 0
|
||||
FrmLoginMs.ComboAccounts.Items.Remove(sender.Parent)
|
||||
Case McLoginType.Legacy
|
||||
'离线
|
||||
Dim Names As New List(Of String)
|
||||
Names.AddRange(Setup.Get("LoginLegacyName").ToString.Split("¨"))
|
||||
Names.AddRange(Settings.Get(Of String)("LoginLegacyName").ToString.Split("¨"))
|
||||
Names.Remove(sender.Tag)
|
||||
Setup.Set("LoginLegacyName", Join(Names, "¨"))
|
||||
Settings.Set("LoginLegacyName", Names.Join("¨"c))
|
||||
FrmLoginLegacy.ComboName.ItemsSource = Names
|
||||
FrmLoginLegacy.ComboName.Text = If(Names.Any, Names(0), "")
|
||||
Case Else
|
||||
'第三方
|
||||
Dim Token As String = GetStringFromEnum(Setup.Get("LoginType"))
|
||||
Dim Token As String = Settings.Get(Of McLoginType)("LoginType").ToString()
|
||||
Dim Dict As New Dictionary(Of String, String)
|
||||
Dim Names As New List(Of String)
|
||||
Dim Passs As New List(Of String)
|
||||
If Not Setup.Get("Login" & Token & "Email") = "" Then Names.AddRange(Setup.Get("Login" & Token & "Email").ToString.Split("¨"))
|
||||
If Not Setup.Get("Login" & Token & "Pass") = "" Then Passs.AddRange(Setup.Get("Login" & Token & "Pass").ToString.Split("¨"))
|
||||
If Not Settings.Get(Of String)("Login" & Token & "Email") = "" Then Names.AddRange(Settings.Get(Of String)("Login" & Token & "Email").Split("¨"))
|
||||
If Not Settings.Get(Of String)("Login" & Token & "Pass") = "" Then Passs.AddRange(Settings.Get(Of String)("Login" & Token & "Pass").Split("¨"))
|
||||
For i = 0 To Names.Count - 1
|
||||
Dict.Add(Names(i), Passs(i))
|
||||
Next
|
||||
Dict.Remove(sender.Tag)
|
||||
Setup.Set("Login" & Token & "Email", Join(Dict.Keys, "¨"))
|
||||
Setup.Set("Login" & Token & "Pass", Join(Dict.Values, "¨"))
|
||||
Settings.Set("Login" & Token & "Email", Dict.Keys.Join("¨"c))
|
||||
Settings.Set("Login" & Token & "Pass", Dict.Values.Join("¨"c))
|
||||
Select Case Token
|
||||
Case "Nide"
|
||||
FrmLoginNide.ComboName.ItemsSource = Dict.Keys
|
||||
@@ -251,12 +286,4 @@ Public Class Application
|
||||
End Select
|
||||
End Sub
|
||||
|
||||
Public Shared ShowingTooltips As New List(Of Border)
|
||||
Private Sub TooltipLoaded(sender As Border, e As EventArgs)
|
||||
ShowingTooltips.Add(sender)
|
||||
End Sub
|
||||
Private Sub TooltipUnloaded(sender As Border, e As RoutedEventArgs)
|
||||
ShowingTooltips.Remove(sender)
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -0,0 +1,216 @@
|
||||
' Author: uye (owner of the MaaAssistantArknights team)
|
||||
' Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights
|
||||
' License: Apache License 2.0 (this file only)
|
||||
'
|
||||
' This file is originally developed for MaaAssistantArknights and licensed under AGPL v3.0.
|
||||
' As the original author and copyright holder,
|
||||
' I hereby re-license this file under the Apache License 2.0 for use in the Plain Craft Launcher project.
|
||||
'
|
||||
' This file is contributed under the spirit of good faith and open cooperation.
|
||||
' It does not implement any core launcher functions of PCL.
|
||||
'
|
||||
' Description:
|
||||
' Provides a WPF clipboard handling fix to avoid OpenClipboard exceptions
|
||||
' in TextBox, RichTextBox, and DataGrid under Windows focus issues or clipboard hooks.
|
||||
'
|
||||
' Date: 2025-07-03
|
||||
|
||||
Namespace Controls.Behaviors
|
||||
Public NotInheritable Class ClipboardInterceptor
|
||||
Private Sub New()
|
||||
End Sub
|
||||
|
||||
Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty =
|
||||
DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor),
|
||||
New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged))
|
||||
|
||||
Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean)
|
||||
element.SetValue(EnableSafeClipboardProperty, value)
|
||||
End Sub
|
||||
|
||||
Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean
|
||||
Return element.GetValue(EnableSafeClipboardProperty)
|
||||
End Function
|
||||
|
||||
Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then
|
||||
AddCommandBindingsToTextBox(DirectCast(d, TextBox))
|
||||
ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then
|
||||
AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox))
|
||||
ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then
|
||||
AddCommandBindingsToDataGrid(DirectCast(d, DataGrid))
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox)
|
||||
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox))
|
||||
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox, AddressOf CanEditTextBox))
|
||||
tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox, AddressOf CanEditTextBox))
|
||||
End Sub
|
||||
|
||||
Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox)
|
||||
rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox))
|
||||
rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox))
|
||||
rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox))
|
||||
End Sub
|
||||
|
||||
Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid)
|
||||
dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid))
|
||||
End Sub
|
||||
|
||||
Private Shared Sub SetClipboardText(text As String)
|
||||
Forms.Clipboard.SetDataObject(text, False, retryTimes:=10, retryDelay:=100) '实际的复制
|
||||
Try
|
||||
System.Windows.Clipboard.Flush() '将数据持久化到系统剪贴板,避免应用程序退出后数据丢失
|
||||
Catch ex As Exception '持久化失败不抛出异常
|
||||
Logger.Warn(ex, $"剪贴板持久化失败(错误码:0x{ex.HResult:X8})")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim tb = TryCast(sender, TextBox)
|
||||
If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return
|
||||
|
||||
Try
|
||||
SetClipboardText(tb.SelectedText)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
End Try
|
||||
|
||||
e.Handled = True
|
||||
End Sub
|
||||
|
||||
Private Shared Sub CanEditTextBox(sender As Object, e As CanExecuteRoutedEventArgs)
|
||||
Dim tb = TryCast(sender, TextBox)
|
||||
e.CanExecute = tb IsNot Nothing AndAlso Not tb.IsReadOnly AndAlso
|
||||
(e.Command IsNot ApplicationCommands.Cut OrElse tb.SelectionLength > 0)
|
||||
e.Handled = True
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim tb = TryCast(sender, TextBox)
|
||||
e.Handled = True
|
||||
If tb Is Nothing OrElse tb.IsReadOnly OrElse tb.SelectionLength <= 0 Then Return
|
||||
|
||||
Try
|
||||
SetClipboardText(tb.SelectedText)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
Return
|
||||
End Try
|
||||
|
||||
tb.SelectedText = String.Empty
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim tb = TryCast(sender, TextBox)
|
||||
e.Handled = True
|
||||
If tb Is Nothing OrElse tb.IsReadOnly Then Return
|
||||
|
||||
Try
|
||||
Dim pasteText = Retrier.Attempt(func:=
|
||||
Function(Attempt) As String
|
||||
Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing)
|
||||
End Function, maxAttempts:=10, delay:=Function() TimeSpan.FromMilliseconds(100))
|
||||
If pasteText IsNot Nothing Then
|
||||
|
||||
'将换行符替换为空格(#7825)
|
||||
If Not tb.AcceptsReturn Then pasteText = pasteText.ReplaceLineEndings(" ", mergeMultiple:=True)
|
||||
|
||||
Dim start = tb.SelectionStart
|
||||
|
||||
tb.SelectedText = pasteText
|
||||
tb.CaretIndex = start + pasteText.Length
|
||||
tb.SelectionLength = 0
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim rtb = TryCast(sender, RichTextBox)
|
||||
If rtb Is Nothing Then Return
|
||||
|
||||
Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End)
|
||||
If String.IsNullOrEmpty(textRange.Text) Then Return
|
||||
|
||||
Try
|
||||
SetClipboardText(textRange.Text)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
End Try
|
||||
|
||||
e.Handled = True
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim rtb = TryCast(sender, RichTextBox)
|
||||
If rtb Is Nothing Then Return
|
||||
|
||||
Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End)
|
||||
If String.IsNullOrEmpty(selection.Text) Then Return
|
||||
e.Handled = True
|
||||
|
||||
Try
|
||||
SetClipboardText(selection.Text)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"剪切失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
Return
|
||||
End Try
|
||||
|
||||
selection.Text = String.Empty
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim rtb = TryCast(sender, RichTextBox)
|
||||
If rtb Is Nothing Then Return
|
||||
|
||||
Try
|
||||
Dim pasteText = Retrier.Attempt(func:=
|
||||
Function(Attempt) As String
|
||||
Return If(Forms.Clipboard.ContainsText(), Forms.Clipboard.GetText(), Nothing)
|
||||
End Function, maxAttempts:=2, delay:=Function() TimeSpan.FromMilliseconds(200))
|
||||
If pasteText Is Nothing Then Return
|
||||
Dim selection = rtb.Selection
|
||||
|
||||
selection.Text = pasteText
|
||||
|
||||
Dim caretPos = selection.End
|
||||
rtb.CaretPosition = caretPos
|
||||
rtb.Selection.Select(caretPos, caretPos)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"粘贴失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
End Try
|
||||
|
||||
e.Handled = True
|
||||
End Sub
|
||||
|
||||
Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs)
|
||||
Dim dg = TryCast(sender, DataGrid)
|
||||
If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return
|
||||
|
||||
Dim sb = New StringBuilder
|
||||
Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item)
|
||||
|
||||
For Each row In rowGroups
|
||||
Dim rowText = String.Join(vbTab, row.Select(
|
||||
Function(cell)
|
||||
Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock)
|
||||
Return If(tb IsNot Nothing, tb.Text, "")
|
||||
End Function))
|
||||
sb.AppendLine(rowText)
|
||||
Next
|
||||
|
||||
Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf)
|
||||
|
||||
Try
|
||||
SetClipboardText(sbStr)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"复制失败,请稍后再试(错误码:0x{ex.HResult:X8})", LogBehavior.Toast)
|
||||
End Try
|
||||
|
||||
e.Handled = True
|
||||
End Sub
|
||||
End Class
|
||||
End Namespace
|
||||
@@ -0,0 +1,23 @@
|
||||
Imports System.Windows.Threading
|
||||
|
||||
''' <summary>
|
||||
''' 指示页面可以通过按下 F5 或以其他方式刷新。
|
||||
''' </summary>
|
||||
Public Interface IRefreshable
|
||||
Sub Refresh()
|
||||
End Interface
|
||||
|
||||
''' <summary>
|
||||
''' 指示页面可以处理未捕获的异常。
|
||||
''' </summary>
|
||||
Public Interface IDispatcherUnhandledException
|
||||
Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs)
|
||||
End Interface
|
||||
|
||||
''' <summary>
|
||||
''' 指示该控件是可勾选的。
|
||||
''' </summary>
|
||||
Public Interface IMyRadio
|
||||
Event Check(sender As Object, e As RouteEventArgs)
|
||||
Event Changed(sender As Object, e As RouteEventArgs)
|
||||
End Interface
|
||||
@@ -0,0 +1,46 @@
|
||||
Public Class LazyLoadBehavior
|
||||
|
||||
''' <summary>
|
||||
''' 指定首次进入 ScrollViewer 的可视范围时执行的操作。
|
||||
''' </summary>
|
||||
Public Shared Sub OnFirstEnterScrollViewerViewport(obj As DependencyObject, value As Action)
|
||||
obj.SetValue(IsInViewportProperty, value)
|
||||
End Sub
|
||||
|
||||
Private Shared ReadOnly IsInViewportProperty As DependencyProperty =
|
||||
DependencyProperty.RegisterAttached("IsInViewport", GetType(Action), GetType(LazyLoadBehavior),
|
||||
New PropertyMetadata(Nothing, AddressOf OnIsInViewportChanged))
|
||||
|
||||
Private Shared Sub OnIsInViewportChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
Dim Element = TryCast(d, FrameworkElement)
|
||||
If Element Is Nothing OrElse e.NewValue Is Nothing Then Return
|
||||
|
||||
Dim Handled As Boolean = False
|
||||
Dim Handler As EventHandler =
|
||||
Sub()
|
||||
If Handled Then Return
|
||||
'判断元素是否可见
|
||||
If Element.RenderSize.Width < Double.Epsilon Then Return
|
||||
If Not Element.IsVisible Then Return
|
||||
'判断是否在 ScrollViewer 的可视区域内
|
||||
Dim Scroll = FindParentScrollViewer(Element)
|
||||
If Scroll Is Nothing Then Return
|
||||
If Not New Rect(0, 0, Scroll.ViewportWidth, Scroll.ViewportHeight).IntersectsWith(
|
||||
Element.TransformToAncestor(Scroll).TransformBounds(New Rect(New Point(0, 0), Element.RenderSize))) Then Return
|
||||
'执行
|
||||
Handled = True
|
||||
CType(e.NewValue, Action).Invoke()
|
||||
RemoveHandler Element.LayoutUpdated, Handler
|
||||
End Sub
|
||||
AddHandler Element.LayoutUpdated, Handler
|
||||
End Sub
|
||||
|
||||
Private Shared Function FindParentScrollViewer(d As DependencyObject) As ScrollViewer
|
||||
While d IsNot Nothing
|
||||
If TypeOf d Is ScrollViewer Then Return CType(d, ScrollViewer)
|
||||
d = VisualTreeHelper.GetParent(d)
|
||||
End While
|
||||
Return Nothing
|
||||
End Function
|
||||
|
||||
End Class
|
||||
@@ -1,4 +0,0 @@
|
||||
Public Interface IMyRadio
|
||||
Event Check(sender As Object, e As RouteEventArgs)
|
||||
Event Changed(sender As Object, e As RouteEventArgs)
|
||||
End Interface
|
||||
@@ -1,4 +1,4 @@
|
||||
<Border x:Class="MyButton"
|
||||
<Border x:Class="MyButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
FocusVisualStyle="{x:Null}" x:Name="PanBack"
|
||||
@@ -7,7 +7,7 @@
|
||||
<Border.RenderTransform>
|
||||
<ScaleTransform />
|
||||
</Border.RenderTransform>
|
||||
<TextBlock TextOptions.TextFormattingMode="Display" SnapsToDevicePixels="True" TextTrimming="None" Text="Button" HorizontalAlignment="Center" x:Name="LabText" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" Foreground="{Binding Parent.BorderBrush, RelativeSource={RelativeSource Self}}" FontSize="13" IsHitTestVisible="False">
|
||||
<TextBlock TextOptions.TextFormattingMode="Display" SnapsToDevicePixels="True" TextTrimming="None" HorizontalAlignment="Center" x:Name="LabText" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" Foreground="{Binding Parent.BorderBrush, RelativeSource={RelativeSource Self}}" FontSize="13" IsHitTestVisible="False">
|
||||
<TextBlock.RenderTransform>
|
||||
<ScaleTransform />
|
||||
</TextBlock.RenderTransform>
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
Public Class MyButton
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyButton
|
||||
|
||||
'声明
|
||||
Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
|
||||
|
||||
'自定义属性
|
||||
Public Uuid As Integer = GetUuid()
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return LabText.Text
|
||||
Return GetValue(TextProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
LabText.Text = value
|
||||
SetValue(TextProperty, value)
|
||||
End Set
|
||||
End Property '显示文本
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyButton), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyButton).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If sender IsNot Nothing Then CType(sender, MyButton).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Public Property TextPadding As Thickness
|
||||
Get
|
||||
Return LabText.Padding
|
||||
@@ -128,43 +136,18 @@
|
||||
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新按钮颜色出错")
|
||||
Logger.Warn(ex, "刷新按钮颜色出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
'实现自定义事件
|
||||
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
|
||||
If Not IsMouseDown Then Exit Sub
|
||||
Log("[Control] 按下按钮:" & Text)
|
||||
RaiseEvent Click(sender, e)
|
||||
If Not String.IsNullOrEmpty(Tag) Then
|
||||
If Tag.ToString.StartsWithF("链接-") OrElse Tag.ToString.StartsWithF("启动-") Then
|
||||
Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!")
|
||||
End If
|
||||
End If
|
||||
ModEvent.TryStartEvent(EventType, EventData)
|
||||
End Sub
|
||||
Public Property EventType As String
|
||||
Get
|
||||
Return GetValue(EventTypeProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventTypeProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing))
|
||||
Public Property EventData As String
|
||||
Get
|
||||
Return GetValue(EventDataProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventDataProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing))
|
||||
|
||||
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
|
||||
Private IsMouseDown As Boolean = False
|
||||
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
|
||||
If Not IsMouseDown Then Return
|
||||
Logger.Info($"按下按钮:{Text}")
|
||||
RaiseEvent Click(sender, e)
|
||||
RaiseCustomEvent() '自定义事件
|
||||
End Sub
|
||||
Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
|
||||
IsMouseDown = True
|
||||
Focus()
|
||||
@@ -177,7 +160,7 @@
|
||||
AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid)
|
||||
End Sub
|
||||
Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
|
||||
If Not IsMouseDown Then Exit Sub
|
||||
If Not IsMouseDown Then Return
|
||||
IsMouseDown = False
|
||||
AniStart({
|
||||
AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle))
|
||||
@@ -185,7 +168,7 @@
|
||||
End Sub
|
||||
Private Sub Button_MouseLeave() Handles Me.MouseLeave
|
||||
AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid)
|
||||
If Not IsMouseDown Then Exit Sub
|
||||
If Not IsMouseDown Then Return
|
||||
IsMouseDown = False
|
||||
AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid)
|
||||
End Sub
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Public Class MyCard
|
||||
Public Class MyCard
|
||||
|
||||
'控件
|
||||
Inherits Grid
|
||||
Private ReadOnly MainGrid As Grid
|
||||
Private ReadOnly MainChrome As SystemDropShadowChrome
|
||||
Public ReadOnly Property MainChrome As MyDropShadow
|
||||
Private ReadOnly MainBorder As Border
|
||||
Public Property BorderChild As UIElement
|
||||
Get
|
||||
@@ -36,6 +36,20 @@
|
||||
|
||||
'属性
|
||||
Public Uuid As Integer = GetUuid()
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return MainTextBlock.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property CornerRadius As CornerRadius
|
||||
Get
|
||||
Return MainChrome.CornerRadius
|
||||
End Get
|
||||
Set(value As CornerRadius)
|
||||
MainChrome.CornerRadius = value
|
||||
MainBorder.CornerRadius = value
|
||||
End Set
|
||||
End Property
|
||||
Public Property Title As String
|
||||
Get
|
||||
Return GetValue(TitleProperty)
|
||||
@@ -49,20 +63,21 @@
|
||||
|
||||
'UI 建立
|
||||
Public Sub New()
|
||||
MainChrome = New SystemDropShadowChrome With {.Margin = New Thickness(-9.5, -9, 0.5, -0.5), .Opacity = 0.1, .CornerRadius = New CornerRadius(6)}
|
||||
MainChrome.SetResourceReference(SystemDropShadowChrome.ColorProperty, "ColorObject1")
|
||||
MainChrome = New MyDropShadow With {
|
||||
.Margin = New Thickness(-3, -3, -3, -3 - GetWPFSize(1)), .ShadowRadius = 3, .Opacity = DropShadowIdleOpacity, .CornerRadius = New CornerRadius(5)}
|
||||
MainChrome.SetResourceReference(MyDropShadow.ColorProperty, "ColorObject1")
|
||||
Children.Insert(0, MainChrome)
|
||||
MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(205, 255, 255, 255)), .CornerRadius = New CornerRadius(6), .IsHitTestVisible = False}
|
||||
MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(245, 255, 255, 255)), .CornerRadius = New CornerRadius(5), .IsHitTestVisible = False}
|
||||
Children.Insert(1, MainBorder)
|
||||
MainGrid = New Grid
|
||||
Children.Add(MainGrid)
|
||||
End Sub
|
||||
Private IsLoad As Boolean = False
|
||||
Private Sub Init() Handles Me.Loaded
|
||||
If IsLoad Then Exit Sub
|
||||
If IsLoad Then Return
|
||||
IsLoad = True
|
||||
'初次加载限定
|
||||
If Title <> "" AndAlso MainTextBlock Is Nothing Then
|
||||
If MainTextBlock Is Nothing Then
|
||||
MainTextBlock = New TextBlock With {.HorizontalAlignment = HorizontalAlignment.Left, .VerticalAlignment = VerticalAlignment.Top, .Margin = New Thickness(15, 12, 0, 0), .FontWeight = FontWeights.Bold, .FontSize = 13, .IsHitTestVisible = False}
|
||||
MainTextBlock.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrush1")
|
||||
MainTextBlock.SetBinding(TextBlock.TextProperty, New Binding("Title") With {.Source = Me, .Mode = BindingMode.OneWay})
|
||||
@@ -75,8 +90,9 @@
|
||||
MainGrid.Children.Add(MainSwap)
|
||||
End If
|
||||
'改变默认的折叠
|
||||
If IsSwaped AndAlso SwapControl IsNot Nothing Then
|
||||
If IsSwapped AndAlso SwapControl IsNot Nothing Then
|
||||
MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0))
|
||||
SwapControl.Visibility = Visibility.Collapsed
|
||||
'取消由于高度变化被迫触发的高度动画
|
||||
Dim RawUseAnimation As Boolean = UseAnimation
|
||||
UseAnimation = False
|
||||
@@ -86,116 +102,62 @@
|
||||
RunInUi(Sub() UseAnimation = RawUseAnimation, True)
|
||||
End If
|
||||
End Sub
|
||||
Public Sub StackInstall()
|
||||
StackInstall(SwapControl, SwapType, Title)
|
||||
|
||||
'填充
|
||||
Private ContentBuilder As Action(Of Panel)
|
||||
Public Sub SetContentBuilder(Builder As Action(Of Panel))
|
||||
ContentBuilder = Builder
|
||||
End Sub
|
||||
Public Sub SetContentBuilder(Of T)(Items As IEnumerable(Of T), ItemBuilder As Func(Of T, UIElement))
|
||||
SetContentBuilder(
|
||||
Sub(Stack)
|
||||
For Each Item In Items
|
||||
Stack.Children.Add(ItemBuilder(Item))
|
||||
Next
|
||||
Stack.Children.Add(New FrameworkElement With {.Height = 18})
|
||||
End Sub)
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 使用 ContentBuilder 生成卡片内容。
|
||||
''' </summary>
|
||||
Public Sub BuildContent()
|
||||
If ContentBuilder Is Nothing Then Return
|
||||
Dim Stack = TryCast(SwapControl, Panel)
|
||||
If Stack Is Nothing Then Return
|
||||
Try
|
||||
ContentBuilder(Stack)
|
||||
ContentBuilder = Nothing
|
||||
Catch
|
||||
Stack.Children.Clear()
|
||||
Throw
|
||||
End Try
|
||||
TriggerForceResize()
|
||||
End Sub
|
||||
Public Shared Sub StackInstall(ByRef Stack As StackPanel, Type As Integer, Optional CardTitle As String = "")
|
||||
'这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎
|
||||
'但是现在……反正勉强能用……懒得改了就这样吧.jpg
|
||||
'别骂了别骂了.jpg
|
||||
If IsNothing(Stack.Tag) Then Exit Sub
|
||||
'排序
|
||||
Select Case Type
|
||||
Case 3
|
||||
Stack.Tag = Sort(CType(Stack.Tag, List(Of DlOptiFineListEntry)), Function(a, b) VersionSortBoolean(a.NameDisplay, b.NameDisplay))
|
||||
Case 4, 10
|
||||
Stack.Tag = Sort(CType(Stack.Tag, List(Of DlLiteLoaderListEntry)), Function(a, b) VersionSortBoolean(a.Inherit, b.Inherit))
|
||||
Case 6
|
||||
Stack.Tag = Sort(CType(Stack.Tag, List(Of DlForgeVersionEntry)), Function(a, b) a.Version > b.Version)
|
||||
Case 8, 9
|
||||
Stack.Tag = Sort(CType(Stack.Tag, List(Of CompFile)), Function(a, b) a.ReleaseDate > b.ReleaseDate)
|
||||
End Select
|
||||
'控件转换
|
||||
Select Case Type
|
||||
Case 5
|
||||
Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)}
|
||||
Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain)
|
||||
LoadingPickaxe.State = Loader
|
||||
Loader.Start(Stack.Tag)
|
||||
AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged
|
||||
AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click
|
||||
Stack.Children.Add(LoadingPickaxe)
|
||||
Case 6
|
||||
ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True)
|
||||
Case 8
|
||||
CompFilesCardPreload(Stack, Stack.Tag)
|
||||
End Select
|
||||
'实现控件虚拟化
|
||||
For Each Data As Object In Stack.Tag
|
||||
Select Case Type
|
||||
Case 0
|
||||
Stack.Children.Add(PageSelectRight.McVersionListItem(Data))
|
||||
Case 2
|
||||
Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True))
|
||||
Case 3
|
||||
Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True))
|
||||
Case 4
|
||||
Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False))
|
||||
Case 5
|
||||
Case 6
|
||||
Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True))
|
||||
Case 7
|
||||
'不能使用 AddressOf,这导致了 #535,原因完全不明,疑似是编译器 Bug
|
||||
Stack.Children.Add(McDownloadListItem(Data, Sub(sender, e) FrmDownloadInstall.MinecraftSelected(sender, e), False))
|
||||
Case 8
|
||||
If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <>
|
||||
CType(Stack.Tag, List(Of CompFile)).Count Then
|
||||
'存在重复的名称(#1344)
|
||||
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True))
|
||||
Else
|
||||
'不存在重复的名称,正常加载
|
||||
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Save_Click))
|
||||
End If
|
||||
Case 9
|
||||
If CType(Stack.Tag, List(Of CompFile)).Distinct(Function(a, b) a.DisplayName = b.DisplayName).Count <>
|
||||
CType(Stack.Tag, List(Of CompFile)).Count Then
|
||||
'存在重复的名称(#1344)
|
||||
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click, BadDisplayName:=True))
|
||||
Else
|
||||
'不存在重复的名称,正常加载
|
||||
Stack.Children.Add(CType(Data, CompFile).ToListItem(AddressOf FrmDownloadCompDetail.Install_Click, AddressOf FrmDownloadCompDetail.Save_Click))
|
||||
End If
|
||||
Case 10
|
||||
Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf LiteLoaderSave_Click, True))
|
||||
Case 11
|
||||
Stack.Children.Add(CType(Data, HelpEntry).ToListItem)
|
||||
Case 12
|
||||
Stack.Children.Add(FabricDownloadListItem(CType(Data, JObject), AddressOf FrmDownloadInstall.Fabric_Selected))
|
||||
Case 13
|
||||
Stack.Children.Add(NeoForgeDownloadListItem(Data, AddressOf NeoForgeSave_Click, True))
|
||||
Case Else
|
||||
Log("未知的虚拟化种类:" & Type, LogLevel.Feedback)
|
||||
End Select
|
||||
Next
|
||||
Stack.Children.Add(New FrameworkElement With {.Height = 18}) '下边距,同时适应折叠
|
||||
Stack.Tag = Nothing
|
||||
End Sub
|
||||
|
||||
'事件
|
||||
'动画
|
||||
Private Const DropShadowIdleOpacity As Double = 0.07
|
||||
Private Const DropShadowHoverOpacity As Double = 0.4
|
||||
Public Property HasMouseAnimation As Boolean = True
|
||||
Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter
|
||||
If Not HasMouseAnimation Then Exit Sub
|
||||
If Not HasMouseAnimation Then Return
|
||||
Dim AniList As New List(Of AniData)
|
||||
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150))
|
||||
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150))
|
||||
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 90))
|
||||
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 90))
|
||||
AniList.AddRange({
|
||||
AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject2", 180),
|
||||
AaColor(MainBorder, Border.BackgroundProperty, New MyColor(230, 255, 255, 255) - MainBorder.Background, 180),
|
||||
AaOpacity(MainChrome, 0.3 - MainChrome.Opacity, 180)
|
||||
})
|
||||
AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject4", 90),
|
||||
AaOpacity(MainChrome, DropShadowHoverOpacity - MainChrome.Opacity, 90)
|
||||
})
|
||||
AniStart(AniList, "MyCard Mouse " & Uuid)
|
||||
End Sub
|
||||
Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
|
||||
If Not HasMouseAnimation Then Exit Sub
|
||||
If Not HasMouseAnimation Then Return
|
||||
Dim AniList As New List(Of AniData)
|
||||
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250))
|
||||
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250))
|
||||
If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 90))
|
||||
If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 90))
|
||||
AniList.AddRange({
|
||||
AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject1", 300),
|
||||
AaColor(MainBorder, Border.BackgroundProperty, New MyColor(205, 255, 255, 255) - MainBorder.Background, 300),
|
||||
AaOpacity(MainChrome, 0.1 - MainChrome.Opacity, 300)
|
||||
})
|
||||
AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject1", 90),
|
||||
AaOpacity(MainChrome, DropShadowIdleOpacity - MainChrome.Opacity, 90)
|
||||
})
|
||||
AniStart(AniList, "MyCard Mouse " & Uuid)
|
||||
End Sub
|
||||
|
||||
@@ -208,115 +170,161 @@
|
||||
Private IsHeightAnimating As Boolean = False
|
||||
Private ActualUsedHeight As Double '回滚实际高度(例如 NaN)
|
||||
Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
|
||||
If Not UseAnimation Then Exit Sub
|
||||
Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
|
||||
If Not UseAnimation Then Return
|
||||
Dim DeltaHeight As Double = If(IsSwapped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height
|
||||
'卡片的进入时动画已被页面通用切换动画替代
|
||||
If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Exit Sub
|
||||
If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Return
|
||||
StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False)
|
||||
End Sub
|
||||
Private Sub StartHeightAnimation(DeltaHeight As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
|
||||
If IsHeightAnimating OrElse FrmMain Is Nothing Then Exit Sub '避免 XAML 设计器出错
|
||||
Private Sub StartHeightAnimation(Delta As Double, PreviousHeight As Double, IsLoadAnimation As Boolean)
|
||||
If IsHeightAnimating OrElse FrmMain Is Nothing Then Return '避免 XAML 设计器出错
|
||||
|
||||
Dim AnimList As New List(Of AniData)
|
||||
If DeltaHeight > 10 OrElse (DeltaHeight < -10 AndAlso Not IsNothing(SwapControl)) Then '如果不是需要折叠的卡片,高度减小时的弹跳会吞掉按钮下边框
|
||||
'高度增加较大,使用弹起动画
|
||||
Dim Delta As Double = MathClamp(Math.Abs(DeltaHeight) * 0.05, 3, 10) * Math.Sign(DeltaHeight)
|
||||
AnimList.AddRange({
|
||||
AaHeight(Me, DeltaHeight + Delta, 300, If(IsLoadAnimation, 30, 0), If(DeltaHeight > FrmMain.Height, New AniEaseInFluent(AniEasePower.ExtraStrong), New AniEaseOutFluent(AniEasePower.ExtraStrong))),
|
||||
AaHeight(Me, -Delta, 150, 260, Ease:=New AniEaseOutFluent(AniEasePower.Strong))
|
||||
})
|
||||
Else
|
||||
'普通的改变就行啦
|
||||
AnimList.AddRange({
|
||||
AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent)
|
||||
})
|
||||
End If
|
||||
AnimList.Add(AaCode(Sub()
|
||||
IsHeightAnimating = False
|
||||
Height = ActualUsedHeight
|
||||
If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed
|
||||
End Sub,, True))
|
||||
AniStart(AnimList, "MyCard Height " & Uuid)
|
||||
Dim AbsDelta = Math.Abs(Delta)
|
||||
|
||||
If AbsDelta <= 800 Then
|
||||
'短距离,直接使用 150ms 的缓动动画
|
||||
AnimList.Add(AaHeight(Me, Delta, 150,, New AniEaseOutFluent(AniEasePower.ExtraStrong)))
|
||||
Else
|
||||
Dim EaseLength As Integer, EaseTime As Integer
|
||||
Dim InitSpeed As Integer '到达缓动区前的初速度
|
||||
If Delta < 0 AndAlso AbsDelta - EaseLength > 5000 * 0.1 Then
|
||||
'收回距离过长 (>0.1s),强制以 100ms 完成匀速段,然后让减速段更长
|
||||
EaseLength = 200
|
||||
EaseTime = 150
|
||||
InitSpeed = (AbsDelta - EaseLength) / 0.1
|
||||
ElseIf Delta > 0 AndAlso AbsDelta - EaseLength > 5000 * 0.6 Then
|
||||
'展开距离过长 (>0.6s),以 5000 速度展示 300ms 匀速段,剩下的距离全部归入减速段
|
||||
InitSpeed = 5000
|
||||
EaseLength = AbsDelta - InitSpeed * 0.3
|
||||
EaseTime = 400
|
||||
Else
|
||||
'中程,匀速地快速展开(或收回)
|
||||
EaseLength = 150
|
||||
EaseTime = 200
|
||||
InitSpeed = 4000
|
||||
End If
|
||||
'匀速段
|
||||
AnimList.Add(AaHeight(Me, (AbsDelta - EaseLength) * Math.Sign(Delta),
|
||||
(AbsDelta - EaseLength) / InitSpeed * 1000))
|
||||
'减速段
|
||||
AnimList.Add(AaHeight(Me, EaseLength * Math.Sign(Delta),
|
||||
EaseTime,, New AniEaseOutFluentWithInitial(InitSpeed, EaseTime / 1000, EaseLength), True))
|
||||
End If
|
||||
|
||||
AnimList.Add(AaCode(
|
||||
Sub()
|
||||
IsHeightAnimating = False
|
||||
Height = ActualUsedHeight
|
||||
If IsSwapped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed
|
||||
End Sub,, True))
|
||||
AniStart(AnimList, "MyCard Height " & Uuid)
|
||||
IsHeightAnimating = True
|
||||
ActualUsedHeight = If(IsSwaped, SwapedHeight, Height)
|
||||
ActualUsedHeight = If(IsSwapped, SwapedHeight, Height)
|
||||
Height = PreviousHeight
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。
|
||||
''' 通知 MyCard,控件内容已改变,需要中断动画并瞬间更新高度。
|
||||
''' </summary>
|
||||
Public Sub TriggerForceResize()
|
||||
Height = If(IsSwaped, SwapedHeight, Double.NaN)
|
||||
Height = If(IsSwapped, SwapedHeight, Double.NaN)
|
||||
AniStop("MyCard Height " & Uuid)
|
||||
IsHeightAnimating = False
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
'折叠
|
||||
#Region "折叠"
|
||||
|
||||
'若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠
|
||||
'这是因为不能直接在 XAML 中设置 SwapControl
|
||||
Public SwapControl As Object
|
||||
Public SwapControl As FrameworkElement
|
||||
Public Property CanSwap As Boolean = False
|
||||
''' <summary>
|
||||
''' 被折叠的种类,用于控件虚拟化。
|
||||
''' </summary>
|
||||
Public Property SwapType As Integer
|
||||
''' <summary>
|
||||
''' 是否已被折叠。
|
||||
''' </summary>
|
||||
Public Property IsSwaped As Boolean
|
||||
Public Property IsSwapped As Boolean
|
||||
Get
|
||||
Return _IsSwaped
|
||||
Return _IsSwapped
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
If _IsSwaped = value Then Exit Property
|
||||
_IsSwaped = value
|
||||
If SwapControl Is Nothing Then Exit Property
|
||||
If _IsSwapped = value Then Return
|
||||
_IsSwapped = value
|
||||
If SwapControl Is Nothing Then Return
|
||||
'展开
|
||||
If Not IsSwaped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title)
|
||||
If Not IsSwapped Then BuildContent()
|
||||
'若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行
|
||||
If Not IsLoaded Then Exit Property
|
||||
'更新高度
|
||||
If Not IsLoaded Then Return
|
||||
'更新样式
|
||||
SwapControl.Visibility = Visibility.Visible
|
||||
TriggerForceResize()
|
||||
UpdateCursorStyle()
|
||||
'改变箭头
|
||||
AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 400,, New AniEaseOutBack(AniEasePower.Weak)), "MyCard Swap " & Uuid, True)
|
||||
AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True)
|
||||
End Set
|
||||
End Property
|
||||
Private _IsSwaped As Boolean = False
|
||||
Private _IsSwapped As Boolean = False
|
||||
<Obsolete("IsSwaped 存在拼写错误,请换用 IsSwapped 属性。")>
|
||||
Public Property IsSwaped As Boolean
|
||||
Get
|
||||
Return GetValue(IsSwapedProperty)
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
SetValue(IsSwapedProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False,
|
||||
Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean)))
|
||||
|
||||
Public Property SwapLogoRight As Boolean = False
|
||||
Private IsMouseDown As Boolean = False
|
||||
Private IsSwapMouseDown As Boolean = False '用于触发卡片展开/折叠的 MouseDown
|
||||
Private IsCustomMouseDown As Boolean = False '用于触发自定义事件的 MouseDown
|
||||
Public Event PreviewSwap(sender As Object, e As RouteEventArgs)
|
||||
Public Event Swap(sender As Object, e As RouteEventArgs)
|
||||
Public Const SwapedHeight As Integer = 40
|
||||
Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
|
||||
Dim Pos As Double = Mouse.GetPosition(Me).Y
|
||||
If Not IsSwaped AndAlso (IsNothing(SwapControl) OrElse Pos > SwapedHeight OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
|
||||
IsMouseDown = True
|
||||
If Not IsSwapped AndAlso
|
||||
(Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return
|
||||
IsCustomMouseDown = True
|
||||
If Not IsSwapped AndAlso
|
||||
(SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
|
||||
IsSwapMouseDown = True
|
||||
End Sub
|
||||
Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
|
||||
If IsMouseDown Then
|
||||
IsMouseDown = False
|
||||
Dim Pos As Double = Mouse.GetPosition(Me).Y
|
||||
If Not IsSwaped AndAlso (IsNothing(SwapControl) OrElse Pos > SwapedHeight OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
|
||||
Private Sub MyCard_MouseLeftButtonUp() Handles Me.MouseLeftButtonUp
|
||||
If Not IsCustomMouseDown Then Return
|
||||
IsCustomMouseDown = False
|
||||
RaiseCustomEvent() '触发自定义事件
|
||||
|
||||
Dim ee = New RouteEventArgs(True)
|
||||
RaiseEvent PreviewSwap(Me, ee)
|
||||
If ee.Handled Then
|
||||
IsMouseDown = False
|
||||
Exit Sub
|
||||
End If
|
||||
If Not IsSwapMouseDown Then Return
|
||||
IsSwapMouseDown = False
|
||||
|
||||
IsSwaped = Not IsSwaped
|
||||
Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
|
||||
RaiseEvent Swap(Me, ee)
|
||||
Dim Pos As Double = Mouse.GetPosition(Me).Y
|
||||
If Not IsSwapped AndAlso
|
||||
(SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判
|
||||
|
||||
Dim e = New RouteEventArgs(True)
|
||||
RaiseEvent PreviewSwap(Me, e)
|
||||
If e.Handled Then
|
||||
IsSwapMouseDown = False
|
||||
Return
|
||||
End If
|
||||
|
||||
IsSwapped = Not IsSwapped
|
||||
Logger.Info($"{If(IsSwapped, "折叠卡片", "展开卡片")}{If(Title Is Nothing, "", ":" & Title)}")
|
||||
RaiseEvent Swap(Me, e)
|
||||
End Sub
|
||||
Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
|
||||
IsMouseDown = False
|
||||
IsSwapMouseDown = False
|
||||
End Sub
|
||||
Private Sub UpdateCursorStyle() Handles Me.MouseMove
|
||||
If _MainSwap Is Nothing Then Cursor = Nothing : Return
|
||||
Dim Pos As Double = Mouse.GetPosition(Me).Y
|
||||
Cursor = If(MainSwap.Visibility = Visibility.Visible AndAlso (IsSwapped OrElse (Pos <= SwapedHeight - 6 AndAlso Not (Pos = 0 AndAlso Not IsMouseDirectlyOver))),
|
||||
Cursors.Hand, Nothing)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
End Class
|
||||
Partial Public Module ModAnimation
|
||||
@@ -324,22 +332,23 @@ Partial Public Module ModAnimation
|
||||
If Control.IsHitTestVisible Then
|
||||
Control.IsHitTestVisible = False
|
||||
AniStart({
|
||||
AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
|
||||
AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
|
||||
AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
|
||||
AaCode(Sub()
|
||||
If RemoveFromChildren Then
|
||||
If Control.Parent Is Nothing Then Exit Sub
|
||||
CType(Control.Parent, Object).Children.Remove(Control)
|
||||
Else
|
||||
Control.Visibility = Visibility.Collapsed
|
||||
End If
|
||||
If CallBack IsNot Nothing Then CallBack(Control)
|
||||
End Sub,, True)
|
||||
AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
|
||||
AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
|
||||
AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
|
||||
AaCode(
|
||||
Sub()
|
||||
If RemoveFromChildren Then
|
||||
If Control.Parent Is Nothing Then Return
|
||||
CType(Control.Parent, Object).Children.Remove(Control)
|
||||
Else
|
||||
Control.Visibility = Visibility.Collapsed
|
||||
End If
|
||||
If CallBack IsNot Nothing Then CallBack(Control)
|
||||
End Sub,, True)
|
||||
}, "MyCard Dispose " & Control.Uuid)
|
||||
Else
|
||||
If RemoveFromChildren Then
|
||||
If Control.Parent Is Nothing Then Exit Sub
|
||||
If Control.Parent Is Nothing Then Return
|
||||
CType(Control.Parent, Object).Children.Remove(Control)
|
||||
Else
|
||||
Control.Visibility = Visibility.Collapsed
|
||||
@@ -347,4 +356,4 @@ Partial Public Module ModAnimation
|
||||
If CallBack IsNot Nothing Then CallBack(Control)
|
||||
End If
|
||||
End Sub
|
||||
End Module
|
||||
End Module
|
||||
@@ -1,10 +1,10 @@
|
||||
<Grid
|
||||
<Grid
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MyCheckBox"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
MinWidth="20" x:Name="PanBack" UseLayoutRounding="False" SnapsToDevicePixels="False" MinHeight="20" Background="{StaticResource ColorBrushSemiTransparent}" Focusable="True" d:DesignWidth="126.4" d:DesignHeight="44.8">
|
||||
<TextBlock IsHitTestVisible="False" FontSize="13" x:Name="LabText" Padding="0" Margin="26,0,0,0" Text="Checkbox" Foreground="{DynamicResource ColorBrush1}" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<TextBlock IsHitTestVisible="False" FontSize="13" x:FieldModifier="Public" x:Name="LabText" Padding="0" Margin="26,0,0,0" Foreground="{DynamicResource ColorBrush1}" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<Border IsHitTestVisible="False" x:Name="ShapeBorder" BorderThickness="1.1" HorizontalAlignment="Left" VerticalAlignment="Center" Width="18" Height="18" BorderBrush="{DynamicResource ColorBrush1}" CornerRadius="3" Margin="1,0,0,0" Background="{StaticResource ColorBrushHalfWhite}" />
|
||||
<Path IsHitTestVisible="False" x:Name="ShapeCheck" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="{Binding BorderBrush, ElementName=ShapeBorder}" Width="12" Height="12" Margin="4,0,0,0" Data="M0,6L1.5,4.5 4.5,7.5 10.5,1.5 12,3 4.5,10.5 0,6z">
|
||||
<Path.RenderTransform>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
Public Class MyCheckBox
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyCheckBox
|
||||
Implements ISettingControl
|
||||
|
||||
'基础
|
||||
|
||||
@@ -9,34 +13,34 @@
|
||||
''' <param name="user">是否为用户手动改变的勾选状态。</param>
|
||||
Public Event Change(sender As Object, user As Boolean)
|
||||
Public Event PreviewChange(sender As Object, e As RouteEventArgs)
|
||||
Public Sub RaiseChange()
|
||||
RaiseEvent Change(Me, False)
|
||||
End Sub '使外部程序引发本控件的 Change 事件
|
||||
|
||||
'自定义属性
|
||||
|
||||
Private _Checked As Boolean = False '是否选中
|
||||
Public Property Checked As Boolean
|
||||
Get
|
||||
Return _Checked
|
||||
Return GetValue(CheckedProperty)
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
SetChecked(value, False, True)
|
||||
SetChecked(value, False)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly CheckedProperty As DependencyProperty =
|
||||
DependencyProperty.Register("Checked", GetType(Boolean), GetType(MyCheckBox), New PropertyMetadata(False,
|
||||
Sub(d As MyCheckBox, e As DependencyPropertyChangedEventArgs)
|
||||
'在使用 XAML 设置 Checked 属性时,不会触发 Checked_Set 方法,所以需要在这里手动触发 UI 改变
|
||||
If Not d.IsLoaded Then d.SyncUI()
|
||||
End Sub))
|
||||
|
||||
Private Const AnimationTimeOfCheck As Integer = 150 '勾选状态变更动画长度
|
||||
''' <summary>
|
||||
''' 手动设置 Checked 属性。
|
||||
''' </summary>
|
||||
''' <param name="value">新的 Checked 属性。</param>
|
||||
''' <param name="user">是否由用户引发。</param>
|
||||
''' <param name="anime">是否执行动画。</param>
|
||||
Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
|
||||
Public Sub SetChecked(value As Boolean, user As Boolean)
|
||||
Try
|
||||
If value = _Checked Then Exit Sub
|
||||
If value = Checked Then Return
|
||||
|
||||
'Preview 事件
|
||||
|
||||
If value AndAlso user Then
|
||||
Dim e = New RouteEventArgs(user)
|
||||
RaiseEvent PreviewChange(Me, e)
|
||||
@@ -44,64 +48,72 @@
|
||||
MouseDowned = True
|
||||
Checkbox_MouseLeave()
|
||||
MouseDowned = False
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
End If
|
||||
|
||||
_Checked = value
|
||||
SetValue(CheckedProperty, value)
|
||||
If IsLoaded Then RaiseEvent Change(Me, user)
|
||||
|
||||
'更改动画
|
||||
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 AndAlso anime Then '防止默认属性变更触发动画
|
||||
AllowMouseDown = False
|
||||
If Checked Then
|
||||
'由无变有
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
|
||||
AaScaleTransform(ShapeCheck, 1 - CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack(AniEasePower.Weak)),
|
||||
AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
|
||||
}, "MyCheckBox Scale " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush2"), "ColorBrushGray4"), AnimationTimeOfCheck)
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
AniStart({
|
||||
AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
|
||||
}, "MyCheckBox AllowMouseDown " & Uuid)
|
||||
Else
|
||||
'由有变无
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
|
||||
AaScaleTransform(ShapeCheck, -CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 0.9, , New AniEaseInFluent(AniEasePower.Weak)),
|
||||
AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
|
||||
}, "MyCheckBox Scale " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfCheck)
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
AniStart({
|
||||
AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
|
||||
}, "MyCheckBox AllowMouseDown " & Uuid)
|
||||
End If
|
||||
Else
|
||||
'不使用动画
|
||||
AniStop("MyCheckBox Scale " & Uuid)
|
||||
AniStop("MyCheckBox BorderColor " & Uuid)
|
||||
AniStop("MyCheckBox AllowMouseDown " & Uuid)
|
||||
If Checked Then
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 1
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 1
|
||||
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
|
||||
Else
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 0
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 0
|
||||
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
|
||||
End If
|
||||
End If
|
||||
|
||||
SyncUI()
|
||||
RaiseCustomEvent()
|
||||
Catch ex As Exception
|
||||
Log(ex, "设置 Checked 失败")
|
||||
Logger.Warn(ex, "设置 Checked 失败")
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub SyncUI()
|
||||
If AniControlEnabled = 0 AndAlso IsLoaded Then '防止默认属性变更触发动画
|
||||
AllowMouseDown = False
|
||||
If Checked Then
|
||||
'由无变有
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
|
||||
AaScaleTransform(ShapeCheck, 1 - CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack(AniEasePower.Weak)),
|
||||
AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
|
||||
}, "MyCheckBox Scale " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush2"), "ColorBrushGray5"), AnimationTimeOfCheck)
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
AniStart({
|
||||
AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
|
||||
}, "MyCheckBox AllowMouseDown " & Uuid)
|
||||
Else
|
||||
'由有变无
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
|
||||
AaScaleTransform(ShapeCheck, -CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 0.9, , New AniEaseInFluent(AniEasePower.Weak)),
|
||||
AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
|
||||
}, "MyCheckBox Scale " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush1"), "ColorBrushGray5"), AnimationTimeOfCheck)
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
AniStart({
|
||||
AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
|
||||
}, "MyCheckBox AllowMouseDown " & Uuid)
|
||||
End If
|
||||
Else
|
||||
'不使用动画
|
||||
AniStop("MyCheckBox Scale " & Uuid)
|
||||
AniStop("MyCheckBox BorderColor " & Uuid)
|
||||
AniStop("MyCheckBox AllowMouseDown " & Uuid)
|
||||
If Checked Then
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 1
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 1
|
||||
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray5"))
|
||||
Else
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 0
|
||||
CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 0
|
||||
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
|
||||
End If
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return GetValue(TextProperty)
|
||||
@@ -110,24 +122,24 @@
|
||||
SetValue(TextProperty, value)
|
||||
End Set
|
||||
End Property '内容
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyCheckbox), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyCheckbox).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyCheckBox), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyCheckBox).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
|
||||
'点击事件
|
||||
|
||||
Private MouseDowned As Boolean = False
|
||||
Private AllowMouseDown As Boolean = True
|
||||
Private Sub Checkbox_MouseUp() Handles Me.MouseLeftButtonUp
|
||||
If Not MouseDowned Then Exit Sub
|
||||
Log("[Control] 按下复选框(" & (Not Checked).ToString & "):" & Text)
|
||||
If Not MouseDowned Then Return
|
||||
Logger.Info($"按下复选框({(Not Checked)}):{Text}")
|
||||
MouseDowned = False
|
||||
SetChecked(Not Checked, True, True)
|
||||
SetChecked(Not Checked, True)
|
||||
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
|
||||
End Sub
|
||||
Private Sub Checkbox_MouseDown() Handles Me.MouseLeftButtonDown
|
||||
If Not AllowMouseDown Then Exit Sub
|
||||
If Not AllowMouseDown Then Return
|
||||
MouseDowned = True
|
||||
Focus()
|
||||
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushBg1", 100), "MyCheckBox Background " & Uuid)
|
||||
@@ -141,7 +153,7 @@
|
||||
End If
|
||||
End Sub
|
||||
Private Sub Checkbox_MouseLeave() Handles Me.MouseLeave
|
||||
If Not MouseDowned Then Exit Sub
|
||||
If Not MouseDowned Then Return
|
||||
MouseDowned = False
|
||||
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
|
||||
If Checked Then
|
||||
@@ -167,18 +179,18 @@
|
||||
Else
|
||||
'不可用
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, ColorGray4 - ShapeBorder.BorderBrush, AnimationTimeOfMouseOut)
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, ColorGray5 - ShapeBorder.BorderBrush, AnimationTimeOfMouseOut)
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
AniStart({
|
||||
AaColor(LabText, TextBlock.ForegroundProperty, ColorGray4 - LabText.Foreground, AnimationTimeOfMouseOut)
|
||||
AaColor(LabText, TextBlock.ForegroundProperty, ColorGray5 - LabText.Foreground, AnimationTimeOfMouseOut)
|
||||
}, "MyCheckBox TextColor " & Uuid)
|
||||
End If
|
||||
Else
|
||||
'无动画
|
||||
AniStop("MyCheckBox TextColor " & Uuid)
|
||||
AniStop("MyCheckBox BorderColor " & Uuid)
|
||||
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"))
|
||||
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"))
|
||||
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray5"))
|
||||
ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"))
|
||||
End If
|
||||
End Sub
|
||||
Private Sub Checkbox_MouseEnterAnimation() Handles Me.MouseEnter
|
||||
@@ -190,13 +202,29 @@
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
End Sub
|
||||
Private Sub Checkbox_MouseLeaveAnimation() Handles Me.MouseLeave
|
||||
If Not IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
|
||||
If Not IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
|
||||
AniStart({
|
||||
AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut)
|
||||
AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray5"), AnimationTimeOfMouseOut)
|
||||
}, "MyCheckBox TextColor " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut)
|
||||
AaColor(ShapeBorder, Border.BorderBrushProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"), AnimationTimeOfMouseOut)
|
||||
}, "MyCheckBox BorderColor " & Uuid)
|
||||
End Sub
|
||||
|
||||
#Region "设置"
|
||||
|
||||
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
|
||||
Checked = Boolean.Parse(NewValue)
|
||||
End Sub
|
||||
|
||||
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
|
||||
Return Checked.ToString
|
||||
End Function
|
||||
|
||||
Private Sub SaveSetting() Handles Me.Change
|
||||
SettingService.SaveSetting(Me)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
End Class
|
||||
@@ -1,5 +1,6 @@
|
||||
Public Class MyComboBox
|
||||
Public Class MyComboBox
|
||||
Inherits ComboBox
|
||||
Implements ISettingControl
|
||||
Public Event TextChanged(sender As Object, e As TextChangedEventArgs)
|
||||
|
||||
'基础
|
||||
@@ -8,7 +9,7 @@
|
||||
Public Property HintText As String = ""
|
||||
Public Overrides Sub OnApplyTemplate()
|
||||
MyBase.OnApplyTemplate()
|
||||
If Not IsEditable Then Exit Sub
|
||||
If Not IsEditable Then Return
|
||||
Try
|
||||
TextBox = Template.FindName("PART_EditableTextBox", Me)
|
||||
TextBox.AddHandler(LostFocusEvent, New RoutedEventHandler(AddressOf RefreshColor))
|
||||
@@ -21,7 +22,7 @@
|
||||
End If
|
||||
If HintText.Length > 0 Then TextBox.HintText = HintText
|
||||
Catch ex As Exception
|
||||
Log(ex, "初始化可编辑文本框失败(" & If(Name, "") & ")", LogLevel.Feedback)
|
||||
Logger.Error(ex, $"初始化可编辑文本框失败({If(Name, "")})")
|
||||
End Try
|
||||
End Sub
|
||||
Private _Text As String = SelectedItem
|
||||
@@ -103,10 +104,11 @@
|
||||
Private Sub MyComboBox_DropDownOpened(sender As Object, e As EventArgs) Handles Me.DropDownOpened
|
||||
RealWidth = Width
|
||||
Width = ActualWidth
|
||||
'将下拉菜单透明度与主窗口同步
|
||||
Try
|
||||
CType(Template.FindName("PanPopup", Me), Grid).Opacity = FrmMain.Opacity
|
||||
Catch ex As Exception
|
||||
Log(ex, "设置下拉框透明度失败", LogLevel.Feedback)
|
||||
Logger.Error(ex, "设置下拉框透明度失败")
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub MyComboBox_DropDownClosed(sender As Object, e As EventArgs) Handles Me.DropDownClosed
|
||||
@@ -116,16 +118,15 @@
|
||||
'修复 WPF Bug:下拉框文本修改后,依然误认为还选择着此前的选项,导致再次点击该选项时内容不变
|
||||
Private IsTextChanging As Boolean = False
|
||||
Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
|
||||
If IsTextChanging OrElse Not IsEditable Then Exit Sub
|
||||
If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then
|
||||
Dim RawText As String = Text
|
||||
Dim RawSelectionStart As Integer = TextBox.SelectionStart
|
||||
IsTextChanging = True
|
||||
SelectedItem = Nothing
|
||||
Text = RawText
|
||||
TextBox.SelectionStart = RawSelectionStart
|
||||
IsTextChanging = False
|
||||
End If
|
||||
If IsTextChanging OrElse Not IsEditable Then Return
|
||||
If SelectedItem Is Nothing OrElse Text = SelectedItem.ToString Then Return
|
||||
Dim RawText As String = Text
|
||||
Dim RawSelectionStart As Integer = TextBox.SelectionStart
|
||||
IsTextChanging = True
|
||||
SelectedItem = Nothing
|
||||
Text = RawText
|
||||
TextBox.SelectionStart = RawSelectionStart
|
||||
IsTextChanging = False
|
||||
End Sub
|
||||
|
||||
Public ReadOnly Property ContentPresenter As ContentPresenter
|
||||
@@ -134,4 +135,39 @@
|
||||
End Get
|
||||
End Property
|
||||
|
||||
Private Sub MyComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles Me.SelectionChanged
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then RaiseCustomEvent()
|
||||
End Sub
|
||||
|
||||
#Region "设置"
|
||||
|
||||
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
|
||||
Dim TargetItem = Items.OfType(Of DependencyObject)().FirstOrDefault(Function(i) Val(If(SettingService.GetValue(i), -1)) = NewValue)
|
||||
If TargetItem IsNot Nothing Then
|
||||
SelectedItem = TargetItem
|
||||
ElseIf Items.Count > NewValue Then
|
||||
SelectedIndex = NewValue
|
||||
Else
|
||||
Logger.Warn($"尝试显示设置 {SettingService.GetKey(Me)},但没有找到设置值 {NewValue} 的对应项")
|
||||
SelectedIndex = -1
|
||||
SelectedItem = Nothing
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
|
||||
If SelectedItem Is Nothing Then Return Nothing
|
||||
If TypeOf SelectedItem Is DependencyObject Then
|
||||
Return If(SettingService.GetValue(SelectedItem), SelectedIndex)
|
||||
Else
|
||||
Return SelectedIndex
|
||||
End If
|
||||
End Function
|
||||
|
||||
Private Sub SaveSetting() Handles Me.SelectionChanged
|
||||
If SelectedItem Is Nothing Then Return
|
||||
SettingService.SaveSetting(Me)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyComboBoxItem
|
||||
Public Class MyComboBoxItem
|
||||
Inherits ComboBoxItem
|
||||
|
||||
'基础
|
||||
@@ -35,7 +35,7 @@
|
||||
NewFontOpacity = 0.4
|
||||
Time = AnimationTimeOut
|
||||
End If
|
||||
If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Exit Sub
|
||||
If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Return
|
||||
BackColorName = NewBackColorName
|
||||
FontOpacity = NewFontOpacity
|
||||
'触发颜色动画
|
||||
@@ -61,7 +61,7 @@
|
||||
End Operator
|
||||
|
||||
Private Sub MyComboBoxItem_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
|
||||
Log("[Control] 选择下拉列表项:" & Tostring())
|
||||
Logger.Info($"选择下拉列表项:{Tostring()}")
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
+66
-41
@@ -1,65 +1,89 @@
|
||||
Public NotInheritable Class SystemDropShadowChrome
|
||||
Public Class MyDropShadow
|
||||
Inherits Decorator
|
||||
|
||||
'源码来源于:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1
|
||||
|
||||
Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
|
||||
''' <summary>
|
||||
''' 阴影颜色。
|
||||
''' </summary>
|
||||
Public Property Color As Color
|
||||
Get
|
||||
Return CType(GetValue(ColorProperty), Color)
|
||||
Return GetValue(ColorProperty)
|
||||
End Get
|
||||
Set(value As Color)
|
||||
SetValue(ColorProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(MyDropShadow),
|
||||
New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
|
||||
|
||||
Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
|
||||
''' <summary>
|
||||
''' 阴影模糊半径。
|
||||
''' </summary>
|
||||
Public Property ShadowRadius As Double
|
||||
Get
|
||||
Return GetValue(ShadowRadiusProperty)
|
||||
End Get
|
||||
Set(value As Double)
|
||||
SetValue(ShadowRadiusProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly ShadowRadiusProperty As DependencyProperty = DependencyProperty.Register("ShadowRadius", GetType(Double), GetType(MyDropShadow),
|
||||
New FrameworkPropertyMetadata(CDbl(5), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
|
||||
|
||||
''' <summary>
|
||||
''' 圆角大小。
|
||||
''' </summary>
|
||||
Public Property CornerRadius As CornerRadius
|
||||
Get
|
||||
Return CType(GetValue(CornerRadiusProperty), CornerRadius)
|
||||
Return GetValue(CornerRadiusProperty)
|
||||
End Get
|
||||
Set(value As CornerRadius)
|
||||
SetValue(CornerRadiusProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Private Shared Function IsCornerRadiusValid(value As Object) As Boolean
|
||||
Dim cr As CornerRadius = CType(value, CornerRadius)
|
||||
Dim cr As CornerRadius = value
|
||||
Return Not (cr.TopLeft < 0.0 OrElse cr.TopRight < 0.0 OrElse cr.BottomLeft < 0.0 OrElse cr.BottomRight < 0.0 OrElse Double.IsNaN(cr.TopLeft) OrElse Double.IsNaN(cr.TopRight) OrElse Double.IsNaN(cr.BottomLeft) OrElse Double.IsNaN(cr.BottomRight) OrElse Double.IsInfinity(cr.TopLeft) OrElse Double.IsInfinity(cr.TopRight) OrElse Double.IsInfinity(cr.BottomLeft) OrElse Double.IsInfinity(cr.BottomRight))
|
||||
End Function
|
||||
Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyDropShadow),
|
||||
New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
|
||||
|
||||
|
||||
' =======================================
|
||||
' 渲染
|
||||
' =======================================
|
||||
|
||||
Private Const ShadowDepth As Double = 5
|
||||
|
||||
Protected Overrides Sub OnRender(drawingContext As DrawingContext)
|
||||
Dim cornerRadius As CornerRadius = Me.CornerRadius
|
||||
Dim shadowBounds As New Rect(New Point(ShadowDepth, ShadowDepth), New Size(RenderSize.Width, RenderSize.Height))
|
||||
Dim shadowBounds As New Rect(0, 0, RenderSize.Width, RenderSize.Height)
|
||||
Dim color As Color = Me.Color
|
||||
|
||||
If shadowBounds.Width > 0 AndAlso shadowBounds.Height > 0 AndAlso color.A > 0 Then
|
||||
Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowDepth
|
||||
Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowDepth
|
||||
Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowRadius
|
||||
Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowRadius
|
||||
Dim maxRadius As Double = Math.Min(centerWidth * 0.5, centerHeight * 0.5)
|
||||
cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius)
|
||||
cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius)
|
||||
cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius)
|
||||
cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius)
|
||||
Dim brushes As Brush() = GetBrushes(color, cornerRadius)
|
||||
Dim centerTop As Double = shadowBounds.Top + ShadowDepth
|
||||
Dim centerLeft As Double = shadowBounds.Left + ShadowDepth
|
||||
Dim centerRight As Double = shadowBounds.Right - ShadowDepth
|
||||
Dim centerBottom As Double = shadowBounds.Bottom - ShadowDepth
|
||||
Dim centerTop As Double = shadowBounds.Top + ShadowRadius
|
||||
Dim centerLeft As Double = shadowBounds.Left + ShadowRadius
|
||||
Dim centerRight As Double = shadowBounds.Right - ShadowRadius
|
||||
Dim centerBottom As Double = shadowBounds.Bottom - ShadowRadius
|
||||
Dim guidelineSetX As Double() = New Double() {centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight, centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight}
|
||||
Dim guidelineSetY As Double() = New Double() {centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight, centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom}
|
||||
drawingContext.PushGuidelineSet(New GuidelineSet(guidelineSetX, guidelineSetY))
|
||||
cornerRadius.TopLeft += ShadowDepth
|
||||
cornerRadius.TopRight += ShadowDepth
|
||||
cornerRadius.BottomLeft += ShadowDepth
|
||||
cornerRadius.BottomRight += ShadowDepth
|
||||
cornerRadius.TopLeft += ShadowRadius
|
||||
cornerRadius.TopRight += ShadowRadius
|
||||
cornerRadius.BottomLeft += ShadowRadius
|
||||
cornerRadius.BottomRight += ShadowRadius
|
||||
Dim topLeft As New Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft)
|
||||
drawingContext.DrawRectangle(brushes(Placement.TopLeft), Nothing, topLeft)
|
||||
Dim topWidth As Double = guidelineSetX(2) - guidelineSetX(1)
|
||||
|
||||
If topWidth > 0 Then
|
||||
Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowDepth)
|
||||
Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowRadius)
|
||||
drawingContext.DrawRectangle(brushes(Placement.Top), Nothing, top)
|
||||
End If
|
||||
|
||||
@@ -68,14 +92,14 @@
|
||||
Dim leftHeight As Double = guidelineSetY(3) - guidelineSetY(1)
|
||||
|
||||
If leftHeight > 0 Then
|
||||
Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowDepth, leftHeight)
|
||||
Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowRadius, leftHeight)
|
||||
drawingContext.DrawRectangle(brushes(Placement.Left), Nothing, left)
|
||||
End If
|
||||
|
||||
Dim rightHeight As Double = guidelineSetY(4) - guidelineSetY(2)
|
||||
|
||||
If rightHeight > 0 Then
|
||||
Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowDepth, rightHeight)
|
||||
Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowRadius, rightHeight)
|
||||
drawingContext.DrawRectangle(brushes(Placement.Right), Nothing, right)
|
||||
End If
|
||||
|
||||
@@ -84,20 +108,20 @@
|
||||
Dim bottomWidth As Double = guidelineSetX(4) - guidelineSetX(3)
|
||||
|
||||
If bottomWidth > 0 Then
|
||||
Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowDepth)
|
||||
Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowRadius)
|
||||
drawingContext.DrawRectangle(brushes(Placement.Bottom), Nothing, bottom)
|
||||
End If
|
||||
|
||||
Dim bottomRight As New Rect(guidelineSetX(4), guidelineSetY(4), cornerRadius.BottomRight, cornerRadius.BottomRight)
|
||||
drawingContext.DrawRectangle(brushes(Placement.BottomRight), Nothing, bottomRight)
|
||||
|
||||
If cornerRadius.TopLeft = ShadowDepth AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
|
||||
If cornerRadius.TopLeft = ShadowRadius AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
|
||||
Dim center As New Rect(guidelineSetX(0), guidelineSetY(0), centerWidth, centerHeight)
|
||||
drawingContext.DrawRectangle(brushes(Placement.Center), Nothing, center)
|
||||
Else
|
||||
Dim figure As New PathFigure()
|
||||
|
||||
If cornerRadius.TopLeft > ShadowDepth Then
|
||||
If cornerRadius.TopLeft > ShadowRadius Then
|
||||
figure.StartPoint = New Point(guidelineSetX(1), guidelineSetY(0))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(1), guidelineSetY(1)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(1)), True))
|
||||
@@ -105,7 +129,7 @@
|
||||
figure.StartPoint = New Point(guidelineSetX(0), guidelineSetY(0))
|
||||
End If
|
||||
|
||||
If cornerRadius.BottomLeft > ShadowDepth Then
|
||||
If cornerRadius.BottomLeft > ShadowRadius Then
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(3)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(3)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(5)), True))
|
||||
@@ -113,7 +137,7 @@
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(5)), True))
|
||||
End If
|
||||
|
||||
If cornerRadius.BottomRight > ShadowDepth Then
|
||||
If cornerRadius.BottomRight > ShadowRadius Then
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(5)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(4)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(4)), True))
|
||||
@@ -121,7 +145,7 @@
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(5)), True))
|
||||
End If
|
||||
|
||||
If cornerRadius.TopRight > ShadowDepth Then
|
||||
If cornerRadius.TopRight > ShadowRadius Then
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(2)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(2)), True))
|
||||
figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(0)), True))
|
||||
@@ -158,28 +182,27 @@
|
||||
Private _brushes As Brush()
|
||||
|
||||
Private Shared Sub ClearBrushes(o As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
CType(o, SystemDropShadowChrome)._brushes = Nothing
|
||||
CType(o, MyDropShadow)._brushes = Nothing
|
||||
End Sub
|
||||
Private Shared Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
|
||||
Dim gradientScale As Double = 1 / (cornerRadius + ShadowDepth)
|
||||
Dim gsc As New GradientStopCollection From {
|
||||
New GradientStop(c, (0.5 + cornerRadius) * gradientScale)
|
||||
}
|
||||
Private Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
|
||||
Dim gradientScale As Double = 1 / (ShadowRadius + cornerRadius)
|
||||
Dim gsc As New GradientStopCollection
|
||||
Dim stopColor As Color = c
|
||||
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.1 + cornerRadius) * gradientScale))
|
||||
stopColor.A = CByte(0.74336 * c.A)
|
||||
gsc.Add(New GradientStop(stopColor, (1.5 + cornerRadius) * gradientScale))
|
||||
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.3 + cornerRadius) * gradientScale))
|
||||
stopColor.A = CByte(0.38053 * c.A)
|
||||
gsc.Add(New GradientStop(stopColor, (2.5 + cornerRadius) * gradientScale))
|
||||
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.5 + cornerRadius) * gradientScale))
|
||||
stopColor.A = CByte(0.12389 * c.A)
|
||||
gsc.Add(New GradientStop(stopColor, (3.5 + cornerRadius) * gradientScale))
|
||||
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.7 + cornerRadius) * gradientScale))
|
||||
stopColor.A = CByte(0.02654 * c.A)
|
||||
gsc.Add(New GradientStop(stopColor, (4.5 + cornerRadius) * gradientScale))
|
||||
gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.9 + cornerRadius) * gradientScale))
|
||||
stopColor.A = 0
|
||||
gsc.Add(New GradientStop(stopColor, (5 + cornerRadius) * gradientScale))
|
||||
gsc.Add(New GradientStop(stopColor, (ShadowRadius + cornerRadius) * gradientScale))
|
||||
gsc.Freeze()
|
||||
Return gsc
|
||||
End Function
|
||||
Private Shared Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
|
||||
Private Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
|
||||
Dim brushes As Brush() = New Brush(8) {}
|
||||
brushes(Placement.Center) = New SolidColorBrush(c)
|
||||
brushes(Placement.Center).Freeze()
|
||||
@@ -297,3 +320,5 @@
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
'参考自:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1
|
||||
@@ -1,8 +1,8 @@
|
||||
<Grid
|
||||
<Grid
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Name="PanBack" mc:Ignorable="d" x:Class="MyExtraButton"
|
||||
Width="40" RenderTransformOrigin="0.5,0.5" ToolTipService.Placement="Left" ToolTipService.VerticalOffset="16" ToolTipService.HorizontalOffset="-8" Height="0">
|
||||
Width="40" RenderTransformOrigin="0.5,0.5" Height="0">
|
||||
<Grid.RenderTransform>
|
||||
<ScaleTransform ScaleX="0" ScaleY="0" />
|
||||
</Grid.RenderTransform>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyExtraButton
|
||||
Public Class MyExtraButton
|
||||
|
||||
'声明
|
||||
Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
|
||||
@@ -11,7 +11,7 @@
|
||||
Return _Progress
|
||||
End Get
|
||||
Set(value As Double)
|
||||
If _Progress = value Then Exit Property
|
||||
If _Progress = value Then Return
|
||||
_Progress = value
|
||||
If value < 0.0001 Then
|
||||
PanProgress.Visibility = Visibility.Collapsed
|
||||
@@ -30,7 +30,7 @@
|
||||
Return _Logo
|
||||
End Get
|
||||
Set(value As String)
|
||||
If value = _Logo Then Exit Property
|
||||
If value = _Logo Then Return
|
||||
_Logo = value
|
||||
Path.Data = (New GeometryConverter).ConvertFromString(value)
|
||||
End Set
|
||||
@@ -51,27 +51,28 @@
|
||||
Return _Show
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
If _Show = value Then Exit Property
|
||||
If _Show = value Then Return
|
||||
_Show = value
|
||||
RunInUi(Sub()
|
||||
If value Then
|
||||
'有了
|
||||
Visibility = Visibility.Visible
|
||||
AniStart({
|
||||
AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
|
||||
AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
|
||||
AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
|
||||
}, "MyExtraButton MainScale " & Uuid)
|
||||
Else
|
||||
'没了
|
||||
AniStart({
|
||||
AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
|
||||
AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
|
||||
AaCode(Sub() Visibility = Visibility.Collapsed,, True)
|
||||
}, "MyExtraButton MainScale " & Uuid)
|
||||
End If
|
||||
IsHitTestVisible = value '防止缩放动画中依然可以点进去
|
||||
End Sub)
|
||||
RunInUi(
|
||||
Sub()
|
||||
If value Then
|
||||
'有了
|
||||
Visibility = Visibility.Visible
|
||||
AniStart({
|
||||
AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)),
|
||||
AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)),
|
||||
AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak))
|
||||
}, "MyExtraButton MainScale " & Uuid)
|
||||
Else
|
||||
'没了
|
||||
AniStart({
|
||||
AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)),
|
||||
AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()),
|
||||
AaCode(Sub() Visibility = Visibility.Collapsed,, True)
|
||||
}, "MyExtraButton MainScale " & Uuid)
|
||||
End If
|
||||
IsHitTestVisible = value '防止缩放动画中依然可以点进去
|
||||
End Sub)
|
||||
End Set
|
||||
End Property
|
||||
Public Delegate Function ShowCheckDelegate() As Boolean
|
||||
@@ -83,7 +84,7 @@
|
||||
'触发点击事件
|
||||
Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
|
||||
If IsLeftMouseHeld Then
|
||||
Log("[Control] 按下附加按钮" & If(ToolTip = "", "", ":" & ToolTip.ToString))
|
||||
Logger.Info($"按下附加按钮{If(ToolTip = "", "", ":" & ToolTip)}")
|
||||
RaiseEvent Click(sender, e)
|
||||
e.Handled = True
|
||||
Button_LeftMouseUp()
|
||||
@@ -91,7 +92,7 @@
|
||||
End Sub
|
||||
Private Sub Button_RightMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonUp
|
||||
If IsRightMouseHeld Then
|
||||
Log("[Control] 右键按下附加按钮" & If(ToolTip = "", "", ":" & ToolTip.ToString))
|
||||
Logger.Info($"右键按下附加按钮{If(ToolTip = "", "", ":" & ToolTip)}")
|
||||
RaiseEvent RightClick(sender, e)
|
||||
e.Handled = True
|
||||
Button_RightMouseUp()
|
||||
@@ -113,20 +114,20 @@
|
||||
Private Sub Button_LeftMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonDown
|
||||
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
|
||||
AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
|
||||
AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
End If
|
||||
IsLeftMouseHeld = True
|
||||
Focus()
|
||||
End Sub
|
||||
Private Sub Button_RightMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonDown
|
||||
If Not CanRightClick Then Exit Sub
|
||||
If Not CanRightClick Then Return
|
||||
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
|
||||
AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
|
||||
AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
End If
|
||||
IsRightMouseHeld = True
|
||||
Focus()
|
||||
@@ -134,18 +135,19 @@
|
||||
Private Sub Button_LeftMouseUp() Handles PanClick.MouseLeftButtonUp
|
||||
If Not IsRightMouseHeld Then
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
End If
|
||||
If IsLeftMouseHeld Then RaiseCustomEvent()
|
||||
IsLeftMouseHeld = False
|
||||
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
|
||||
If Not CanRightClick Then Exit Sub
|
||||
If Not CanRightClick Then Return
|
||||
If Not IsLeftMouseHeld Then
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
End If
|
||||
IsRightMouseHeld = False
|
||||
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
@@ -154,8 +156,8 @@
|
||||
IsLeftMouseHeld = False
|
||||
IsRightMouseHeld = False
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
|
||||
}, "MyExtraButton Scale " & Uuid)
|
||||
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
|
||||
@@ -163,11 +165,14 @@
|
||||
'务必放在 IsMouseDown 更新之后
|
||||
Private Const AnimationColorIn As Integer = 120
|
||||
Private Const AnimationColorOut As Integer = 150
|
||||
Public Sub RefreshColor() Handles PanClick.MouseEnter, PanClick.MouseLeave, Me.Loaded
|
||||
Public Sub RefreshColor() Handles PanClick.MouseEnter, PanClick.MouseLeave, Me.Loaded, Me.IsEnabledChanged
|
||||
Try
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
|
||||
If IsMouseOver Then
|
||||
If Not IsEnabled Then
|
||||
'禁用
|
||||
AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraButton Color " & Uuid)
|
||||
ElseIf IsMouseOver Then
|
||||
'指向
|
||||
AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraButton Color " & Uuid)
|
||||
Else
|
||||
@@ -178,7 +183,9 @@
|
||||
Else
|
||||
|
||||
AniStop("MyExtraButton Color " & Uuid)
|
||||
If IsMouseOver Then
|
||||
If Not IsEnabled Then
|
||||
PanColor.SetResourceReference(BackgroundProperty, "ColorBrushGray4")
|
||||
ElseIf IsMouseOver Then
|
||||
PanColor.SetResourceReference(BackgroundProperty, "ColorBrush4")
|
||||
Else
|
||||
PanColor.SetResourceReference(BackgroundProperty, "ColorBrush3")
|
||||
@@ -186,7 +193,7 @@
|
||||
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新图标按钮颜色出错")
|
||||
Logger.Warn(ex, "刷新图标按钮颜色出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -194,16 +201,17 @@
|
||||
''' 发出一圈波浪效果提示。
|
||||
''' </summary>
|
||||
Public Sub Ribble()
|
||||
RunInUi(Sub()
|
||||
Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
|
||||
Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
|
||||
PanScale.Children.Insert(0, Shape)
|
||||
AniStart({
|
||||
AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
|
||||
AaOpacity(Shape, -Shape.Opacity, 1000),
|
||||
AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
|
||||
}, "ExtraButton Ribble " & GetUuid())
|
||||
End Sub)
|
||||
RunInUi(
|
||||
Sub()
|
||||
Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()}
|
||||
Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5")
|
||||
PanScale.Children.Insert(0, Shape)
|
||||
AniStart({
|
||||
AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)),
|
||||
AaOpacity(Shape, -Shape.Opacity, 1000),
|
||||
AaCode(Sub() PanScale.Children.Remove(Shape), After:=True)
|
||||
}, "ExtraButton Ribble " & GetUuid())
|
||||
End Sub)
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -0,0 +1,33 @@
|
||||
<Grid
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Name="PanBack" mc:Ignorable="d" x:Class="MyExtraTextButton"
|
||||
RenderTransformOrigin="0.5,0.5" Height="52" Margin="20" Cursor="Hand">
|
||||
<Grid.RenderTransform>
|
||||
<ScaleTransform ScaleX="0" ScaleY="0" />
|
||||
</Grid.RenderTransform>
|
||||
<Border x:Name="PanClick" BorderThickness="0.001" Background="{StaticResource ColorBrushSemiTransparent}"
|
||||
CornerRadius="{Binding ActualHeight, ConverterParameter=0.4, Converter={StaticResource MultiplicationConverter}, ElementName=PanBack, Mode=OneWay}"/>
|
||||
<Grid x:Name="PanScale" IsHitTestVisible="False" RenderTransformOrigin="0.5,0.5" Margin="0,10,0,0">
|
||||
<Grid.RenderTransform>
|
||||
<ScaleTransform />
|
||||
</Grid.RenderTransform>
|
||||
<Border x:Name="PanColor" BorderThickness="0.001" Background="{DynamicResource ColorBrush3}"
|
||||
CornerRadius="{Binding ActualHeight, ConverterParameter=0.4, Converter={StaticResource MultiplicationConverter}, ElementName=PanBack, Mode=OneWay}">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="0" BlurRadius="10" Opacity="0.2" />
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
<StackPanel Orientation="Horizontal" Margin="20,0" SnapsToDevicePixels="False" UseLayoutRounding="False">
|
||||
<Path x:Name="Path" Stretch="Uniform" Margin="2,12,0,12" Fill="{DynamicResource ColorBrush8}" RenderTransformOrigin="0.5,0.5">
|
||||
<Path.RenderTransform>
|
||||
<ScaleTransform />
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
<TextBlock x:Name="LabText" VerticalAlignment="Center" Foreground="{DynamicResource ColorBrush8}" FontSize="16"
|
||||
Margin="12,0,0,0.8" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -0,0 +1,163 @@
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyExtraTextButton
|
||||
|
||||
'声明
|
||||
Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
|
||||
|
||||
'自定义属性
|
||||
Public Uuid As Integer = GetUuid()
|
||||
Private _Logo As String = ""
|
||||
Public Property Logo As String
|
||||
Get
|
||||
Return _Logo
|
||||
End Get
|
||||
Set(value As String)
|
||||
If value = _Logo Then Return
|
||||
_Logo = value
|
||||
Path.Data = (New GeometryConverter).ConvertFromString(value)
|
||||
End Set
|
||||
End Property
|
||||
Private _LogoScale As Double = 1
|
||||
Public Property LogoScale() As Double
|
||||
Get
|
||||
Return _LogoScale
|
||||
End Get
|
||||
Set(value As Double)
|
||||
_LogoScale = value
|
||||
If Path IsNot Nothing Then Path.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale}
|
||||
End Set
|
||||
End Property
|
||||
'显示文本
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return GetValue(TextProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(TextProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyExtraTextButton), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If sender IsNot Nothing Then CType(sender, MyExtraTextButton).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
|
||||
'动画
|
||||
Private _Show As Boolean = False
|
||||
Public Property Show As Boolean
|
||||
Get
|
||||
Return _Show
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
If _Show = value Then Return
|
||||
_Show = value
|
||||
RunInUi(
|
||||
Sub()
|
||||
If value Then
|
||||
'有了
|
||||
Opacity = 0
|
||||
AniStart({
|
||||
AaOpacity(Me, 1 - Opacity, 80, 50),
|
||||
AaScaleTransform(Me, 0.15 - CType(RenderTransform, ScaleTransform).ScaleX, 400, 50, New AniEaseOutBack),
|
||||
AaScaleTransform(Me, 0.85, 160, 50, New AniEaseOutFluent(AniEasePower.Middle))
|
||||
}, "MyExtraTextButton MainScale " & Uuid)
|
||||
Else
|
||||
'没了
|
||||
AniStart({
|
||||
AaOpacity(Me, -Opacity, 50, 50),
|
||||
AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak))
|
||||
}, "MyExtraTextButton MainScale " & Uuid)
|
||||
End If
|
||||
IsHitTestVisible = value '防止缩放动画中依然可以点进去
|
||||
End Sub)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
'触发点击事件
|
||||
Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
|
||||
If Not IsLeftMouseHeld Then Return
|
||||
Logger.Info($"按下附加图标按钮:{Text}")
|
||||
RaiseEvent Click(sender, e)
|
||||
e.Handled = True
|
||||
RaiseCustomEvent()
|
||||
Button_LeftMouseUp()
|
||||
End Sub
|
||||
|
||||
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
|
||||
Private IsLeftMouseHeld As Boolean = False
|
||||
Private Sub Button_LeftMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonDown
|
||||
If Not IsLeftMouseHeld Then
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
|
||||
AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
|
||||
}, "MyExtraTextButton Scale " & Uuid)
|
||||
End If
|
||||
IsLeftMouseHeld = True
|
||||
Focus()
|
||||
End Sub
|
||||
Private Sub Button_LeftMouseUp() Handles PanClick.MouseLeftButtonUp
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
|
||||
}, "MyExtraTextButton Scale " & Uuid)
|
||||
IsLeftMouseHeld = False
|
||||
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
|
||||
If Not IsLeftMouseHeld Then
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
|
||||
}, "MyExtraTextButton Scale " & Uuid)
|
||||
End If
|
||||
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
Private Sub Button_MouseLeave() Handles PanClick.MouseLeave
|
||||
IsLeftMouseHeld = False
|
||||
AniStart({
|
||||
AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
|
||||
}, "MyExtraTextButton Scale " & Uuid)
|
||||
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
|
||||
'自定义事件
|
||||
'务必放在 IsMouseDown 更新之后
|
||||
Private Const AnimationColorIn As Integer = 120
|
||||
Private Const AnimationColorOut As Integer = 150
|
||||
Public Sub RefreshColor() Handles PanClick.MouseEnter, PanClick.MouseLeave, Me.Loaded, Me.IsEnabledChanged
|
||||
Try
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
|
||||
If Not IsEnabled Then
|
||||
'禁用
|
||||
AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
|
||||
ElseIf IsMouseOver Then
|
||||
'指向
|
||||
AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
|
||||
Else
|
||||
'普通
|
||||
AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush3", AnimationColorOut), "MyExtraTextButton Color " & Uuid)
|
||||
End If
|
||||
|
||||
Else
|
||||
|
||||
AniStop("MyExtraTextButton Color " & Uuid)
|
||||
If Not IsEnabled Then
|
||||
PanColor.SetResourceReference(BackgroundProperty, "ColorBrushGray4")
|
||||
ElseIf IsMouseOver Then
|
||||
PanColor.SetResourceReference(BackgroundProperty, "ColorBrush4")
|
||||
Else
|
||||
PanColor.SetResourceReference(BackgroundProperty, "ColorBrush3")
|
||||
End If
|
||||
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, "刷新附加图标按钮颜色出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -1,24 +1,16 @@
|
||||
<Border x:Class="MyHint" x:Name="PanBack"
|
||||
<Border x:Class="MyHint" x:Name="PanBack"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="1" BorderBrush="#99FF4444" CornerRadius="3">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
|
||||
<GradientStop x:Name="Gradient1" Color="#88FFBBBB" Offset="0.3"/>
|
||||
<GradientStop x:Name="Gradient2" Color="#88FF8888" Offset="0.9"/>
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="3,0,0,0" BorderBrush="#99FF4444" CornerRadius="2">
|
||||
<Grid UseLayoutRounding="False" SnapsToDevicePixels="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path IsHitTestVisible="False" Grid.Column="0" x:Name="Path" VerticalAlignment="Center" Fill="#BF0000" Width="14" Stretch="Uniform" Margin="10,0,2,0" Data="F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z " />
|
||||
<TextBlock IsHitTestVisible="False" Grid.Column="1" x:Name="LabText" Margin="0,0.5,0,0" Padding="6,6,8,6" Text="提示文本" VerticalAlignment="Center" Foreground="#BF0000" TextWrapping="Wrap" />
|
||||
<local:MyIconButton x:Name="BtnClose" SnapsToDevicePixels="True" Visibility="Collapsed" Foreground="#BF0000" Theme="Custom" Margin="0,0,6,0" Grid.Column="2" Height="20" Width="20" VerticalAlignment="Center" Logo="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
|
||||
ToolTip="不再显示" ToolTipService.Placement="Center" ToolTipService.VerticalOffset="29" ToolTipService.HorizontalOffset="2" />
|
||||
<TextBlock IsHitTestVisible="False" x:Name="LabText" LineHeight="16" Margin="0,0,0,-1" Padding="12,9" VerticalAlignment="Center" TextWrapping="Wrap" />
|
||||
<local:MyIconButton x:Name="BtnClose" Visibility="Collapsed" Theme="Custom" Margin="0,0,8,0" Grid.Column="1" Height="20" Width="20" VerticalAlignment="Center" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z"
|
||||
ToolTip="不再提示" />
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -1,44 +1,94 @@
|
||||
Public Class MyHint
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyHint
|
||||
Public Uuid As Integer = GetUuid()
|
||||
|
||||
Private _IsWarn As Boolean = True
|
||||
Public Property IsWarn As Boolean
|
||||
'边框
|
||||
Public Property HasBorder As Boolean
|
||||
Get
|
||||
Return _IsWarn
|
||||
Return BorderThickness.Top > 0
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
If _IsWarn = value Then Exit Property
|
||||
_IsWarn = value
|
||||
If _IsWarn Then
|
||||
BorderBrush = New MyColor("#CCFF4444")
|
||||
Gradient1.Color = New MyColor("#BBFFBBBB")
|
||||
Gradient2.Color = New MyColor("#BBFF8888")
|
||||
Path.Fill = New MyColor("#BF0000")
|
||||
LabText.Foreground = New MyColor("#BF0000")
|
||||
BtnClose.Foreground = New MyColor("#BF0000")
|
||||
Path.Data = (New GeometryConverter).ConvertFromString("F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z ")
|
||||
If value Then
|
||||
BorderThickness = New Thickness(3, GetWPFSize(1), GetWPFSize(1), GetWPFSize(1))
|
||||
Else
|
||||
BorderBrush = New MyColor("#CC4D76FF")
|
||||
Gradient1.Color = New MyColor("#BBB0D0FF")
|
||||
Gradient2.Color = New MyColor("#BB9EBAFF")
|
||||
Path.Fill = New MyColor("#0062BF")
|
||||
LabText.Foreground = New MyColor("#0062BF")
|
||||
BtnClose.Foreground = New MyColor("#0062BF")
|
||||
Path.Data = (New GeometryConverter).ConvertFromString("F1M38,19C48.4934,19 57,27.5066 57,38 57,48.4934 48.4934,57 38,57 27.5066,57 19,48.4934 19,38 19,27.5066 27.5066,19 38,19z M33.25,33.25L33.25,36.4167 36.4166,36.4167 36.4166,47.5 33.25,47.5 33.25,50.6667 44.3333,50.6667 44.3333,47.5 41.1666,47.5 41.1666,36.4167 41.1666,33.25 33.25,33.25z M38.7917,25.3333C37.48,25.3333 36.4167,26.3967 36.4167,27.7083 36.4167,29.02 37.48,30.0833 38.7917,30.0833 40.1033,30.0833 41.1667,29.02 41.1667,27.7083 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333z")
|
||||
BorderThickness = New Thickness(3, 0, 0, 0)
|
||||
End If
|
||||
End Set
|
||||
End Property
|
||||
|
||||
Public Property Text As String
|
||||
'配色
|
||||
Public Enum Themes
|
||||
Blue = 0
|
||||
Red = 1
|
||||
Yellow = 2
|
||||
End Enum
|
||||
Public Property Theme As Themes
|
||||
Get
|
||||
Return LabText.Text
|
||||
Return _ColorType
|
||||
End Get
|
||||
Set(value As String)
|
||||
LabText.Text = value
|
||||
Set(value As Themes)
|
||||
_ColorType = value
|
||||
UpdateUI()
|
||||
End Set
|
||||
End Property
|
||||
Private _ColorType As Themes = Themes.Red
|
||||
Private Sub UpdateUI() Handles Me.Loaded
|
||||
Select Case Theme
|
||||
Case Themes.Blue
|
||||
Background = New MyColor("#d9ecff")
|
||||
BorderBrush = New MyColor("#1172D4")
|
||||
LabText.Foreground = New MyColor("#0F64B8")
|
||||
BtnClose.Foreground = New MyColor("#0F64B8")
|
||||
Case Themes.Red
|
||||
Background = New MyColor("#ffdddf")
|
||||
BorderBrush = New MyColor("#d82929")
|
||||
LabText.Foreground = New MyColor("#bf0b0b")
|
||||
BtnClose.Foreground = New MyColor("#bf0b0b")
|
||||
Case Themes.Yellow
|
||||
Background = New MyColor("#ffebd7")
|
||||
BorderBrush = New MyColor("#f57a00")
|
||||
LabText.Foreground = New MyColor("#d86c00")
|
||||
BtnClose.Foreground = New MyColor("#d86c00")
|
||||
End Select
|
||||
End Sub
|
||||
<Obsolete("IsWarn 已过时。请换用 Theme 属性。")>
|
||||
Public Property IsWarn As Boolean
|
||||
Get
|
||||
Return Theme = Themes.Red
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
Theme = If(value, Themes.Red, Themes.Blue)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly IsWarnProperty As DependencyProperty =
|
||||
DependencyProperty.Register("IsWarn", GetType(Boolean), GetType(MyHint), New PropertyMetadata(True,
|
||||
Sub(d As MyHint, e As DependencyPropertyChangedEventArgs)
|
||||
d.Theme = If(e.NewValue, Themes.Red, Themes.Blue)
|
||||
End Sub))
|
||||
|
||||
'文本
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return GetValue(TextProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(TextProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty =
|
||||
DependencyProperty.Register("Text", GetType(String), GetType(MyHint), New PropertyMetadata("",
|
||||
Sub(d As MyHint, e As DependencyPropertyChangedEventArgs)
|
||||
d.LabText.Text = e.NewValue
|
||||
End Sub))
|
||||
|
||||
'关闭按钮
|
||||
Public Property CanClose As Boolean
|
||||
Get
|
||||
Return BtnClose.Visibility = Visibility.Visible
|
||||
@@ -47,26 +97,26 @@
|
||||
BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
Public RelativeSetup As String = ""
|
||||
Public Property RelativeSetup As String = ""
|
||||
Private Sub MyHint_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
|
||||
If CanClose AndAlso Setup.Get(RelativeSetup) Then
|
||||
Visibility = Visibility.Collapsed
|
||||
End If
|
||||
If CanClose AndAlso Settings.Get(Of Boolean)(RelativeSetup) Then Visibility = Visibility.Collapsed
|
||||
End Sub
|
||||
Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click
|
||||
Setup.Set(RelativeSetup, True)
|
||||
Settings.SetSafe(RelativeSetup, True)
|
||||
AniDispose(Me, False)
|
||||
End Sub
|
||||
|
||||
'触发点击事件
|
||||
Public Event Click(sender As Object, e As MouseButtonEventArgs)
|
||||
Private IsMouseDown As Boolean = False
|
||||
Private Sub MyHint_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
|
||||
If Not IsMouseDown Then Return
|
||||
IsMouseDown = False
|
||||
Log("[Control] 按下提示条" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
|
||||
Logger.Info($"按下提示条{If(String.IsNullOrEmpty(Name), "", ":" & Name)}")
|
||||
RaiseEvent Click(sender, e)
|
||||
If e.Handled Then Return
|
||||
e.Handled = True
|
||||
ModEvent.TryStartEvent(EventType, EventData)
|
||||
RaiseCustomEvent() '自定义事件
|
||||
End Sub
|
||||
Private Sub MyHint_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
|
||||
IsMouseDown = True
|
||||
@@ -74,42 +124,25 @@
|
||||
Private Sub MyHint_MouseLeave() Handles Me.MouseLeave
|
||||
IsMouseDown = False
|
||||
End Sub
|
||||
Public Property EventType As String
|
||||
Get
|
||||
Return GetValue(EventTypeProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventTypeProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing))
|
||||
Public Property EventData As String
|
||||
Get
|
||||
Return GetValue(EventDataProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventDataProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing))
|
||||
|
||||
End Class
|
||||
Partial Public Module ModAnimation
|
||||
Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
|
||||
If Not Control.IsHitTestVisible Then Exit Sub
|
||||
If Not Control.IsHitTestVisible Then Return
|
||||
Control.IsHitTestVisible = False
|
||||
AniStart({
|
||||
AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
|
||||
AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
|
||||
AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
|
||||
AaCode(Sub()
|
||||
If RemoveFromChildren Then
|
||||
CType(Control.Parent, Object).Children.Remove(Control)
|
||||
Else
|
||||
Control.Visibility = Visibility.Collapsed
|
||||
End If
|
||||
If CallBack IsNot Nothing Then CallBack(Control)
|
||||
End Sub,, True)
|
||||
}, "MyCard Dispose " & Control.Uuid)
|
||||
AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent),
|
||||
AaOpacity(Control, -1, 200,, New AniEaseOutFluent),
|
||||
AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent),
|
||||
AaCode(
|
||||
Sub()
|
||||
If RemoveFromChildren Then
|
||||
CType(Control.Parent, Object).Children.Remove(Control)
|
||||
Else
|
||||
Control.Visibility = Visibility.Collapsed
|
||||
End If
|
||||
If CallBack IsNot Nothing Then CallBack(Control)
|
||||
End Sub,, True)
|
||||
}, "MyCard Dispose " & Control.Uuid)
|
||||
End Sub
|
||||
End Module
|
||||
@@ -1,4 +1,4 @@
|
||||
<Border x:Class="MyIconButton"
|
||||
<Border x:Class="MyIconButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
CornerRadius="1000" Background="{StaticResource ColorBrushSemiTransparent}" BorderThickness="0.001">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
Public Class MyIconButton
|
||||
Imports System.Windows.Shapes
|
||||
|
||||
Public Class MyIconButton
|
||||
|
||||
'自定义事件
|
||||
Public Event Click(sender As Object, e As EventArgs)
|
||||
@@ -48,43 +50,27 @@
|
||||
End Set
|
||||
End Property
|
||||
|
||||
'触发点击事件
|
||||
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
|
||||
Private IsMouseDown As Boolean = False
|
||||
Public Property CanClick As Boolean = True
|
||||
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
|
||||
If Not CanClick Then Return
|
||||
If Not IsMouseDown Then Return
|
||||
Log("[Control] 按下图标按钮" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
|
||||
Logger.Info($"按下图标按钮{If(String.IsNullOrEmpty(Name), "", ":" & Name)}")
|
||||
RaiseEvent Click(sender, e)
|
||||
e.Handled = True
|
||||
Button_MouseUp()
|
||||
ModEvent.TryStartEvent(EventType, EventData)
|
||||
RaiseCustomEvent() '自定义事件
|
||||
End Sub
|
||||
Public Property EventType As String
|
||||
Get
|
||||
Return GetValue(EventTypeProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventTypeProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing))
|
||||
Public Property EventData As String
|
||||
Get
|
||||
Return GetValue(EventDataProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventDataProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing))
|
||||
|
||||
'鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
|
||||
Private IsMouseDown As Boolean = False
|
||||
Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown
|
||||
If Not CanClick Then Return
|
||||
IsMouseDown = True
|
||||
Focus()
|
||||
'指向
|
||||
AniStart(AaScaleTransform(PanBack, 0.8 - CType(PanBack.RenderTransform, ScaleTransform).ScaleX, 400,, New AniEaseOutFluent(AniEasePower.Strong)), "MyIconButton Scale " & Uuid)
|
||||
End Sub
|
||||
Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp
|
||||
If Not CanClick Then Return
|
||||
If IsMouseDown Then
|
||||
IsMouseDown = False
|
||||
AniStart({
|
||||
@@ -95,6 +81,7 @@
|
||||
RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
Private Sub Button_MouseLeave() Handles Me.MouseLeave
|
||||
If Not CanClick Then Return
|
||||
IsMouseDown = False
|
||||
AniStart({
|
||||
AaScaleTransform(PanBack, 1 - CType(PanBack.RenderTransform, ScaleTransform).ScaleX, 250,, New AniEaseOutFluent)
|
||||
@@ -102,14 +89,12 @@
|
||||
RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave
|
||||
End Sub
|
||||
|
||||
'自定义事件
|
||||
'务必放在 IsMouseDown 更新之后
|
||||
Private Const AnimationColorIn As Integer = 120
|
||||
Private Const AnimationColorOut As Integer = 150
|
||||
Public Sub RefreshAnim() Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded
|
||||
Public Sub RefreshAnim() Handles Me.MouseEnter, Me.MouseLeave, Me.Loaded, Me.IsEnabledChanged
|
||||
Try
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
|
||||
If PanBack.Background Is Nothing Then PanBack.Background = New MyColor(0, 255, 255, 255)
|
||||
If Path.Fill Is Nothing Then
|
||||
Select Case Theme
|
||||
@@ -121,9 +106,10 @@
|
||||
Path.Fill = New MyColor(160, Foreground)
|
||||
End Select
|
||||
End If
|
||||
If IsMouseOver Then
|
||||
'指向
|
||||
Dim AnimList As New List(Of AniData)
|
||||
Dim AnimList As New List(Of AniData)
|
||||
If Not IsEnabled Then
|
||||
AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrushGray5", AnimationColorIn))
|
||||
ElseIf IsMouseOver Then '指向
|
||||
Select Case Theme
|
||||
Case Themes.Color
|
||||
AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush2", AnimationColorIn))
|
||||
@@ -136,10 +122,7 @@
|
||||
Case Themes.Custom
|
||||
AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(255, Foreground) - Path.Fill, AnimationColorIn))
|
||||
End Select
|
||||
AniStart(AnimList, "MyIconButton Color " & Uuid)
|
||||
Else
|
||||
'普通
|
||||
Dim AnimList As New List(Of AniData)
|
||||
Else '普通
|
||||
Select Case Theme
|
||||
Case Themes.Color
|
||||
AnimList.Add(AaColor(Path, Shape.FillProperty, "ColorBrush4", AnimationColorOut))
|
||||
@@ -157,36 +140,37 @@
|
||||
AnimList.Add(AaColor(Path, Shape.FillProperty, New MyColor(160, Foreground) - Path.Fill, AnimationColorOut))
|
||||
PanBack.Background = New MyColor(0, 255, 255, 255)
|
||||
End Select
|
||||
AniStart(AnimList, "MyIconButton Color " & Uuid)
|
||||
End If
|
||||
|
||||
AniStart(AnimList, "MyIconButton Color " & Uuid)
|
||||
Else
|
||||
|
||||
AniStop("MyIconButton Color " & Uuid)
|
||||
Select Case Theme
|
||||
Case Themes.Color
|
||||
Path.SetResourceReference(Shape.FillProperty, "ColorBrush5")
|
||||
Case Themes.White
|
||||
Path.SetResourceReference(Shape.FillProperty, "ColorBrush8")
|
||||
Case Themes.Red
|
||||
Path.Fill = New MyColor(160, 255, 76, 76)
|
||||
Case Themes.Black
|
||||
Path.Fill = New MyColor(160, 0, 0, 0)
|
||||
Case Themes.Custom
|
||||
Path.Fill = New MyColor(160, Foreground)
|
||||
End Select
|
||||
If IsEnabled Then
|
||||
Select Case Theme
|
||||
Case Themes.Color
|
||||
Path.SetResourceReference(Shape.FillProperty, "ColorBrush5")
|
||||
Case Themes.White
|
||||
Path.SetResourceReference(Shape.FillProperty, "ColorBrush8")
|
||||
Case Themes.Red
|
||||
Path.Fill = New MyColor(160, 255, 76, 76)
|
||||
Case Themes.Black
|
||||
Path.Fill = New MyColor(160, 0, 0, 0)
|
||||
Case Themes.Custom
|
||||
Path.Fill = New MyColor(160, Foreground)
|
||||
End Select
|
||||
Else
|
||||
Path.SetResourceReference(Shape.FillProperty, "ColorBrushGray5")
|
||||
End If
|
||||
PanBack.Background = New MyColor(0, 255, 255, 255)
|
||||
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新图标按钮动画状态出错")
|
||||
Logger.Warn(ex, "刷新图标按钮动画状态出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
Partial Public Module ModAnimation
|
||||
Public Sub AniDispose(Control As MyIconButton, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
|
||||
If Not Control.IsHitTestVisible Then Exit Sub
|
||||
If Not Control.IsHitTestVisible Then Return
|
||||
Control.IsHitTestVisible = False
|
||||
AniStart({
|
||||
AaScaleTransform(Control, -1.5, 200,, New AniEaseInFluent),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Border x:Name="PanBack" x:Class="MyIconTextButton"
|
||||
<Border x:Name="PanBack" x:Class="MyIconTextButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Public Class MyIconTextButton
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyIconTextButton
|
||||
|
||||
'基础
|
||||
|
||||
Public Uuid As Integer = GetUuid()
|
||||
Public Event Check(sender As Object, raiseByMouse As Boolean)
|
||||
Public Event Change(sender As Object, raiseByMouse As Boolean)
|
||||
Public Sub RaiseChange()
|
||||
RaiseEvent Change(Me, False)
|
||||
End Sub '使外部程序可以引发本控件的 Change 事件
|
||||
|
||||
'自定义属性
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
End Set
|
||||
End Property
|
||||
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return GetValue(TextProperty)
|
||||
@@ -39,36 +44,36 @@
|
||||
End Set
|
||||
End Property '内容
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyIconTextButton).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyIconTextButton).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Public Enum ColorState
|
||||
Black
|
||||
Highlight
|
||||
End Enum
|
||||
Private _ColorType As ColorState = ColorState.Black
|
||||
Public Property ColorType As ColorState
|
||||
Get
|
||||
Return _ColorType
|
||||
Return GetValue(ColorTypeProperty)
|
||||
End Get
|
||||
Set(value As ColorState)
|
||||
_ColorType = value
|
||||
If ColorType = value Then Return
|
||||
SetValue(ColorTypeProperty, value)
|
||||
RefreshColor()
|
||||
End Set
|
||||
End Property '颜色类别
|
||||
Public Shared ReadOnly ColorTypeProperty As DependencyProperty =
|
||||
DependencyProperty.Register("ColorType", GetType(ColorState), GetType(MyIconTextButton), New PropertyMetadata())
|
||||
DependencyProperty.Register("ColorType", GetType(ColorState), GetType(MyIconTextButton), New PropertyMetadata(ColorState.Black))
|
||||
|
||||
'点击事件
|
||||
|
||||
Public Event Click(sender As Object, e As RouteEventArgs)
|
||||
Private IsMouseDown As Boolean = False
|
||||
Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp
|
||||
If Not IsMouseDown Then Exit Sub
|
||||
Log("[Control] 按下带图标按钮:" & Text)
|
||||
If Not IsMouseDown Then Return
|
||||
Logger.Info($"按下带图标按钮:{Text}")
|
||||
IsMouseDown = False
|
||||
RaiseEvent Click(Me, New RouteEventArgs(True))
|
||||
ModEvent.TryStartEvent(EventType, EventData)
|
||||
RaiseCustomEvent() '自定义事件
|
||||
RefreshColor()
|
||||
End Sub
|
||||
Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown
|
||||
@@ -79,24 +84,6 @@
|
||||
IsMouseDown = False
|
||||
RefreshColor()
|
||||
End Sub
|
||||
Public Property EventType As String
|
||||
Get
|
||||
Return GetValue(EventTypeProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventTypeProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
|
||||
Public Property EventData As String
|
||||
Get
|
||||
Return GetValue(EventDataProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventDataProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing))
|
||||
|
||||
'动画
|
||||
|
||||
@@ -179,7 +166,7 @@
|
||||
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新带图标按钮颜色出错")
|
||||
Logger.Warn(ex, "刷新带图标按钮颜色出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
Public Class MyImage
|
||||
Inherits Image
|
||||
|
||||
#Region "公开属性"
|
||||
|
||||
''' <summary>
|
||||
''' 网络图片的缓存有效期。
|
||||
''' 在这个时间后,才会重新尝试下载图片。
|
||||
''' </summary>
|
||||
Public FileCacheExpiredTime As New TimeSpan(7, 0, 0, 0) '7 天
|
||||
|
||||
''' <summary>
|
||||
''' 是否允许将网络图片存储到本地用作缓存。
|
||||
''' </summary>
|
||||
Public Property EnableCache As Boolean
|
||||
Get
|
||||
Return GetValue(EnableCacheProperty)
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
SetValue(EnableCacheProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared Shadows ReadOnly EnableCacheProperty As DependencyProperty = DependencyProperty.Register(
|
||||
"EnableCache", GetType(Boolean), GetType(MyImage), New PropertyMetadata(True))
|
||||
|
||||
''' <summary>
|
||||
''' 与 Image 的 Source 类似。
|
||||
''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示;若 EnableCache 设为 True,图片还会保存为本地缓存。
|
||||
''' 支持 WebP 格式的图片。
|
||||
''' </summary>
|
||||
Public Shadows Property Source As String '覆写 Image 的 Source 属性
|
||||
Get
|
||||
Return _Source
|
||||
End Get
|
||||
Set(value As String)
|
||||
If value = "" Then value = Nothing
|
||||
If _Source = value Then Return
|
||||
_Source = value
|
||||
If Not IsInitialized Then Return '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
|
||||
Load()
|
||||
End Set
|
||||
End Property
|
||||
Private _Source As String = ""
|
||||
Public Shared Shadows ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register(
|
||||
"Source", GetType(String), GetType(MyImage), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
|
||||
|
||||
''' <summary>
|
||||
''' 若 Source 是一个网络图片,该地址将作为第二图片源。
|
||||
''' </summary>
|
||||
Public Property FallbackSource As String
|
||||
Get
|
||||
Return _FallbackSource
|
||||
End Get
|
||||
Set(value As String)
|
||||
_FallbackSource = value
|
||||
End Set
|
||||
End Property
|
||||
Private _FallbackSource As String = Nothing
|
||||
|
||||
''' <summary>
|
||||
''' 正在下载网络图片时显示的本地图片。
|
||||
''' </summary>
|
||||
Public Property LoadingSource As String
|
||||
Get
|
||||
Return _LoadingSource
|
||||
End Get
|
||||
Set(value As String)
|
||||
_LoadingSource = value
|
||||
End Set
|
||||
End Property
|
||||
Private _LoadingSource As String = "pack://application:,,,/images/Icons/NoIcon.png"
|
||||
|
||||
#End Region
|
||||
|
||||
''' <summary>
|
||||
''' 实际被呈现的图片地址。
|
||||
''' </summary>
|
||||
Public Property ActualSource As String
|
||||
Get
|
||||
Return _ActualSource
|
||||
End Get
|
||||
Set(value As String)
|
||||
If value = "" Then value = Nothing
|
||||
If _ActualSource = value Then Return
|
||||
_ActualSource = value
|
||||
Try
|
||||
Dim Bitmap As ImageSource = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
|
||||
RunInUi(Sub() MyBase.Source = Bitmap)
|
||||
Catch ex As Exception
|
||||
Logger.Warn(ex, $"加载图片失败({value})")
|
||||
Try
|
||||
If value.StartsWithF(PathTemp, True) Then FileUtils.Delete(value)
|
||||
Catch
|
||||
End Try
|
||||
End Try
|
||||
End Set
|
||||
End Property
|
||||
Private _ActualSource As String = Nothing
|
||||
|
||||
Private Sub Load() _
|
||||
Handles Me.Initialized '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
|
||||
'空
|
||||
If Source Is Nothing Then
|
||||
ActualSource = Nothing
|
||||
Return
|
||||
End If
|
||||
'本地图片
|
||||
If Not Source.StartsWithF("http") Then
|
||||
ActualSource = Source
|
||||
Return
|
||||
End If
|
||||
'从缓存加载网络图片
|
||||
Dim EnableCache As Boolean = Me.EnableCache
|
||||
Dim TempPath As String = $"{PathTemp}MyImage\{Source.GetStableHashCode()}.png{If(EnableCache, "", GetUuid())}" '不启用缓存时在末尾加上随机字符串,避免冲突
|
||||
Dim TempFile = FileUtils.GetInfo(TempPath)
|
||||
If EnableCache AndAlso TempFile.Exists Then
|
||||
ActualSource = TempPath '显示缓存的图片
|
||||
If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存
|
||||
Else
|
||||
ActualSource = LoadingSource '显示加载中图片
|
||||
End If
|
||||
RunInNewThread(
|
||||
Sub()
|
||||
Dim IsLocalFallback As Boolean = Not String.IsNullOrEmpty(FallbackSource) AndAlso Not FallbackSource.StartsWithF("http")
|
||||
Try
|
||||
'下载
|
||||
NetDownloadByLoader(
|
||||
If(FallbackSource IsNot Nothing AndAlso FallbackSource.StartsWithF("http") AndAlso Source <> FallbackSource,
|
||||
{Source, FallbackSource}, {Source}),
|
||||
TempPath, SimulateBrowserHeaders:=True)
|
||||
ActualSource = TempPath
|
||||
If Not EnableCache Then FileUtils.Delete(TempPath)
|
||||
Catch ex As Exception
|
||||
Try
|
||||
If TempPath IsNot Nothing Then FileUtils.Delete(TempPath)
|
||||
Catch
|
||||
End Try
|
||||
'加载本地备用图片
|
||||
If IsLocalFallback Then
|
||||
Try
|
||||
ActualSource = FallbackSource
|
||||
Logger.Warn(ex, $"下载图片失败,使用本地备用图片({Source})")
|
||||
Return
|
||||
Catch exx As Exception
|
||||
Logger.Error(ex, $"下载图片失败({Source})", LogBehavior.Toast)
|
||||
Logger.Error(exx, $"加载备用图片失败({FallbackSource})", LogBehavior.Toast)
|
||||
End Try
|
||||
Else
|
||||
Logger.Error(ex, $"下载图片失败({Source},备用:{FallbackSource})", LogBehavior.Toast)
|
||||
End If
|
||||
End Try
|
||||
End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
<Grid x:Class="MyListItem"
|
||||
<Grid x:Class="MyListItem"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Name="PanBack"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
Public Class MyListItem
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyListItem
|
||||
Implements IMyRadio
|
||||
|
||||
Public Event Click(sender As Object, e As MouseButtonEventArgs)
|
||||
@@ -161,12 +164,17 @@
|
||||
End Property
|
||||
|
||||
'标题
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabTitle.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Title As String
|
||||
Get
|
||||
Return GetValue(TitleProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(TitleProperty, value)
|
||||
SetValue(TitleProperty, value.ReplaceLineEndings(""))
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly TitleProperty As DependencyProperty = DependencyProperty.Register("Title", GetType(String), GetType(MyListItem))
|
||||
@@ -189,8 +197,8 @@
|
||||
Return _Info
|
||||
End Get
|
||||
Set(value As String)
|
||||
If _Info = value Then Exit Property
|
||||
value = value.Replace(vbCr, "").Replace(vbLf, "")
|
||||
If _Info = value Then Return
|
||||
value = value.ReplaceLineEndings("")
|
||||
_Info = value
|
||||
LabInfo.Text = value
|
||||
LabInfo.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
|
||||
@@ -204,7 +212,7 @@
|
||||
Return _Logo
|
||||
End Get
|
||||
Set(value As String)
|
||||
If _Logo = value Then Exit Property
|
||||
If _Logo = value Then Return
|
||||
_Logo = value
|
||||
'删除旧 Logo
|
||||
If Not IsNothing(PathLogo) Then Children.Remove(PathLogo)
|
||||
@@ -212,33 +220,26 @@
|
||||
If Not _Logo = "" Then
|
||||
If _Logo.StartsWithF("http", True) Then
|
||||
'网络图片
|
||||
PathLogo = New Image With {
|
||||
PathLogo = New MyImage With {
|
||||
.Tag = Me,
|
||||
.IsHitTestVisible = LogoClickable,
|
||||
.Source = New ImageSourceConverter().ConvertFromString(_Logo),
|
||||
.Source = _Logo,
|
||||
.RenderTransformOrigin = New Point(0.5, 0.5),
|
||||
.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
|
||||
.SnapsToDevicePixels = True, .UseLayoutRounding = False}
|
||||
RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
|
||||
ElseIf _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) Then
|
||||
ElseIf _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) OrElse _Logo.EndsWithF(".webp", True) Then
|
||||
'位图
|
||||
Dim Bitmap = New MyBitmap(_Logo)
|
||||
PathLogo = New Canvas With {
|
||||
.Tag = Me,
|
||||
.IsHitTestVisible = LogoClickable,
|
||||
.Background = Bitmap,
|
||||
.Background = New MyBitmap(_Logo),
|
||||
.RenderTransformOrigin = New Point(0.5, 0.5),
|
||||
.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
|
||||
.SnapsToDevicePixels = True, .UseLayoutRounding = False}
|
||||
'If Bitmap.Pic.Width = 16 AndAlso Bitmap.Pic.Height = 16 Then
|
||||
' '使用最适合 16x16 物品图片显示的大小
|
||||
' RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.NearestNeighbor)
|
||||
' PathLogo.HorizontalAlignment = HorizontalAlignment.Center : PathLogo.VerticalAlignment = VerticalAlignment.Center
|
||||
' PathLogo.Width = GetWPFSize(Math.Floor(GetPixelSize(32) / 16) * 16) : PathLogo.Height = PathLogo.Width
|
||||
'Else
|
||||
PathLogo.HorizontalAlignment = HorizontalAlignment.Stretch : PathLogo.VerticalAlignment = VerticalAlignment.Stretch
|
||||
RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.HighQuality)
|
||||
'End If
|
||||
.SnapsToDevicePixels = True, .UseLayoutRounding = False,
|
||||
.HorizontalAlignment = HorizontalAlignment.Stretch, .VerticalAlignment = VerticalAlignment.Stretch
|
||||
}
|
||||
RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
|
||||
Else
|
||||
'矢量图
|
||||
PathLogo = New Shapes.Path With {
|
||||
@@ -296,7 +297,7 @@
|
||||
Return _Type
|
||||
End Get
|
||||
Set(value As CheckType)
|
||||
If _Type = value Then Exit Property
|
||||
If _Type = value Then Return
|
||||
_Type = value
|
||||
'切换左栏大小
|
||||
ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
|
||||
@@ -311,7 +312,8 @@
|
||||
Else
|
||||
'添加竖条控件
|
||||
If IsNothing(RectCheck) Then
|
||||
RectCheck = New Border With {.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
|
||||
RectCheck = New Border With {
|
||||
.Width = 5, .Height = If(Checked, Double.NaN, 0), .CornerRadius = New CornerRadius(2, 2, 2, 2),
|
||||
.VerticalAlignment = If(Checked, VerticalAlignment.Stretch, VerticalAlignment.Center),
|
||||
.HorizontalAlignment = HorizontalAlignment.Left, .UseLayoutRounding = False, .SnapsToDevicePixels = False,
|
||||
.Margin = If(Checked, New Thickness(-1, 6, 0, 6), New Thickness(-1, 0, 0, 0))}
|
||||
@@ -327,8 +329,8 @@
|
||||
Private Sub OnSizeChanged() Handles Me.SizeChanged
|
||||
ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
|
||||
ColumnLogo.Width = New GridLength(If(_Logo = "", 0, 34) + If(Height < 40, 0, 4))
|
||||
If Not IsNothing(PathLogo) Then
|
||||
If _Logo.EndsWithF(".png", True) Then
|
||||
If PathLogo IsNot Nothing Then
|
||||
If _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) OrElse _Logo.EndsWithF(".webp", True) Then
|
||||
PathLogo.Margin = New Thickness(4, 5, 3, 5)
|
||||
Else
|
||||
PathLogo.Margin = New Thickness(If(Height < 40, 6, 8), 8, If(Height < 40, 4, 6), 8)
|
||||
@@ -344,7 +346,7 @@
|
||||
Return _Checked
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
SetChecked(value, False, True)
|
||||
SetChecked(value, False, value <> _Checked) '仅在值发生变化时触发动画 (#4596)
|
||||
End Set
|
||||
End Property
|
||||
''' <summary>
|
||||
@@ -366,35 +368,36 @@
|
||||
RaiseEvent Changed(Me, ChangedEventArgs)
|
||||
If ChangedEventArgs.Handled Then
|
||||
_Checked = RawValue
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
End If
|
||||
_Checked = value
|
||||
Else
|
||||
If value = _Checked Then Exit Sub
|
||||
If value = _Checked Then Return
|
||||
_Checked = value
|
||||
If IsInitialized Then
|
||||
RaiseEvent Changed(Me, ChangedEventArgs)
|
||||
If ChangedEventArgs.Handled Then
|
||||
_Checked = RawValue
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
End If
|
||||
End If
|
||||
If value Then
|
||||
Dim CheckEventArgs As New RouteEventArgs(user)
|
||||
RaiseEvent Check(Me, CheckEventArgs)
|
||||
If CheckEventArgs.Handled Then Exit Sub
|
||||
If CheckEventArgs.Handled Then Return
|
||||
End If
|
||||
|
||||
'保证只有一个单选 ListItem 选中
|
||||
|
||||
If Type = CheckType.RadioBox Then
|
||||
If IsNothing(Parent) Then Exit Sub
|
||||
If IsNothing(Parent) Then Return
|
||||
Dim RadioboxList As New List(Of MyListItem)
|
||||
Dim CheckedCount As Integer = 0
|
||||
'收集控件列表与选中个数
|
||||
For Each Control In CType(Parent, Object).Children
|
||||
Control = MyVirtualizingElement.TryInit(Control)
|
||||
If TypeOf Control Is MyListItem AndAlso CType(Control, MyListItem).Type = CheckType.RadioBox Then
|
||||
RadioboxList.Add(Control)
|
||||
If Control.Checked Then CheckedCount += 1
|
||||
@@ -477,7 +480,7 @@
|
||||
End If
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "设置 Checked 失败")
|
||||
Logger.Warn(ex, "设置 Checked 失败")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -501,24 +504,25 @@
|
||||
|
||||
'触发点击事件
|
||||
Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
|
||||
If Not IsMouseDown Then Exit Sub
|
||||
If Not IsMouseDown Then Return
|
||||
RaiseEvent Click(sender, e)
|
||||
If e.Handled Then Exit Sub
|
||||
If e.Handled Then Return
|
||||
'触发自定义事件
|
||||
If Not String.IsNullOrEmpty(EventType) Then
|
||||
ModEvent.TryStartEvent(EventType, EventData)
|
||||
If CustomEventService.GetEventType(sender) <> CustomEvent.EventType.None OrElse
|
||||
CustomEventService.GetEvents(sender).Events.Count > 0 Then
|
||||
RaiseCustomEvent()
|
||||
e.Handled = True
|
||||
End If
|
||||
If e.Handled Then Exit Sub
|
||||
If e.Handled Then Return
|
||||
'实际的单击处理
|
||||
Select Case Type
|
||||
Case CheckType.Clickable
|
||||
Log("[Control] 按下单击列表项:" & Title)
|
||||
Logger.Info($"按下单击列表项:{Title}")
|
||||
Case CheckType.RadioBox
|
||||
Log("[Control] 按下单选列表项:" & Title)
|
||||
Logger.Info($"按下单选列表项:{Title}")
|
||||
If Not Checked Then SetChecked(True, True, True)
|
||||
Case CheckType.CheckBox
|
||||
Log("[Control] 按下复选列表项(" & (Not Checked).ToString & "):" & Title)
|
||||
Logger.Info($"按下复选列表项({Not Checked}):{Title}")
|
||||
SetChecked(Not Checked, True, True)
|
||||
End Select
|
||||
End Sub
|
||||
@@ -536,34 +540,14 @@
|
||||
If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True
|
||||
End Sub
|
||||
|
||||
'实现自定义事件
|
||||
Public Property EventType As String
|
||||
Get
|
||||
Return GetValue(EventTypeProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventTypeProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing))
|
||||
Public Property EventData As String
|
||||
Get
|
||||
Return GetValue(EventDataProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
SetValue(EventDataProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing))
|
||||
|
||||
#End Region
|
||||
|
||||
Private StateLast As String
|
||||
Public IsMouseOverAnimationEnabled As Boolean = True
|
||||
Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
|
||||
Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.PreviewMouseLeftButtonDown, Me.PreviewMouseLeftButtonUp
|
||||
'菜单虚拟化检测
|
||||
If ContentHandler IsNot Nothing Then
|
||||
ContentHandler(sender, e)
|
||||
ContentHandler(Me, e)
|
||||
ContentHandler = Nothing
|
||||
End If
|
||||
'判断当前颜色
|
||||
@@ -580,7 +564,7 @@
|
||||
Time = 180
|
||||
End If
|
||||
End If
|
||||
If StateLast = StateNew Then Exit Sub
|
||||
If StateLast = StateNew Then Return
|
||||
StateLast = StateNew
|
||||
'触发颜色动画
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
@@ -656,11 +640,14 @@
|
||||
SetResourceReference(ForegroundProperty, "ColorBrush1")
|
||||
End If
|
||||
ColumnPaddingRight.Width = New GridLength(MinPaddingRight)
|
||||
If EventType = "打开帮助" AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266
|
||||
If CustomEventService.GetEventType(Me) = CustomEvent.EventType.打开帮助 AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266
|
||||
Try
|
||||
Dim Unused = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me)
|
||||
Dim Entry As New HelpEntry(CustomEvent.GetAbsoluteUrls(CustomEventService.GetEventData(Me), CustomEventService.GetEventType(Me))(0))
|
||||
Entry.SetToListItem(Me)
|
||||
Catch ex As Exception
|
||||
Log(ex, "设置帮助 MyListItem 失败", LogLevel.Msgbox)
|
||||
Logger.Error(ex, "设置帮助 MyListItem 失败", LogBehavior.Alert)
|
||||
CustomEventService.SetEventType(Me, CustomEvent.EventType.None)
|
||||
CustomEventService.SetEventData(Me, "")
|
||||
End Try
|
||||
End If
|
||||
End Sub
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Grid
|
||||
<Grid
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Name="PanBack" mc:Ignorable="d" x:Class="MyLoading"
|
||||
MinWidth="50" MinHeight="50" Background="{StaticResource ColorBrushSemiTransparent}">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Imports PCL.MyLoading
|
||||
Imports PCL.MyLoading
|
||||
|
||||
Public Class MyLoading
|
||||
|
||||
@@ -35,7 +35,7 @@ Public Class MyLoading
|
||||
Return _ShowProgress
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
If _ShowProgress = value Then Exit Property
|
||||
If _ShowProgress = value Then Return
|
||||
_ShowProgress = value
|
||||
RefreshText()
|
||||
End Set
|
||||
@@ -76,14 +76,7 @@ Public Class MyLoading
|
||||
If Ex Is Nothing Then
|
||||
LabText.Text = "未知错误"
|
||||
Else
|
||||
Do While Ex.InnerException IsNot Nothing
|
||||
Ex = Ex.InnerException
|
||||
Loop
|
||||
LabText.Text = StrTrim(Ex.Message)
|
||||
If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
|
||||
"操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then
|
||||
LabText.Text = "网络环境不佳,请重试或尝试使用 VPN"
|
||||
End If
|
||||
LabText.Text = Ex.GetDisplay(False)
|
||||
End If
|
||||
Else
|
||||
LabText.Text = TextError
|
||||
@@ -142,7 +135,7 @@ Public Class MyLoading
|
||||
Return _OuterState
|
||||
End Get
|
||||
Set(value As MyLoadingState)
|
||||
If _OuterState = value Then Exit Property
|
||||
If _OuterState = value Then Return
|
||||
Dim OldValue = _OuterState
|
||||
_OuterState = value
|
||||
'引发事件
|
||||
@@ -159,7 +152,7 @@ Public Class MyLoading
|
||||
Return _InnerState
|
||||
End Get
|
||||
Set(value As MyLoadingState)
|
||||
If _InnerState = value Then Exit Property
|
||||
If _InnerState = value Then Return
|
||||
Dim OldValue = _InnerState
|
||||
_InnerState = value
|
||||
'引发事件
|
||||
@@ -183,34 +176,33 @@ Public Class MyLoading
|
||||
Private IsLooping As Boolean = False
|
||||
Private Sub AniLoop()
|
||||
'这坨循环代码也是老屎坑了,救救.jpg
|
||||
If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub
|
||||
If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Return
|
||||
IsLooping = True
|
||||
ErrorAnimationWaiting = True
|
||||
AniStart({
|
||||
AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)),
|
||||
AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True),
|
||||
AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)),
|
||||
AaCode(Sub()
|
||||
PathLeft.Opacity = 1
|
||||
PathLeft.Margin = New Thickness(7, 41, 0, 0)
|
||||
PathRight.Opacity = 1
|
||||
PathRight.Margin = New Thickness(14, 41, 0, 0)
|
||||
ErrorAnimationWaiting = False
|
||||
End Sub),
|
||||
AaOpacity(PathLeft, -1, 100, 50),
|
||||
AaX(PathLeft, -5, 180,, New AniEaseOutFluent),
|
||||
AaY(PathLeft, -6, 180,, New AniEaseOutFluent),
|
||||
AaOpacity(PathRight, -1, 100, 50),
|
||||
AaX(PathRight, 5, 180,, New AniEaseOutFluent),
|
||||
AaY(PathRight, -6, 180,, New AniEaseOutFluent),
|
||||
AaCode(Sub()
|
||||
IsLooping = False
|
||||
AniLoop()
|
||||
End Sub,, True)
|
||||
}, "MyLoader Loop " & Uuid & "/" & GetUuid())
|
||||
If ShowProgress Then
|
||||
|
||||
End If
|
||||
AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)),
|
||||
AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True),
|
||||
AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)),
|
||||
AaCode(
|
||||
Sub()
|
||||
PathLeft.Opacity = 1
|
||||
PathLeft.Margin = New Thickness(7, 41, 0, 0)
|
||||
PathRight.Opacity = 1
|
||||
PathRight.Margin = New Thickness(14, 41, 0, 0)
|
||||
ErrorAnimationWaiting = False
|
||||
End Sub),
|
||||
AaOpacity(PathLeft, -1, 100, 50),
|
||||
AaX(PathLeft, -5, 180,, New AniEaseOutFluent),
|
||||
AaY(PathLeft, -6, 180,, New AniEaseOutFluent),
|
||||
AaOpacity(PathRight, -1, 100, 50),
|
||||
AaX(PathRight, 5, 180,, New AniEaseOutFluent),
|
||||
AaY(PathRight, -6, 180,, New AniEaseOutFluent),
|
||||
AaCode(
|
||||
Sub()
|
||||
IsLooping = False
|
||||
AniLoop()
|
||||
End Sub,, True)
|
||||
}, "MyLoader Loop " & Uuid & "/" & GetUuid())
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
@@ -271,7 +263,7 @@ Public Class MyLoadingStateSimulator
|
||||
Return _LoadingState
|
||||
End Get
|
||||
Set(value As MyLoadingState)
|
||||
If _LoadingState = value Then Exit Property
|
||||
If _LoadingState = value Then Return
|
||||
Dim OldState = _LoadingState
|
||||
_LoadingState = value
|
||||
RaiseEvent LoadingStateChanged(value, OldState)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyMenuItem
|
||||
Public Class MyMenuItem
|
||||
Inherits MenuItem
|
||||
|
||||
Private Sub MyMenuItem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
|
||||
@@ -7,7 +7,9 @@
|
||||
If IconControl IsNot Nothing Then IconControl.Data = (New GeometryConverter).ConvertFromString(Icon)
|
||||
End If
|
||||
'对父级设置透明度
|
||||
CType(Parent, ContextMenu).Opacity = Setup.Get("UiLauncherTransparent") / 1000 + 0.4
|
||||
If TypeOf Parent Is ContextMenu Then
|
||||
CType(Parent, ContextMenu).Opacity = Settings.Get(Of Integer)("UiLauncherTransparent") / 1000 + 0.4
|
||||
End If
|
||||
End Sub
|
||||
|
||||
'基础
|
||||
@@ -37,7 +39,7 @@
|
||||
Time = AnimationTimeOut
|
||||
End If
|
||||
'重复性验证
|
||||
If ColorName = BackName Then Exit Sub
|
||||
If ColorName = BackName Then Return
|
||||
ColorName = BackName
|
||||
'触发颜色动画
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
@@ -54,4 +56,7 @@
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Private Sub MyMenuItem_Click(sender As Object, e As RoutedEventArgs) Handles Me.Click
|
||||
RaiseCustomEvent()
|
||||
End Sub
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
<Grid x:Class="MyMsgInput"
|
||||
<Grid x:Class="MyMsgInput"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:my="clr-namespace:PCL"
|
||||
@@ -25,7 +25,7 @@
|
||||
<TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}" HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9" Text="测试标题文本" VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False" />
|
||||
<Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" />
|
||||
<my:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="PanText" Margin="0,0,0,7" Padding="7,0,15,0"
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMuity="0.7">
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
|
||||
<TextBlock VerticalAlignment="Top" TextTrimming="None" TextWrapping="Wrap" Name="LabText" FontSize="15" LineHeight="18"
|
||||
Foreground="#FF5C5C5C" FontWeight="Normal" Padding="1" />
|
||||
</my:MyScrollViewer>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyMsgInput
|
||||
Public Class MyMsgInput
|
||||
|
||||
Private ReadOnly MyConverter As MyMsgBoxConverter
|
||||
Private ReadOnly Uuid As Integer = GetUuid()
|
||||
@@ -26,7 +26,7 @@
|
||||
ShapeLine.StrokeThickness = GetWPFSize(1)
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "输入弹窗初始化失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "输入弹窗初始化失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -46,10 +46,10 @@
|
||||
AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
|
||||
}, "MyMsgBox " & Uuid)
|
||||
'记录日志
|
||||
Log("[Control] 输入弹窗:" & LabTitle.Text)
|
||||
Logger.Info($"输入弹窗:{LabTitle.Text}")
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "输入弹窗加载失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "输入弹窗加载失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub Close()
|
||||
@@ -72,20 +72,21 @@
|
||||
End Sub
|
||||
|
||||
Public Sub Btn1_Click() Handles Btn1.Click
|
||||
If MyConverter.IsExited OrElse Not TextArea.ValidateResult = "" Then Exit Sub
|
||||
TextArea.Validate() '#5773
|
||||
If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Return
|
||||
MyConverter.IsExited = True
|
||||
MyConverter.Result = TextArea.Text
|
||||
Close()
|
||||
End Sub
|
||||
Public Sub Btn2_Click() Handles Btn2.Click
|
||||
If MyConverter.IsExited Then Exit Sub
|
||||
If MyConverter.IsExited Then Return
|
||||
MyConverter.IsExited = True
|
||||
MyConverter.Result = Nothing
|
||||
Close()
|
||||
End Sub
|
||||
|
||||
Private Sub TextCaption_ValidateChanged(sender As Object, e As EventArgs) Handles TextArea.ValidateChanged
|
||||
Btn1.IsEnabled = TextArea.ValidateResult = ""
|
||||
Btn1.IsEnabled = TextArea.IsValidated
|
||||
End Sub
|
||||
|
||||
Private Sub Drag(sender As Object, e As MouseButtonEventArgs) Handles PanBorder.MouseLeftButtonDown, LabTitle.MouseLeftButtonDown
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Grid x:Class="MyMsgSelect"
|
||||
<Grid x:Class="MyMsgSelect"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:my="clr-namespace:PCL"
|
||||
@@ -24,7 +24,7 @@
|
||||
<TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}" HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9" Text="测试标题文本" VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False" />
|
||||
<Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" />
|
||||
<my:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="PanCaption" Margin="0,0,0,17" Padding="7,0,15,0"
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMuity="0.7">
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
|
||||
<StackPanel x:Name="PanSelection" Orientation="Vertical" />
|
||||
</my:MyScrollViewer>
|
||||
<StackPanel Grid.Row="4" Name="PanBtn" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="150,0,8,0" Orientation="Horizontal">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyMsgSelect
|
||||
Public Class MyMsgSelect
|
||||
|
||||
Private ReadOnly MyConverter As MyMsgBoxConverter
|
||||
Private ReadOnly Uuid As Integer = GetUuid()
|
||||
@@ -21,7 +21,9 @@
|
||||
ShapeLine.StrokeThickness = GetWPFSize(1)
|
||||
'添加选择控件
|
||||
Btn1.IsEnabled = False
|
||||
For Each Selection As IMyRadio In Converter.Content
|
||||
For Each Content In Converter.Content
|
||||
Content = MyVirtualizingElement.TryInit(Content)
|
||||
Dim Selection As IMyRadio = Content
|
||||
PanSelection.Children.Add(Selection)
|
||||
AddHandler Selection.Check, AddressOf OnChecked
|
||||
If TypeOf Selection Is MyListItem Then
|
||||
@@ -33,7 +35,7 @@
|
||||
Next
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "选择弹窗初始化失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "选择弹窗初始化失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -51,10 +53,10 @@
|
||||
AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
|
||||
}, "MyMsgBox " & Uuid)
|
||||
'记录日志
|
||||
Log("[Control] 选择弹窗:" & LabTitle.Text)
|
||||
Logger.Info($"选择弹窗:{LabTitle.Text}")
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "选择弹窗加载失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "选择弹窗加载失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub Close()
|
||||
@@ -77,13 +79,13 @@
|
||||
End Sub
|
||||
|
||||
Public Sub Btn1_Click() Handles Btn1.Click
|
||||
If MyConverter.IsExited OrElse SelectedIndex = -1 Then Exit Sub
|
||||
If MyConverter.IsExited OrElse SelectedIndex = -1 Then Return
|
||||
MyConverter.IsExited = True
|
||||
MyConverter.Result = SelectedIndex
|
||||
Close()
|
||||
End Sub
|
||||
Public Sub Btn2_Click() Handles Btn2.Click
|
||||
If MyConverter.IsExited Then Exit Sub
|
||||
If MyConverter.IsExited Then Return
|
||||
MyConverter.IsExited = True
|
||||
MyConverter.Result = Nothing
|
||||
Close()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Grid x:Class="MyMsgText"
|
||||
<Grid x:Class="MyMsgText"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:my="clr-namespace:PCL"
|
||||
@@ -24,7 +24,7 @@
|
||||
<TextBlock Grid.Row="0" FontSize="23" TextTrimming="None" Foreground="{DynamicResource ColorBrush2}" HorizontalAlignment="Left" Name="LabTitle" Margin="7,-1,70,9" Text="测试标题文本" VerticalAlignment="Top" SnapsToDevicePixels="False" UseLayoutRounding="False" />
|
||||
<Rectangle x:Name="ShapeLine" Grid.Row="1" Height="2" Fill="{Binding Foreground, ElementName=LabTitle}" />
|
||||
<my:MyScrollViewer Grid.Row="3" VerticalAlignment="Top" x:Name="PanCaption" Margin="0,0,0,17" Padding="7,0,15,0"
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMuity="0.7">
|
||||
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
|
||||
<TextBlock VerticalAlignment="Top" TextTrimming="None" TextWrapping="Wrap" Name="LabCaption" FontSize="15" LineHeight="18"
|
||||
Foreground="#FF5C5C5C" FontWeight="Normal" Padding="1" />
|
||||
</my:MyScrollViewer>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyMsgText
|
||||
Public Class MyMsgText
|
||||
|
||||
Private ReadOnly MyConverter As MyMsgBoxConverter
|
||||
Private ReadOnly Uuid As Integer = GetUuid()
|
||||
@@ -25,7 +25,7 @@
|
||||
ShapeLine.StrokeThickness = GetWPFSize(1)
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "普通弹窗初始化失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "普通弹窗初始化失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
|
||||
}, "MyMsgBox " & Uuid)
|
||||
'记录日志
|
||||
Log("[Control] 普通弹窗:" & LabTitle.Text & vbCrLf & LabCaption.Text)
|
||||
Logger.Info($"普通弹窗:{LabTitle.Text}{vbCrLf}{LabCaption.Text}")
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "普通弹窗加载失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "普通弹窗加载失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub Close()
|
||||
@@ -70,7 +70,7 @@
|
||||
End Sub
|
||||
|
||||
Public Sub Btn1_Click() Handles Btn1.Click
|
||||
If MyConverter.IsExited Then Exit Sub
|
||||
If MyConverter.IsExited Then Return
|
||||
If MyConverter.Button1Action IsNot Nothing Then
|
||||
MyConverter.Button1Action()
|
||||
Else
|
||||
@@ -80,7 +80,7 @@
|
||||
End If
|
||||
End Sub
|
||||
Public Sub Btn2_Click() Handles Btn2.Click
|
||||
If MyConverter.IsExited Then Exit Sub
|
||||
If MyConverter.IsExited Then Return
|
||||
If MyConverter.Button2Action IsNot Nothing Then
|
||||
MyConverter.Button2Action()
|
||||
Else
|
||||
@@ -90,7 +90,7 @@
|
||||
End If
|
||||
End Sub
|
||||
Public Sub Btn3_Click() Handles Btn3.Click
|
||||
If MyConverter.IsExited Then Exit Sub
|
||||
If MyConverter.IsExited Then Return
|
||||
If MyConverter.Button3Action IsNot Nothing Then
|
||||
MyConverter.Button3Action()
|
||||
Else
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
Public Class MyPageLeft
|
||||
Public Class MyPageLeft
|
||||
Inherits Grid
|
||||
Private Uuid As Integer = GetUuid()
|
||||
|
||||
'执行逐个进入动画的控件
|
||||
Public Property AnimatedControl As FrameworkElement
|
||||
Public Property AnimatedControl As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
|
||||
Get
|
||||
Return GetValue(AnimatedControlProperty)
|
||||
End Get
|
||||
Set(value As FrameworkElement)
|
||||
Set(value As String)
|
||||
SetValue(AnimatedControlProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(FrameworkElement), GetType(MyPageLeft), New PropertyMetadata(Nothing))
|
||||
Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing))
|
||||
|
||||
Public Sub TriggerShowAnimation()
|
||||
If AnimatedControl Is Nothing Then
|
||||
@@ -30,6 +30,7 @@
|
||||
Dim AniList As New List(Of AniData)
|
||||
Dim Id As Integer = 0, Delay As Integer = 0
|
||||
For Each Element As FrameworkElement In GetAllAnimControls(True)
|
||||
Element = MyVirtualizingElement.TryInit(Element)
|
||||
If Element.Visibility = Visibility.Collapsed Then
|
||||
'还原之前的隐藏动画可能导致的改变(#2436)
|
||||
Element.Opacity = 1
|
||||
@@ -39,16 +40,17 @@
|
||||
Element.Opacity = 0
|
||||
Element.RenderTransform = New TranslateTransform(-25, 0)
|
||||
If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = False
|
||||
AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 200, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
|
||||
AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
|
||||
AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent))
|
||||
AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak)))
|
||||
If TypeOf Element Is MyListItem Then
|
||||
AniList.Add(AaCode(Sub()
|
||||
CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
|
||||
CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
|
||||
End Sub, Delay + 280))
|
||||
AniList.Add(AaCode(
|
||||
Sub()
|
||||
CType(Element, MyListItem).IsMouseOverAnimationEnabled = True
|
||||
CType(Element, MyListItem).RefreshColor(Me, New EventArgs)
|
||||
End Sub, Delay + 280))
|
||||
End If
|
||||
Delay += Math.Max(8, 20 - Id) * 2.5
|
||||
Delay += Math.Max(15 - Id, 7) * 2
|
||||
Id += 1
|
||||
End If
|
||||
Next
|
||||
@@ -63,8 +65,8 @@
|
||||
RenderTransformOrigin = New Point(0.5, 0.5)
|
||||
End If
|
||||
AniStart({
|
||||
AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 130,, New AniEaseInFluent(AniEasePower.Weak)),
|
||||
AaOpacity(Me, -Opacity, 100, 30)
|
||||
AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 110,, New AniEaseInFluent(AniEasePower.Weak)),
|
||||
AaOpacity(Me, -Opacity, 80, 30)
|
||||
}, "PageLeft PageChange " & Uuid)
|
||||
Else
|
||||
'逐个退出动画
|
||||
@@ -72,8 +74,8 @@
|
||||
Dim Id As Integer = 0
|
||||
Dim Controls = GetAllAnimControls()
|
||||
For Each Element As FrameworkElement In Controls
|
||||
AniList.Add(AaOpacity(Element, -Element.Opacity, 60, 80 / Controls.Count * Id))
|
||||
AniList.Add(AaTranslateX(Element, -6, 60, 80 / Controls.Count * Id))
|
||||
AniList.Add(AaOpacity(Element, -Element.Opacity, 50, 70 / Controls.Count * Id))
|
||||
AniList.Add(AaTranslateX(Element, -6, 50, 70 / Controls.Count * Id))
|
||||
Id += 1
|
||||
Next
|
||||
AniStart(AniList, "PageLeft PageChange " & Uuid)
|
||||
@@ -83,11 +85,11 @@
|
||||
'遍历获取所有需要生成动画的控件
|
||||
Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
|
||||
Dim AllControls As New List(Of FrameworkElement)
|
||||
GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility)
|
||||
GetAllAnimControls(FindName(AnimatedControl), AllControls, IgnoreInvisibility)
|
||||
Return AllControls
|
||||
End Function
|
||||
Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
|
||||
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
|
||||
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
|
||||
If TypeOf Element Is MyTextButton Then
|
||||
AllControls.Add(Element)
|
||||
ElseIf TypeOf Element Is MyListItem Then
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Public Class MyPageRight
|
||||
Public Class MyPageRight
|
||||
Inherits AdornerDecorator
|
||||
Public PageUuid As Integer = GetUuid()
|
||||
|
||||
'“返回顶部” 按钮检测的滚动区域
|
||||
Public Property PanScroll As MyScrollViewer
|
||||
Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664)
|
||||
Get
|
||||
Return GetValue(PanScrollProperty)
|
||||
End Get
|
||||
Set(value As MyScrollViewer)
|
||||
Set(value As String)
|
||||
SetValue(PanScrollProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Private Shared ReadOnly PanScrollProperty =
|
||||
DependencyProperty.Register("PanScroll", GetType(MyScrollViewer), GetType(MyPageRight), New PropertyMetadata(Nothing))
|
||||
DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing))
|
||||
|
||||
'当前状态
|
||||
Public Enum PageStates
|
||||
@@ -33,9 +33,9 @@
|
||||
Return _PageState
|
||||
End Get
|
||||
Set(value As PageStates)
|
||||
If _PageState = value Then Exit Property
|
||||
If _PageState = value Then Return
|
||||
_PageState = value
|
||||
If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value))
|
||||
Logger.Trace($"页面状态切换为 {value}")
|
||||
End Set
|
||||
End Property
|
||||
|
||||
@@ -59,9 +59,9 @@
|
||||
''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
|
||||
''' <param name="RealLoader">在工作线程执行的加载器。</param>
|
||||
''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
|
||||
Public Sub PageLoaderInit(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
|
||||
RealLoader As LoaderBase,
|
||||
Optional FinishedInvoke As Action(Of LoaderBase) = Nothing, Optional InputInvoke As Func(Of Object) = Nothing,
|
||||
Public Sub PageLoaderInit(Of TIn, TOut)(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
|
||||
RealLoader As LoaderTask(Of TIn, TOut),
|
||||
Optional FinishedInvoke As Action(Of LoaderTask(Of TIn, TOut)) = Nothing, Optional InputInvoke As Func(Of TIn) = Nothing,
|
||||
Optional AutoRun As Boolean = True)
|
||||
'初始化参数
|
||||
Me.PanLoader = PanLoader
|
||||
@@ -89,13 +89,59 @@
|
||||
If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
|
||||
'初次运行加载器
|
||||
If PageLoaderAutoRun Then
|
||||
If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
|
||||
PageLoader.Start(CType(PageLoader, Object).StartGetInput(Nothing, PageLoaderInputInvoke))
|
||||
Else
|
||||
Dim Input = Nothing
|
||||
If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
|
||||
PageLoader.Start(Input)
|
||||
End If
|
||||
'上下两个重载只有这里不一样
|
||||
PageLoader.Start(RealLoader.StartGetInput(Nothing, PageLoaderInputInvoke))
|
||||
End If
|
||||
If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
|
||||
RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
|
||||
End If
|
||||
'设置加载环
|
||||
PageLoaderUi.State = RealLoader
|
||||
AddHandler PageLoaderUi.Click, Sub() If RealLoader.State = LoadState.Failed Then PageLoaderRestart() '点击重试事件
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 表明页面存在需要在后台执行的加载器。
|
||||
''' </summary>
|
||||
''' <param name="LoaderUi">MyLoading 控件。</param>
|
||||
''' <param name="PanLoader">MyLoading 控件对应的卡片。</param>
|
||||
''' <param name="PanContent">加载结束后出现的内容容器。</param>
|
||||
''' <param name="PanAlways">无论是否在加载总是要显示的容器。可以为 Nothing。</param>
|
||||
''' <param name="RealLoader">在工作线程执行的加载器。</param>
|
||||
''' <param name="FinishedInvoke">当加载器执行完成,在 UI 线程触发的 UI 初始化事件。</param>
|
||||
Public Sub PageLoaderInit(Of TIn)(LoaderUi As MyLoading, PanLoader As FrameworkElement, PanContent As FrameworkElement, PanAlways As FrameworkElement,
|
||||
RealLoader As LoaderCombo(Of TIn),
|
||||
Optional FinishedInvoke As Action(Of LoaderCombo(Of TIn)) = Nothing, Optional InputInvoke As Func(Of TIn) = Nothing,
|
||||
Optional AutoRun As Boolean = True)
|
||||
'初始化参数
|
||||
Me.PanLoader = PanLoader
|
||||
Me.PanContent = PanContent
|
||||
Me.PanAlways = PanAlways
|
||||
Me.PageLoader = RealLoader
|
||||
Me.PageLoaderUi = LoaderUi
|
||||
Me.PageLoaderInputInvoke = InputInvoke
|
||||
Me.PageLoaderAutoRun = AutoRun
|
||||
'添加结束 Invoke
|
||||
If FinishedInvoke IsNot Nothing Then
|
||||
AddHandler RealLoader.PreviewFinish,
|
||||
Sub()
|
||||
Do While PageState = MyPageRight.PageStates.PageExit OrElse PageState = MyPageRight.PageStates.ContentExit
|
||||
Thread.Sleep(10) '不在退出动画时执行 UI 线程操作,避免退出动画被重置
|
||||
Loop
|
||||
RunInUiWait(Sub() FinishedInvoke(RealLoader))
|
||||
Thread.Sleep(20) '由于大量初始化控件会导致掉帧,延迟触发 State 改变事件
|
||||
End Sub
|
||||
End If
|
||||
AddHandler RealLoader.OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) RunInUi(Sub() PageLoaderState(Loader, NewState, OldState))
|
||||
'隐藏 UI
|
||||
PanLoader.Visibility = Visibility.Collapsed
|
||||
PanContent.Visibility = Visibility.Collapsed
|
||||
If PanAlways IsNot Nothing Then PanAlways.Visibility = Visibility.Collapsed
|
||||
'初次运行加载器
|
||||
If PageLoaderAutoRun Then
|
||||
'上下两个重载只有这里不一样
|
||||
Dim Input = Nothing
|
||||
If PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
|
||||
PageLoader.Start(Input)
|
||||
End If
|
||||
If PageLoader.State = LoadState.Finished AndAlso FinishedInvoke IsNot Nothing Then
|
||||
RunInUiWait(Sub() FinishedInvoke(RealLoader)) '加载器已提前完成,直接触发事件
|
||||
@@ -106,8 +152,8 @@
|
||||
End Sub
|
||||
'重试
|
||||
Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试
|
||||
If Not PageLoaderAutoRun Then Exit Sub
|
||||
If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then
|
||||
If Not PageLoaderAutoRun Then Return
|
||||
If GeneralUtils.IsGenericInstanceOf(PageLoader, GetType(LoaderTask(Of ,))) OrElse TypeOf PageLoader Is LoaderWorker Then
|
||||
PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart)
|
||||
Else
|
||||
If Input Is Nothing AndAlso PageLoaderInputInvoke IsNot Nothing Then Input = PageLoaderInputInvoke()
|
||||
@@ -125,36 +171,37 @@
|
||||
''' 需要根据加载器状态,从 Empty 切换到 ContentEnter、LoaderWait、LoaderEnter。
|
||||
''' </summary>
|
||||
Public Sub PageOnEnter()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnEnter")
|
||||
Logger.Trace("已触发 PageOnEnter")
|
||||
RaiseEvent PageEnter()
|
||||
Select Case PageState
|
||||
Case PageStates.Empty
|
||||
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
|
||||
'如果加载器在进入页面时不启动(例如 HiPer 联机),那么在此时就会有 State = Waiting
|
||||
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Canceled Then
|
||||
'如果加载器在进入页面时不启动(例如联机),那么在此时就会有 State = Waiting
|
||||
PageState = PageStates.ContentEnter
|
||||
TriggerEnterAnimation(PanAlways, If(PanContent, Child))
|
||||
ElseIf PageLoader.State = LoadState.Loading Then
|
||||
PageState = PageStates.LoaderWait
|
||||
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
|
||||
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid)
|
||||
Else 'PageLoader.State = LoadState.Failed
|
||||
PageState = PageStates.LoaderEnter
|
||||
TriggerEnterAnimation(PanAlways, PanLoader)
|
||||
End If
|
||||
Case PageStates.ContentExit
|
||||
'和上面的一样,但是不管 PanAlways
|
||||
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Aborted Then
|
||||
If PageLoader Is Nothing OrElse PageLoader.State = LoadState.Finished OrElse PageLoader.State = LoadState.Waiting OrElse PageLoader.State = LoadState.Canceled Then
|
||||
PageState = PageStates.ContentEnter
|
||||
TriggerEnterAnimation(If(PanContent, Child))
|
||||
ElseIf PageLoader.State = LoadState.Loading Then
|
||||
PageState = PageStates.LoaderWait
|
||||
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 200), "PageRight PageChange " & PageUuid)
|
||||
AniStart(AaCode(AddressOf PageOnLoaderWaitFinished, 400), "PageRight PageChange " & PageUuid)
|
||||
Else 'PageLoader.State = LoadState.Failed
|
||||
PageState = PageStates.LoaderEnter
|
||||
TriggerEnterAnimation(PanLoader)
|
||||
End If
|
||||
Case PageStates.ContentEnter '重复调用 PageOnEnter,直接忽略
|
||||
Case PageStates.ContentEnter
|
||||
'重复调用 PageOnEnter,直接忽略
|
||||
Case Else
|
||||
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnter 事件。")
|
||||
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnEnter 事件。")
|
||||
End Select
|
||||
End Sub
|
||||
Public Event PageEnter()
|
||||
@@ -163,7 +210,7 @@
|
||||
''' 需要立即切换至 PageExit 或 Empty。
|
||||
''' </summary>
|
||||
Public Sub PageOnExit()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnExit")
|
||||
Logger.Trace("已触发 PageOnExit")
|
||||
RaiseEvent PageExit()
|
||||
Select Case PageState
|
||||
Case PageStates.ContentEnter, PageStates.ContentStay
|
||||
@@ -187,8 +234,8 @@
|
||||
''' 需要立即切换至 Empty。
|
||||
''' </summary>
|
||||
Public Sub PageOnForceExit()
|
||||
If PageState = PageStates.Empty Then Exit Sub
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnForceExit")
|
||||
If PageState = PageStates.Empty Then Return
|
||||
Logger.Trace("已触发 PageOnForceExit")
|
||||
PageState = PageStates.Empty
|
||||
AniStop("PageRight PageChange " & PageUuid)
|
||||
'由于动画会被强制中止,所以需要手动进行隐藏
|
||||
@@ -205,10 +252,10 @@
|
||||
''' 需要在 PageEnter 事件确认要显示的子页面有哪些。
|
||||
''' </summary>
|
||||
Public Sub PageOnContentExit()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnContentExit")
|
||||
Logger.Trace("已触发 PageOnContentExit")
|
||||
If PageLoader IsNot Nothing AndAlso PageLoader.State = LoadState.Loading Then
|
||||
Throw New Exception("在调用 PageOnContentExit 时,加载器不能为 Loading 状态")
|
||||
'Loading 的加载器可能触发进一步变化,难以预测会触发子页面的动画还是加载器完成的动画
|
||||
Throw New Exception("在调用 PageOnContentExit 时,加载器不能为 Loading 状态")
|
||||
End If
|
||||
Select Case PageState
|
||||
Case PageStates.ContentEnter, PageStates.ContentStay
|
||||
@@ -219,8 +266,12 @@
|
||||
Case PageStates.LoaderEnter, PageStates.LoaderStayForce, PageStates.LoaderStay
|
||||
PageState = PageStates.ContentExit
|
||||
TriggerExitAnimation(PanLoader)
|
||||
Case PageStates.LoaderWait, PageStates.Empty
|
||||
Case PageStates.LoaderWait
|
||||
PageOnEnter()
|
||||
Case PageStates.Empty
|
||||
'可能是在离开页面后,其他后台线程调用了本函数,此时不应触发 PageOnEnter()
|
||||
'例如:在加入联机房间后,切换到其他页面,然后联机房间被解散
|
||||
If IsLoaded Then PageOnEnter()
|
||||
End Select
|
||||
End Sub
|
||||
|
||||
@@ -230,7 +281,7 @@
|
||||
''' 需要根据目前状态,从 ContentEnter 切换到 ContentStay,或从 LoaderEnter 切换到 LoaderStayForce。
|
||||
''' </summary>
|
||||
Private Sub PageOnEnterAnimationFinished()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnEnterAnimationFinished")
|
||||
Logger.Trace("已触发 PageOnEnterAnimationFinished")
|
||||
Select Case PageState
|
||||
Case PageStates.ContentEnter
|
||||
PageState = PageStates.ContentStay
|
||||
@@ -238,7 +289,7 @@
|
||||
PageState = PageStates.LoaderStayForce
|
||||
AniStart(AaCode(AddressOf PageOnLoaderStayFinished, 400), "PageRight PageChange " & PageUuid)
|
||||
Case Else
|
||||
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnEnterAnimationFinished 事件。")
|
||||
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnEnterAnimationFinished 事件。")
|
||||
End Select
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -246,7 +297,7 @@
|
||||
''' 需要根据目前状态,从 AllExit 切换到 Empty,或从 LoaderExit 切换到 ContentEnter,或从 ContentExit 重新触发 PageOnEnter。
|
||||
''' </summary>
|
||||
Private Sub PageOnExitAnimationFinished()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnExitAnimationFinished")
|
||||
Logger.Trace("已触发 PageOnExitAnimationFinished")
|
||||
Select Case PageState
|
||||
Case PageStates.PageExit
|
||||
PageState = PageStates.Empty
|
||||
@@ -256,7 +307,7 @@
|
||||
PageState = PageStates.ContentEnter
|
||||
TriggerEnterAnimation(PanContent)
|
||||
Case Else
|
||||
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnExitAnimationFinished 事件。")
|
||||
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnExitAnimationFinished 事件。")
|
||||
End Select
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -264,7 +315,7 @@
|
||||
''' 需要从 LoaderWait 切换到 LoaderEnter。
|
||||
''' </summary>
|
||||
Private Sub PageOnLoaderWaitFinished()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnLoaderWaitFinished")
|
||||
Logger.Trace("已触发 PageOnLoaderWaitFinished")
|
||||
Select Case PageState
|
||||
Case PageStates.LoaderWait
|
||||
PageState = PageStates.LoaderEnter
|
||||
@@ -274,7 +325,7 @@
|
||||
TriggerEnterAnimation(PanLoader)
|
||||
End If
|
||||
Case Else
|
||||
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
|
||||
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnLoaderWaitFinished 事件。")
|
||||
End Select
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -282,7 +333,7 @@
|
||||
''' 需要从 LoaderStayForce 切换到 LoaderStay 或 LoaderExit。
|
||||
''' </summary>
|
||||
Private Sub PageOnLoaderStayFinished()
|
||||
If ModeDebug Then Log("[UI] 已触发 PageOnLoaderStayFinished")
|
||||
Logger.Trace("已触发 PageOnLoaderStayFinished")
|
||||
Select Case PageState
|
||||
Case PageStates.LoaderStayForce
|
||||
If PageLoader.State = LoadState.Finished Then
|
||||
@@ -292,7 +343,7 @@
|
||||
PageState = PageStates.LoaderStay
|
||||
End If
|
||||
Case Else
|
||||
Throw New Exception("在状态为 " & GetStringFromEnum(PageState) & " 时触发了 PageOnLoaderWaitFinished 事件。")
|
||||
Throw New Exception("在状态为 " & PageState.ToString() & " 时触发了 PageOnLoaderWaitFinished 事件。")
|
||||
End Select
|
||||
End Sub
|
||||
|
||||
@@ -302,8 +353,8 @@
|
||||
Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState)
|
||||
Select Case NewState
|
||||
Case LoadState.Failed, LoadState.Loading
|
||||
If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Exit Sub
|
||||
If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)")
|
||||
If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Return
|
||||
Logger.Trace("已触发 PageLoaderState (Start/Refresh)")
|
||||
'(重新)开始运行
|
||||
'需要从部分状态切换到 ReloadExit
|
||||
Select Case PageState
|
||||
@@ -313,9 +364,9 @@
|
||||
Case PageStates.LoaderExit
|
||||
PageState = PageStates.ContentExit
|
||||
End Select
|
||||
Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting
|
||||
If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Exit Sub
|
||||
If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)")
|
||||
Case LoadState.Finished, LoadState.Canceled, LoadState.Waiting
|
||||
If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Return
|
||||
Logger.Trace("已触发 PageLoaderState (Stop/Canceled)")
|
||||
'运行结束
|
||||
'需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit
|
||||
Select Case PageState
|
||||
@@ -353,12 +404,16 @@
|
||||
If Control.RenderTransform IsNot Nothing AndAlso TypeOf Control.RenderTransform Is TranslateTransform Then Control.RenderTransform = Nothing
|
||||
Next
|
||||
For Each Control As FrameworkElement In GetAllAnimControls(Element)
|
||||
Control.Opacity = 0
|
||||
Control.RenderTransform = New TranslateTransform(0, -16)
|
||||
AniList.Add(AaOpacity(Control, 1, 150, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
|
||||
AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
|
||||
AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
|
||||
Delay += 40
|
||||
If TypeOf Control Is MyExtraTextButton Then
|
||||
CType(Control, MyExtraTextButton).Show = True
|
||||
Else
|
||||
Control.Opacity = 0
|
||||
Control.RenderTransform = New TranslateTransform(0, -16)
|
||||
AniList.Add(AaOpacity(Control, 1, 100, Delay, New AniEaseOutFluent(AniEasePower.Weak)))
|
||||
AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent))
|
||||
AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack))
|
||||
Delay += 25
|
||||
End If
|
||||
Next
|
||||
Next
|
||||
'滚动条动画
|
||||
@@ -369,7 +424,7 @@
|
||||
End If
|
||||
'结束
|
||||
AniList.Add(AaCode(Sub() PageOnEnterAnimationFinished(),, True))
|
||||
AniStart(AniList, "PageRight PageChange " & PageUuid)
|
||||
AniStart(AniList, "PageRight PageChange " & PageUuid, True)
|
||||
End Sub
|
||||
|
||||
'逐个退出动画
|
||||
@@ -379,10 +434,14 @@
|
||||
Dim Delay As Integer = 0
|
||||
For Each Element In RealElements
|
||||
For Each Control As FrameworkElement In GetAllAnimControls(Element)
|
||||
Control.IsHitTestVisible = False
|
||||
AniList.Add(AaOpacity(Control, -1, 90, Delay))
|
||||
AniList.Add(AaTranslateY(Control, -6, 90, Delay))
|
||||
Delay += 20
|
||||
If TypeOf Control Is MyExtraTextButton Then
|
||||
CType(Control, MyExtraTextButton).Show = False
|
||||
Else
|
||||
Control.IsHitTestVisible = False
|
||||
AniList.Add(AaOpacity(Control, -1, 70, Delay))
|
||||
AniList.Add(AaTranslateY(Control, -6, 70, Delay))
|
||||
Delay += 15
|
||||
End If
|
||||
Next
|
||||
Next
|
||||
'滚动条动画
|
||||
@@ -392,31 +451,38 @@
|
||||
AniList.Add(AaTranslateX(Scroll, 10 - CType(Scroll.RenderTransform, TranslateTransform).X, 90, 0, New AniEaseInFluent))
|
||||
End If
|
||||
'结束
|
||||
AniList.Add(AaCode(Sub()
|
||||
For Each Element In RealElements
|
||||
Element.Visibility = Visibility.Collapsed
|
||||
Next
|
||||
PageOnExitAnimationFinished()
|
||||
End Sub,, True))
|
||||
AniList.Add(AaCode(
|
||||
Sub()
|
||||
For Each Element In RealElements
|
||||
Element.Visibility = Visibility.Collapsed
|
||||
Next
|
||||
PageOnExitAnimationFinished()
|
||||
End Sub,, True))
|
||||
AniStart(AniList, "PageRight PageChange " & PageUuid)
|
||||
End Sub
|
||||
|
||||
'遍历获取所有需要生成动画的控件
|
||||
Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
|
||||
''' <summary>
|
||||
''' 禁用页面切换动画的控件列表。
|
||||
''' </summary>
|
||||
Public DisabledPageAnimControls As New List(Of FrameworkElement)
|
||||
''' <summary>
|
||||
''' 遍历获取所有需要生成动画的控件。
|
||||
''' </summary>
|
||||
Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As IEnumerable(Of FrameworkElement)
|
||||
Dim AllControls As New List(Of FrameworkElement)
|
||||
GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
|
||||
Return AllControls
|
||||
_GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
|
||||
Return AllControls.Except(DisabledPageAnimControls)
|
||||
End Function
|
||||
Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
|
||||
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub
|
||||
If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
|
||||
Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
|
||||
If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return
|
||||
If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
|
||||
AllControls.Add(Element)
|
||||
ElseIf TypeOf Element Is ContentControl Then
|
||||
Dim Content = CType(Element, ContentControl).Content
|
||||
If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
|
||||
If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then _GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
|
||||
ElseIf TypeOf Element Is Panel Then
|
||||
For Each Element2 In CType(Element, Panel).Children
|
||||
If TypeOf Element2 Is FrameworkElement Then GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
|
||||
If TypeOf Element2 Is FrameworkElement Then _GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
|
||||
Next
|
||||
End If
|
||||
End Sub
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Grid x:Class="MyRadioBox"
|
||||
<Grid x:Class="MyRadioBox"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
MinWidth="20" x:Name="PanBack" UseLayoutRounding="False" SnapsToDevicePixels="False" MinHeight="20" Background="{StaticResource ColorBrushSemiTransparent}" Focusable="True">
|
||||
<TextBlock IsHitTestVisible="False" x:Name="LabText" FontSize="13" Padding="0" Margin="26,0,0,0" Text="Radiobox" Foreground="{DynamicResource ColorBrush1}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<TextBlock IsHitTestVisible="False" x:Name="LabText" FontSize="13" Padding="0" Margin="26,0,0,0" Foreground="{DynamicResource ColorBrush1}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<Ellipse IsHitTestVisible="False" x:Name="ShapeBorder" StrokeThickness="1.1" HorizontalAlignment="Left" VerticalAlignment="Center" Width="18" Height="18" Stroke="{DynamicResource ColorBrush1}" Margin="1,0,0,0" Fill="{StaticResource ColorBrushHalfWhite}" />
|
||||
<Ellipse IsHitTestVisible="False" x:Name="ShapeDot" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="{Binding Stroke, ElementName=ShapeBorder}" Width="0" Height="0" Margin="10,0,0,0" StrokeThickness="0" />
|
||||
</Grid>
|
||||
@@ -1,5 +1,9 @@
|
||||
Public Class MyRadioBox
|
||||
Implements IMyRadio
|
||||
Imports System.Windows.Markup
|
||||
Imports System.Windows.Shapes
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyRadioBox
|
||||
Implements IMyRadio, ISettingControl
|
||||
|
||||
'基础
|
||||
|
||||
@@ -10,144 +14,145 @@
|
||||
Public Event Changed(sender As Object, e As RouteEventArgs) Implements IMyRadio.Changed
|
||||
|
||||
'自定义属性
|
||||
|
||||
Private _Checked As Boolean = False '是否选中
|
||||
Public Property Checked As Boolean
|
||||
Get
|
||||
Return _Checked
|
||||
Return GetValue(CheckedProperty)
|
||||
End Get
|
||||
Set(value As Boolean)
|
||||
SetChecked(value, False, True)
|
||||
SetChecked(value, False)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly CheckedProperty As DependencyProperty =
|
||||
DependencyProperty.Register("Checked", GetType(Boolean), GetType(MyRadioBox), New PropertyMetadata(False,
|
||||
Sub(d As MyRadioBox, e As DependencyPropertyChangedEventArgs)
|
||||
'在使用 XAML 设置 Checked 属性时,不会触发 Checked_Set 方法,所以需要在这里手动触发 UI 改变
|
||||
If Not d.IsLoaded Then d.SyncUI()
|
||||
End Sub))
|
||||
|
||||
''' <summary>
|
||||
''' 手动设置 Checked 属性。
|
||||
''' </summary>
|
||||
''' <param name="value">新的 Checked 属性。</param>
|
||||
''' <param name="user">是否由用户引发。</param>
|
||||
''' <param name="anime">是否执行动画。</param>
|
||||
Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
|
||||
Public Sub SetChecked(value As Boolean, user As Boolean)
|
||||
Try
|
||||
|
||||
'Preview 事件
|
||||
|
||||
If value AndAlso user Then
|
||||
Dim e = New RouteEventArgs(user)
|
||||
RaiseEvent PreviewCheck(Me, e)
|
||||
If e.Handled Then
|
||||
Radiobox_MouseLeave()
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
End If
|
||||
|
||||
'自定义属性基础
|
||||
|
||||
Dim IsChanged As Boolean = False
|
||||
If IsLoaded AndAlso Not value = _Checked Then RaiseEvent PreviewChange(Me, New RouteEventArgs(user))
|
||||
If Not value = _Checked Then
|
||||
_Checked = value
|
||||
If IsLoaded AndAlso Not value = Checked Then RaiseEvent PreviewChange(Me, New RouteEventArgs(user))
|
||||
If value <> Checked Then
|
||||
SetValue(CheckedProperty, value)
|
||||
IsChanged = True
|
||||
End If
|
||||
|
||||
'保证只有一个单选框选中
|
||||
|
||||
If Parent Is Nothing Then Exit Sub
|
||||
'保证最多只有一个单选框选中
|
||||
If Parent Is Nothing Then Return
|
||||
Dim RadioboxList As New List(Of MyRadioBox)
|
||||
Dim CheckedCount As Integer = 0
|
||||
'收集控件列表与选中个数
|
||||
For Each Control In CType(Parent, Object).Children
|
||||
For Each Control In CType(Parent, Object).Children '收集控件列表与选中个数
|
||||
If TypeOf Control Is MyRadioBox Then
|
||||
RadioboxList.Add(Control)
|
||||
If Control.Checked Then CheckedCount += 1
|
||||
End If
|
||||
Next
|
||||
'判断选中情况
|
||||
Select Case CheckedCount
|
||||
Case 0
|
||||
'没有任何单选框被选中,选择第一个
|
||||
RadioboxList(0).Checked = True
|
||||
Case Is > 1
|
||||
'选中项目多于 1 个
|
||||
If Checked Then
|
||||
'如果本控件选中,则取消其他所有控件的选中
|
||||
For Each Control As MyRadioBox In RadioboxList
|
||||
If Control.Checked AndAlso Not Control.Equals(Me) Then Control.Checked = False
|
||||
Next
|
||||
Else
|
||||
'如果本控件未选中,则只保留第一个选中的控件
|
||||
Dim FirstChecked = False
|
||||
For Each Control As MyRadioBox In RadioboxList
|
||||
If Control.Checked Then
|
||||
If FirstChecked Then
|
||||
Control.Checked = False '修改 Checked 会自动触发 Change 事件,所以不用额外触发
|
||||
Else
|
||||
FirstChecked = True
|
||||
End If
|
||||
If CheckedCount > 1 Then
|
||||
If Checked Then
|
||||
'如果本控件选中,则取消其他所有控件的选中
|
||||
For Each Control As MyRadioBox In RadioboxList
|
||||
If Control.Checked AndAlso Not Control.Equals(Me) Then Control.Checked = False
|
||||
Next
|
||||
Else
|
||||
'如果本控件未选中,则只保留第一个选中的控件
|
||||
Dim FirstChecked = False
|
||||
For Each Control As MyRadioBox In RadioboxList
|
||||
If Control.Checked Then
|
||||
If FirstChecked Then
|
||||
Control.Checked = False '修改 Checked 会自动触发 Change 事件,所以不用额外触发
|
||||
Else
|
||||
FirstChecked = True
|
||||
End If
|
||||
Next
|
||||
End If
|
||||
End Select
|
||||
'End If
|
||||
|
||||
'更改动画
|
||||
|
||||
If Not IsChanged Then Exit Sub
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 AndAlso anime Then '防止默认属性变更触发动画
|
||||
If Checked Then
|
||||
'由无变有
|
||||
If ShapeDot.Opacity < 0.01 Then ShapeDot.Opacity = 1
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 10 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent(AniEasePower.Weak), , True),
|
||||
AaScale(ShapeBorder, 8, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.6, New AniEaseOutBack, , True)
|
||||
}, "MyRadioBox Border " & Uuid)
|
||||
AniStart({
|
||||
AaScale(ShapeDot, 9 - ShapeDot.Width, AnimationTimeOfCheck * 2.6,, New AniEaseOutBack(AniEasePower.Weak), , True),
|
||||
AaOpacity(ShapeDot, 1 - ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.6)
|
||||
}, "MyRadioBox Dot " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush2", "ColorBrushGray4")), AnimationTimeOfCheck)
|
||||
}, "MyRadioBox BorderColor " & Uuid)
|
||||
Else
|
||||
'由有变无
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 18 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True)
|
||||
}, "MyRadioBox Border " & Uuid)
|
||||
AniStart({
|
||||
AaScale(ShapeDot, -ShapeDot.Width, AnimationTimeOfCheck, , New AniEaseInFluent, , True),
|
||||
AaOpacity(ShapeDot, -ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.2)
|
||||
}, "MyRadioBox Dot " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush1", "ColorBrushGray4")), AnimationTimeOfCheck)
|
||||
}, "MyRadioBox BorderColor " & Uuid)
|
||||
End If
|
||||
Else
|
||||
'不使用动画
|
||||
AniStop("MyRadioBox Border " & Uuid)
|
||||
AniStop("MyRadioBox Dot " & Uuid)
|
||||
AniStop("MyRadioBox BorderColor " & Uuid)
|
||||
If Checked Then
|
||||
ShapeDot.Width = 9
|
||||
ShapeDot.Height = 9
|
||||
ShapeDot.Opacity = 1
|
||||
ShapeDot.Margin = New Thickness(5.5, 0, 0, 0)
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
|
||||
Else
|
||||
ShapeDot.Width = 0
|
||||
ShapeDot.Height = 0
|
||||
ShapeDot.Opacity = 0
|
||||
ShapeDot.Margin = New Thickness(10, 0, 0, 0)
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
|
||||
End If
|
||||
Next
|
||||
End If
|
||||
End If
|
||||
|
||||
'触发事件
|
||||
If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
|
||||
RaiseEvent Changed(Me, New RouteEventArgs(user))
|
||||
If IsChanged Then
|
||||
'触发事件
|
||||
If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
|
||||
RaiseEvent Changed(Me, New RouteEventArgs(user))
|
||||
RaiseCustomEvent()
|
||||
'更改动画
|
||||
SyncUI()
|
||||
End If
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "单选框勾选改变错误", LogLevel.Hint)
|
||||
Logger.Error(ex, "单选框勾选改变错误", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub SyncUI()
|
||||
If AniControlEnabled = 0 AndAlso IsLoaded Then '防止默认属性变更触发动画
|
||||
If Checked Then
|
||||
'由无变有
|
||||
If ShapeDot.Opacity < 0.01 Then ShapeDot.Opacity = 1
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 10 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent(AniEasePower.Weak), , True),
|
||||
AaScale(ShapeBorder, 8, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.6, New AniEaseOutBack, , True)
|
||||
}, "MyRadioBox Border " & Uuid)
|
||||
AniStart({
|
||||
AaScale(ShapeDot, 9 - ShapeDot.Width, AnimationTimeOfCheck * 2.6,, New AniEaseOutBack(AniEasePower.Weak), , True),
|
||||
AaOpacity(ShapeDot, 1 - ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.6)
|
||||
}, "MyRadioBox Dot " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush2", "ColorBrushGray5")), AnimationTimeOfCheck)
|
||||
}, "MyRadioBox BorderColor " & Uuid)
|
||||
Else
|
||||
'由有变无
|
||||
AniStart({
|
||||
AaScale(ShapeBorder, 18 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True)
|
||||
}, "MyRadioBox Border " & Uuid)
|
||||
AniStart({
|
||||
AaScale(ShapeDot, -ShapeDot.Width, AnimationTimeOfCheck, , New AniEaseInFluent, , True),
|
||||
AaOpacity(ShapeDot, -ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.2)
|
||||
}, "MyRadioBox Dot " & Uuid)
|
||||
AniStart({
|
||||
AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush1", "ColorBrushGray5")), AnimationTimeOfCheck)
|
||||
}, "MyRadioBox BorderColor " & Uuid)
|
||||
End If
|
||||
Else
|
||||
'不使用动画
|
||||
AniStop("MyRadioBox Border " & Uuid)
|
||||
AniStop("MyRadioBox Dot " & Uuid)
|
||||
AniStop("MyRadioBox BorderColor " & Uuid)
|
||||
If Checked Then
|
||||
ShapeDot.Width = 9
|
||||
ShapeDot.Height = 9
|
||||
ShapeDot.Opacity = 1
|
||||
ShapeDot.Margin = New Thickness(5.5, 0, 0, 0)
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray5"))
|
||||
Else
|
||||
ShapeDot.Width = 0
|
||||
ShapeDot.Height = 0
|
||||
ShapeDot.Opacity = 0
|
||||
ShapeDot.Margin = New Thickness(10, 0, 0, 0)
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
|
||||
End If
|
||||
End If
|
||||
End Sub
|
||||
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return GetValue(TextProperty)
|
||||
@@ -157,19 +162,17 @@
|
||||
End Set
|
||||
End Property '内容
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioBox), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyRadioBox).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyRadioBox).LabText.Text = e.NewValue)))
|
||||
|
||||
'点击事件
|
||||
|
||||
Private MouseDowned As Boolean = False
|
||||
Private AllowMouseDown As Boolean = True
|
||||
Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
|
||||
If Not MouseDowned Then Exit Sub
|
||||
If Not MouseDowned Then Return
|
||||
Logger.Info($"按下单选框:{Text}")
|
||||
SetChecked(True, True)
|
||||
MouseDowned = False
|
||||
Log("[Control] 按下单选框:" & Text)
|
||||
SetChecked(True, True, True)
|
||||
AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
|
||||
End Sub
|
||||
Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown
|
||||
@@ -181,7 +184,7 @@
|
||||
End If
|
||||
End Sub
|
||||
Private Sub Radiobox_MouseLeave() Handles Me.MouseLeave
|
||||
If Not MouseDowned Then Exit Sub
|
||||
If Not MouseDowned Then Return
|
||||
MouseDowned = False
|
||||
AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
|
||||
If Not Checked Then
|
||||
@@ -202,15 +205,15 @@
|
||||
Radiobox_MouseLeaveAnimation()
|
||||
Else
|
||||
'不可用
|
||||
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, ColorGray4 - ShapeBorder.Stroke, AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
|
||||
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, ColorGray4 - LabText.Foreground, AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
|
||||
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, ColorGray5 - ShapeBorder.Stroke, AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
|
||||
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, ColorGray5 - LabText.Foreground, AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
|
||||
End If
|
||||
Else
|
||||
'无动画
|
||||
AniStop("MyRadioBox BorderColor " & Uuid)
|
||||
AniStop("MyRadioBox TextColor " & Uuid)
|
||||
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"))
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"))
|
||||
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray5"))
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(Me.IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"))
|
||||
End If
|
||||
End Sub
|
||||
Private Sub Radiobox_MouseEnterAnimation() Handles Me.MouseEnter
|
||||
@@ -218,16 +221,32 @@
|
||||
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn), "MyRadioBox TextColor " & Uuid)
|
||||
End Sub
|
||||
Private Sub Radiobox_MouseLeaveAnimation() Handles Me.MouseLeave
|
||||
If Not Me.IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
|
||||
If Not Me.IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then
|
||||
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
|
||||
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
|
||||
AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid)
|
||||
AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid)
|
||||
Else
|
||||
AniStop("MyRadioBox BorderColor " & Uuid)
|
||||
AniStop("MyRadioBox TextColor " & Uuid)
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"))
|
||||
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
|
||||
ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray5"))
|
||||
LabText.SetResourceReference(TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray5"))
|
||||
End If
|
||||
End Sub
|
||||
|
||||
#Region "设置"
|
||||
|
||||
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
|
||||
Checked = NewValue = SettingService.GetValue(Me)
|
||||
End Sub
|
||||
|
||||
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
|
||||
Return If(Checked, SettingService.GetValue(Me), Nothing)
|
||||
End Function
|
||||
|
||||
Private Sub SaveSetting() Handles Me.Check
|
||||
SettingService.SaveSetting(Me)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
End Class
|
||||
@@ -1,9 +1,9 @@
|
||||
<Border x:Name="PanBack" x:Class="MyRadioButton"
|
||||
<Border x:Name="PanBack" x:Class="MyRadioButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path x:Name="ShapeLogo" HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5" Margin="12,0,0,0" Stretch="Uniform" Fill="White" MaxHeight="16" MaxWidth="16" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="LabText" VerticalAlignment="Center" Grid.Column="1" Foreground="White" Margin="8,0,12,1" Grid.ColumnSpan="2" SnapsToDevicePixels="False" UseLayoutRounding="False" />
|
||||
<TextBlock x:Name="LabText" VerticalAlignment="Center" Grid.Column="1" Foreground="White" Margin="8,0,12,0" Grid.ColumnSpan="2" SnapsToDevicePixels="False" UseLayoutRounding="False" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -1,13 +1,12 @@
|
||||
Public Class MyRadioButton
|
||||
Imports System.Windows.Markup
|
||||
|
||||
<ContentProperty("Inlines")>
|
||||
Public Class MyRadioButton
|
||||
|
||||
'基础
|
||||
|
||||
Public Uuid As Integer = GetUuid()
|
||||
Public Event Check(sender As Object, raiseByMouse As Boolean)
|
||||
Public Event Change(sender As Object, raiseByMouse As Boolean)
|
||||
Public Sub RaiseChange()
|
||||
RaiseEvent Change(Me, False)
|
||||
End Sub '使外部程序可以引发本控件的 Change 事件
|
||||
|
||||
'自定义属性
|
||||
|
||||
@@ -43,23 +42,22 @@
|
||||
''' 手动设置 Checked 属性。
|
||||
''' </summary>
|
||||
''' <param name="value">新的 Checked 属性。</param>
|
||||
''' <param name="user">是否由用户引发。</param>
|
||||
''' <param name="raiseByMouse">是否由用户引发。</param>
|
||||
''' <param name="anime">是否执行动画。</param>
|
||||
Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
|
||||
Public Sub SetChecked(value As Boolean, raiseByMouse As Boolean, anime As Boolean)
|
||||
Try
|
||||
|
||||
'自定义属性基础
|
||||
|
||||
Dim IsChanged As Boolean = False
|
||||
If IsLoaded AndAlso Not value = _Checked Then RaiseEvent Change(Me, user)
|
||||
If Not value = _Checked Then
|
||||
If _Checked <> value Then
|
||||
_Checked = value
|
||||
IsChanged = True
|
||||
End If
|
||||
|
||||
'保证只有一个单选框选中
|
||||
|
||||
If IsNothing(Parent) Then Exit Sub
|
||||
If Parent Is Nothing Then Return
|
||||
Dim RadioboxList As New List(Of MyRadioButton)
|
||||
Dim CheckedCount As Integer = 0
|
||||
'收集控件列表与选中个数
|
||||
@@ -98,16 +96,23 @@
|
||||
|
||||
'更改动画
|
||||
|
||||
If Not IsChanged Then Exit Sub
|
||||
If Not IsChanged Then Return
|
||||
RefreshColor(Nothing, anime)
|
||||
|
||||
'触发事件
|
||||
If Checked Then RaiseEvent Check(Me, user)
|
||||
If Checked Then RaiseEvent Check(Me, raiseByMouse)
|
||||
RaiseCustomEvent()
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "单选按钮勾选改变错误", LogLevel.Hint)
|
||||
Logger.Error(ex, "单选按钮勾选改变错误", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
Public ReadOnly Property Inlines As InlineCollection
|
||||
Get
|
||||
Return LabText.Inlines
|
||||
End Get
|
||||
End Property
|
||||
Public Property Text As String
|
||||
Get
|
||||
Return GetValue(TextProperty)
|
||||
@@ -117,9 +122,7 @@
|
||||
End Set
|
||||
End Property '内容
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioButton), New PropertyMetadata(New PropertyChangedCallback(
|
||||
Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
|
||||
If Not IsNothing(sender) Then CType(sender, MyRadioButton).LabText.Text = e.NewValue
|
||||
End Sub)))
|
||||
Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyRadioButton).LabText.Text = e.NewValue)))
|
||||
Public Enum ColorState
|
||||
White
|
||||
Highlight
|
||||
@@ -140,17 +143,17 @@
|
||||
Public Event PreviewClick(sender As Object, e As RouteEventArgs)
|
||||
Private IsMouseDown As Boolean = False
|
||||
Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
|
||||
If Checked Then Exit Sub
|
||||
If Not IsMouseDown Then Exit Sub
|
||||
Log("[Control] 按下单选按钮:" & Text)
|
||||
If Checked Then Return
|
||||
If Not IsMouseDown Then Return
|
||||
Logger.Info($"按下单选按钮:{Text}")
|
||||
IsMouseDown = False
|
||||
Dim e As New RouteEventArgs(True)
|
||||
RaiseEvent PreviewClick(Me, e)
|
||||
If e.Handled Then Exit Sub
|
||||
If e.Handled Then Return
|
||||
SetChecked(True, True, True)
|
||||
End Sub
|
||||
Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown
|
||||
If Checked Then Exit Sub
|
||||
If Checked Then Return
|
||||
IsMouseDown = True
|
||||
RefreshColor()
|
||||
End Sub
|
||||
@@ -252,7 +255,7 @@
|
||||
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新单选按钮颜色出错")
|
||||
Logger.Warn(ex, "刷新单选按钮颜色出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Imports System.Runtime.InteropServices
|
||||
Imports System.Runtime.InteropServices
|
||||
Imports System.Windows.Interop
|
||||
Imports System.Windows.Threading
|
||||
|
||||
@@ -301,7 +301,7 @@ Public Class MyResizer
|
||||
End Sub
|
||||
|
||||
Private Sub updateMouseDown()
|
||||
Dim flag As Boolean = Mouse.LeftButton = MouseButtonState.Released
|
||||
Dim flag = (GetAsyncKeyState(&H1) And &H8000) = 0 '调用原生API判断鼠标是否抬起,如果使用WPF的API的话鼠标不在窗口上时不会更新状态 (#5655)
|
||||
If flag Then
|
||||
resizeRight = False
|
||||
resizeLeft = False
|
||||
@@ -311,5 +311,5 @@ Public Class MyResizer
|
||||
End Sub
|
||||
|
||||
Private Declare Function GetCursorPos Lib "user32.dll" (<Out()> ByRef lpPoint As PointAPI) As Boolean
|
||||
|
||||
Private Declare Function GetAsyncKeyState Lib "user32.dll" (vKey As Integer) As Short
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyScrollBar
|
||||
Public Class MyScrollBar
|
||||
Inherits Primitives.ScrollBar
|
||||
|
||||
'基础
|
||||
@@ -44,7 +44,7 @@
|
||||
End If
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "滚动条颜色改变出错")
|
||||
Logger.Warn(ex, "滚动条颜色改变出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
|
||||
@@ -1,34 +1,31 @@
|
||||
Public Class MyScrollViewer
|
||||
Public Class MyScrollViewer
|
||||
Inherits ScrollViewer
|
||||
|
||||
Public Property DeltaMuity As Double = 1
|
||||
|
||||
Public Property DeltaMult As Double = 1
|
||||
|
||||
Private RealOffset As Double
|
||||
Private Sub MyScrollViewer_PreviewMouseWheel(sender As Object, e As MouseWheelEventArgs) Handles Me.PreviewMouseWheel
|
||||
If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Exit Sub
|
||||
If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Return
|
||||
Dim SourceType = e.Source.GetType
|
||||
If Content.TemplatedParent Is Nothing AndAlso (
|
||||
(GetType(ComboBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, ComboBox).IsDropDownOpen) OrElse
|
||||
(GetType(TextBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, TextBox).AcceptsReturn) OrElse
|
||||
GetType(ComboBoxItem).IsAssignableFrom(SourceType) OrElse
|
||||
TypeOf e.Source Is CheckBox
|
||||
) Then
|
||||
(TypeOf e.Source Is Visual AndAlso
|
||||
PresentationSource.FromVisual(e.Source)?.RootVisual?.GetType().FullName = "System.Windows.Controls.Primitives.PopupRoot")) Then
|
||||
'如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
e.Handled = True
|
||||
PerformVerticalOffsetDelta(-e.Delta)
|
||||
'关闭 Tooltip (#2552)
|
||||
For Each TooltipBorder In Application.ShowingTooltips
|
||||
AniStart(AaOpacity(TooltipBorder, -1, 100), $"Hide Tooltip {GetUuid()}")
|
||||
Next
|
||||
End Sub
|
||||
Public Sub PerformVerticalOffsetDelta(Delta As Double)
|
||||
AniStart(AaDouble(Sub(AnimDelta As Double)
|
||||
RealOffset = MathClamp(RealOffset + AnimDelta, 0, ExtentHeight - ActualHeight)
|
||||
ScrollToVerticalOffset(RealOffset)
|
||||
End Sub, Delta * DeltaMuity, 300,, New AniEaseOutFluent(6)))
|
||||
AniStart(
|
||||
AaDouble(
|
||||
Sub(AnimDelta As Double)
|
||||
RealOffset = (RealOffset + AnimDelta).Clamp(0, ExtentHeight - ActualHeight)
|
||||
ScrollToVerticalOffset(RealOffset)
|
||||
End Sub, Delta * DeltaMult, 300,, New AniEaseOutFluent(6)))
|
||||
End Sub
|
||||
Private Sub MyScrollViewer_ScrollChanged(sender As Object, e As ScrollChangedEventArgs) Handles Me.ScrollChanged
|
||||
RealOffset = VerticalOffset
|
||||
@@ -43,4 +40,7 @@
|
||||
ScrollBar = GetTemplateChild("PART_VerticalScrollBar")
|
||||
End Sub
|
||||
|
||||
Private Sub MyScrollViewer_PreviewGotKeyboardFocus(sender As Object, e As KeyboardFocusChangedEventArgs) Handles Me.PreviewGotKeyboardFocus
|
||||
If e.NewFocus IsNot Nothing AndAlso TypeOf e.NewFocus Is MySlider Then e.Handled = True '#3854,阻止获得焦点时自动滚动
|
||||
End Sub
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
<local:MyCard x:Class="MySearchBox"
|
||||
<local:MyCard x:Class="MySearchBox"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -8,7 +8,7 @@
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Height="40">
|
||||
<Grid>
|
||||
<local:MyTextBox HasAnimation="False" x:Name="TextBox" BorderThickness="0" Padding="32,0,40,0" MaxLength="50" FontSize="13.5" UseLayoutRounding="False" SnapsToDevicePixels="False" VerticalContentAlignment="Center" />
|
||||
<local:MyTextBox HasBackground="False" x:Name="TextBox" BorderThickness="0" Padding="32,0,40,0" MaxLength="50" FontSize="13.5" UseLayoutRounding="False" SnapsToDevicePixels="False" VerticalContentAlignment="Center" />
|
||||
<Path Width="14" Margin="14,0,0,0" IsHitTestVisible="False" Height="14" Fill="{DynamicResource ColorBrush1}" Stretch="Uniform" Data="M675.9168 242.816C556.3008 123.2 362.4448 123.2 242.8288 242.816 123.2128 362.432 123.2128 556.288 242.8288 675.904 362.4448 795.52 556.3008 795.52 675.9168 675.904 795.5328 556.288 795.5328 362.432 675.9168 242.816L675.9168 242.816ZM1000.7168 892.48C1030.6048 922.368 1030.6048 970.816 1000.7168 1000.704 970.8288 1030.656 922.3808 1030.656 892.4288 1000.704L730.0608 838.336C728.1408 836.416 727.4368 833.92 725.7728 831.872 546.3168 960.448 295.7568 945.408 134.5408 784.192-44.8512 604.8-44.8512 313.92 134.5408 134.528 313.9328-44.864 604.8128-44.864 784.2048 134.528 945.4208 295.744 960.4608 546.304 831.8848 725.76 833.9328 727.424 836.4288 728.128 838.3488 730.048L1000.7168 892.48Z" HorizontalAlignment="Left" />
|
||||
<local:MyIconButton x:Name="BtnClear" IsHitTestVisible="False" Opacity="0" Width="24" Margin="0,0,10,0" LogoScale="0.75" Height="24" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" HorizontalAlignment="Right" Theme="Black" />
|
||||
</Grid>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Class MySearchBox
|
||||
Class MySearchBox
|
||||
Inherits MyCard
|
||||
|
||||
Public Event TextChanged(sender As Object, e As EventArgs)
|
||||
@@ -39,4 +39,7 @@
|
||||
TextBox.Focus()
|
||||
End Sub
|
||||
|
||||
Private Sub MySearchBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
|
||||
If e.Key = Key.Enter Then RaiseCustomEvent()
|
||||
End Sub
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
<Border x:Class="MySlider"
|
||||
<Border x:Class="MySlider"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
Public Class MySlider
|
||||
Imports System.Windows.Shapes
|
||||
|
||||
Public Class MySlider
|
||||
Implements ISettingControl
|
||||
|
||||
'基础
|
||||
|
||||
@@ -14,7 +17,7 @@
|
||||
Return _MaxValue
|
||||
End Get
|
||||
Set(value As Integer)
|
||||
If value = _MaxValue Then Exit Property
|
||||
If value = _MaxValue Then Return
|
||||
_MaxValue = value
|
||||
RefreshWidth(Nothing, Nothing)
|
||||
End Set
|
||||
@@ -28,8 +31,8 @@
|
||||
Set(newValue As Integer)
|
||||
Try
|
||||
|
||||
newValue = MathClamp(newValue, 0, MaxValue)
|
||||
If _Value = newValue Then Exit Property
|
||||
newValue = newValue.Clamp(0, MaxValue)
|
||||
If _Value = newValue Then Return
|
||||
|
||||
'触发 Preview 事件,修改新值
|
||||
Dim OldValue = _Value
|
||||
@@ -40,12 +43,12 @@
|
||||
If e.Handled Then
|
||||
_Value = OldValue
|
||||
DragStop()
|
||||
Exit Property
|
||||
Return
|
||||
End If
|
||||
End If
|
||||
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then
|
||||
If ActualWidth < ShapeDot.Width Then Exit Property
|
||||
If ActualWidth < ShapeDot.Width Then Return
|
||||
Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width)
|
||||
Dim DeltaProcess As Double = Math.Abs(LineFore.Width / (ActualWidth - ShapeDot.Width) - _Value / MaxValue)
|
||||
Dim Time As Double = (1 - Math.Pow(1 - DeltaProcess, 3)) * 300 + If(ChangeByKey, 100, 0)
|
||||
@@ -60,7 +63,7 @@
|
||||
If AniControlEnabled = 0 Then RaiseEvent Change(Me, False)
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "滑动条进度改变出错", LogLevel.Hint)
|
||||
Logger.Error(ex, "滑动条进度改变出错", LogBehavior.Toast)
|
||||
End Try
|
||||
End Set
|
||||
End Property
|
||||
@@ -70,7 +73,7 @@
|
||||
Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width)
|
||||
LineFore.Width = Math.Max(0, NewWidth + If(NewWidth < 0.5, 0, 0.5))
|
||||
LineBack.Width = Math.Max(0, ActualWidth - ShapeDot.Width - NewWidth + If(ActualWidth - ShapeDot.Width - NewWidth < 0.5, 0, 0.5))
|
||||
SetLeft(ShapeDot, NewWidth)
|
||||
ShapeDot.Margin = New Thickness(NewWidth, ShapeDot.Margin.Top, ShapeDot.Margin.Right, ShapeDot.Margin.Bottom)
|
||||
End Sub
|
||||
|
||||
'拖动
|
||||
@@ -88,7 +91,7 @@
|
||||
AniStop("MySlider KeyPopup " & Uuid)
|
||||
End Sub
|
||||
Public Sub DragDoing()
|
||||
Dim Percent As Double = MathClamp((Mouse.GetPosition(PanMain).X - ShapeDot.Width / 2) / (ActualWidth - ShapeDot.Width), 0, 1)
|
||||
Dim Percent As Double = ((Mouse.GetPosition(PanMain).X - ShapeDot.Width / 2) / (ActualWidth - ShapeDot.Width)).Clamp(0, 1)
|
||||
Dim NewValue As Integer = Percent * MaxValue
|
||||
If Not NewValue = Value Then
|
||||
Value = NewValue
|
||||
@@ -103,7 +106,7 @@
|
||||
Popup.IsOpen = False
|
||||
End Sub
|
||||
Public Sub RefreshPopup()
|
||||
If GetHintText Is Nothing Then Exit Sub
|
||||
If GetHintText Is Nothing Then Return
|
||||
Popup.IsOpen = True
|
||||
TextHint.Text = GetHintText.DynamicInvoke(Value)
|
||||
Dim typeface As New Typeface(TextHint.FontFamily, TextHint.FontStyle, TextHint.FontWeight, TextHint.FontStretch)
|
||||
@@ -153,7 +156,7 @@
|
||||
End If
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "滑动条颜色改变出错")
|
||||
Logger.Warn(ex, "滑动条颜色改变出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -165,7 +168,7 @@
|
||||
End Sub
|
||||
Private Sub MySlider_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
|
||||
'拒绝一边拖动一边用按键改变
|
||||
If ReferenceEquals(Me, DragControl) Then Exit Sub
|
||||
If ReferenceEquals(Me, DragControl) Then Return
|
||||
'改变值
|
||||
If e.Key = Key.Left Then
|
||||
ChangeByKey = True
|
||||
@@ -178,7 +181,7 @@
|
||||
ChangeByKey = False
|
||||
e.Handled = True
|
||||
Else
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
'更新 Popup
|
||||
If GetHintText IsNot Nothing Then
|
||||
@@ -188,4 +191,20 @@
|
||||
End If
|
||||
End Sub
|
||||
|
||||
#Region "设置"
|
||||
|
||||
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
|
||||
Value = Val(NewValue)
|
||||
End Sub
|
||||
|
||||
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
|
||||
Return Value
|
||||
End Function
|
||||
|
||||
Private Sub SaveSetting() Handles Me.Change
|
||||
SettingService.SaveSetting(Me)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
<Border x:Class="MySliderDot"
|
||||
<Border x:Class="MySliderDot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MySliderDot
|
||||
Public Class MySliderDot
|
||||
|
||||
'基础
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
If AniControlEnabled = 0 Then RaiseEvent Change(Me, False)
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "滑动条进度改变出错", LogLevel.Hint)
|
||||
Logger.Warning(ex, "滑动条进度改变出错", LogLevel.Hint)
|
||||
End Try
|
||||
End Set
|
||||
End Property
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
Public Class MyTextBox
|
||||
Public Class MyTextBox
|
||||
Inherits TextBox
|
||||
Implements ISettingControl
|
||||
|
||||
'自定义属性
|
||||
|
||||
Public Property HasAnimation As Boolean = True
|
||||
Public Property HasBackground As Boolean = True
|
||||
Public Property ShowValidateResult As Boolean = True
|
||||
Public Property CornerRadius As CornerRadius
|
||||
Get
|
||||
Return GetValue(CornerRadiusProperty)
|
||||
End Get
|
||||
Set(value As CornerRadius)
|
||||
SetValue(CornerRadiusProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly CornerRadiusProperty As DependencyProperty =
|
||||
DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyTextBox), New PropertyMetadata(New CornerRadius(3)))
|
||||
|
||||
'额外控件初始化
|
||||
|
||||
@@ -26,7 +37,7 @@
|
||||
End Property
|
||||
Public Overrides Sub OnApplyTemplate()
|
||||
MyBase.OnApplyTemplate()
|
||||
If HintText = "" OrElse labHint.Text <> "" Then Exit Sub
|
||||
If HintText = "" OrElse labHint.Text <> "" Then Return
|
||||
labHint.Text = If(Text = "", HintText, "")
|
||||
End Sub
|
||||
|
||||
@@ -34,7 +45,13 @@
|
||||
|
||||
Public Uuid As Integer = GetUuid()
|
||||
Public Shared Event ValidateChanged(sender As Object, e As EventArgs)
|
||||
Public ChangedEventList As New List(Of RoutedEventHandler)
|
||||
Private _ChangedEventList As List(Of RoutedEventHandler)
|
||||
Public ReadOnly Property ChangedEventList As List(Of RoutedEventHandler)
|
||||
Get
|
||||
If _ChangedEventList Is Nothing Then _ChangedEventList = New List(Of RoutedEventHandler)
|
||||
Return _ChangedEventList
|
||||
End Get
|
||||
End Property
|
||||
Public Custom Event ValidatedTextChanged As RoutedEventHandler
|
||||
AddHandler(value As RoutedEventHandler)
|
||||
ChangedEventList.Add(value)
|
||||
@@ -62,7 +79,24 @@
|
||||
SetValue(ValidateResultProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly ValidateResultProperty As DependencyProperty = DependencyProperty.Register("ValidateResult", GetType(String), GetType(MyTextBox), New PropertyMetadata(""))
|
||||
Public Shared ReadOnly ValidateResultProperty As DependencyProperty =
|
||||
DependencyProperty.Register("ValidateResult", GetType(String), GetType(MyTextBox), New PropertyMetadata("",
|
||||
Sub(d As MyTextBox, e As DependencyPropertyChangedEventArgs)
|
||||
d.SetValue(IsValidatedPropertyKey, String.IsNullOrEmpty(e.NewValue))
|
||||
End Sub))
|
||||
|
||||
''' <summary>
|
||||
''' 是否通过了输入验证。
|
||||
''' </summary>
|
||||
Public ReadOnly Property IsValidated As Boolean
|
||||
Get
|
||||
Return GetValue(IsValidatedProperty)
|
||||
End Get
|
||||
End Property
|
||||
Private Shared ReadOnly IsValidatedPropertyKey As DependencyPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly("IsValidated", GetType(Boolean), GetType(MyTextBox), New PropertyMetadata(True))
|
||||
Public Shared ReadOnly IsValidatedProperty As DependencyProperty = IsValidatedPropertyKey.DependencyProperty
|
||||
|
||||
''' <summary>
|
||||
''' 输入验证的规则。
|
||||
''' </summary>
|
||||
@@ -81,50 +115,49 @@
|
||||
''' </summary>
|
||||
Public Sub Validate() Handles Me.Loaded
|
||||
'执行输入验证
|
||||
ValidateResult = ""
|
||||
For Each ValidateRule As Validate In ValidateRules
|
||||
ValidateResult = ValidateRule.Validate(Text)
|
||||
If IsNothing(ValidateResult) OrElse Not ValidateResult = "" Then Exit For
|
||||
Next
|
||||
Dim IsSuccessful As Boolean = ValidateResult = ""
|
||||
ValidateResult = ModValidate.Validate(Text, ValidateRules)
|
||||
'根据结果改变样式
|
||||
If ShownValidateResult <> If(IsSuccessful, ValidateState.Success, ValidateState.FailedAndShowDetail) Then
|
||||
If ShownValidateResult <> If(IsValidated, ValidateState.Success, ValidateState.FailedAndShowDetail) Then
|
||||
If IsLoaded AndAlso labWrong IsNot Nothing Then
|
||||
ChangeValidateResult(IsSuccessful, True)
|
||||
ChangeValidateResult(IsValidated, True)
|
||||
Else
|
||||
RunInNewThread(Sub()
|
||||
Thread.Sleep(30)
|
||||
RunInUi(Sub() ChangeValidateResult(IsSuccessful, False))
|
||||
End Sub, "DelayedValidate Change")
|
||||
RunInNewThread(
|
||||
Sub()
|
||||
Thread.Sleep(30)
|
||||
RunInUi(Sub() ChangeValidateResult(IsValidated, False))
|
||||
End Sub, "DelayedValidate Change")
|
||||
End If
|
||||
End If
|
||||
'更新错误信息
|
||||
If ShowValidateResult AndAlso Not ValidateResult = "" Then
|
||||
If ShowValidateResult AndAlso Not IsValidated Then
|
||||
If IsLoaded AndAlso labWrong IsNot Nothing Then
|
||||
labWrong.Text = ValidateResult
|
||||
Else
|
||||
RunInNewThread(Sub()
|
||||
Dim IsFinished As Boolean = False
|
||||
Do Until IsFinished
|
||||
Thread.Sleep(20)
|
||||
RunInUiWait(Sub()
|
||||
If labWrong IsNot Nothing Then
|
||||
labWrong.Text = ValidateResult
|
||||
IsFinished = True
|
||||
End If
|
||||
If Not IsLoaded Then IsFinished = True
|
||||
End Sub)
|
||||
Loop
|
||||
End Sub, "DelayedValidate Text")
|
||||
RunInNewThread(
|
||||
Sub()
|
||||
Dim IsFinished As Boolean = False
|
||||
Do Until IsFinished
|
||||
Thread.Sleep(20)
|
||||
RunInUiWait(
|
||||
Sub()
|
||||
If labWrong IsNot Nothing Then
|
||||
labWrong.Text = ValidateResult
|
||||
IsFinished = True
|
||||
End If
|
||||
If Not IsLoaded Then IsFinished = True
|
||||
End Sub)
|
||||
Loop
|
||||
End Sub, "DelayedValidate Text")
|
||||
End If
|
||||
End If
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 强制显示结果为正常,类似尚未输入过文本的状态。不影响实际的检查结果。
|
||||
''' </summary>
|
||||
Public Sub ForceShowAsSuccess()
|
||||
IsTextChanged = False
|
||||
ChangeValidateResult(ValidateResult = "", True)
|
||||
ChangeValidateResult(IsValidated, True)
|
||||
End Sub
|
||||
Private ShownValidateResult As ValidateState = ValidateState.NotInited
|
||||
Private Sub ChangeValidateResult(IsSuccessful As Boolean, IsLoaded As Boolean)
|
||||
@@ -133,18 +166,18 @@
|
||||
'变为正确
|
||||
ShownValidateResult = If(IsSuccessful, ValidateState.Success, ValidateState.FailedButTextNotChanged)
|
||||
AniStart({
|
||||
AaOpacity(labWrong, -labWrong.Opacity, 150),
|
||||
AaHeight(labWrong, -labWrong.Height, 150,, New AniEaseOutFluent),
|
||||
AaCode(Sub() labWrong.Visibility = Visibility.Collapsed,, True)
|
||||
}, "MyTextBox Validate " & Uuid)
|
||||
AaOpacity(labWrong, -labWrong.Opacity, 150),
|
||||
AaHeight(labWrong, -labWrong.Height, 150,, New AniEaseOutFluent),
|
||||
AaCode(Sub() labWrong.Visibility = Visibility.Collapsed,, True)
|
||||
}, "MyTextBox Validate " & Uuid)
|
||||
ElseIf ShowValidateResult Then
|
||||
'变为错误
|
||||
ShownValidateResult = ValidateState.FailedAndShowDetail
|
||||
labWrong.Visibility = Visibility.Visible
|
||||
AniStart({
|
||||
AaOpacity(labWrong, 1 - labWrong.Opacity, 170),
|
||||
AaHeight(labWrong, 21 - labWrong.Height, 300,, New AniEaseOutBack(AniEasePower.Weak))
|
||||
}, "MyTextBox Validate " & Uuid)
|
||||
AaOpacity(labWrong, 1 - labWrong.Opacity, 150),
|
||||
AaHeight(labWrong, 21 - labWrong.Height, 150,, New AniEaseOutFluent)
|
||||
}, "MyTextBox Validate " & Uuid)
|
||||
Else
|
||||
'变为错误,但不显示文本
|
||||
ShownValidateResult = ValidateState.FailedAndHideDetail
|
||||
@@ -170,16 +203,19 @@
|
||||
''' 是否已经由用户输入过文本,若尚未输入过,则不显示输入检查的失败。
|
||||
''' </summary>
|
||||
Private IsTextChanged As Boolean = False
|
||||
Private _HintText As String = ""
|
||||
Public Property HintText As String
|
||||
Get
|
||||
Return _HintText
|
||||
Return GetValue(HintTextProperty)
|
||||
End Get
|
||||
Set(value As String)
|
||||
_HintText = value
|
||||
If labHint IsNot Nothing Then labHint.Text = If(Text = "", HintText, "")
|
||||
SetValue(HintTextProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly HintTextProperty As DependencyProperty =
|
||||
DependencyProperty.Register("HintText", GetType(String), GetType(MyTextBox), New PropertyMetadata("",
|
||||
Sub(t As MyTextBox, e As DependencyPropertyChangedEventArgs)
|
||||
If t.labHint IsNot Nothing Then t.labHint.Text = If(t.Text = "", t.HintText, "")
|
||||
End Sub))
|
||||
Private Sub MyTextBox_TextChanged(sender As MyTextBox, e As TextChangedEventArgs) Handles Me.TextChanged
|
||||
Try
|
||||
'改变提示文本
|
||||
@@ -188,11 +224,11 @@
|
||||
IsTextChanged = IsLoaded
|
||||
'进行输入验证
|
||||
Validate()
|
||||
If Not ValidateResult = "" Then Exit Sub
|
||||
If Not IsValidated Then Return
|
||||
'改变文本
|
||||
RaiseEvent ValidatedTextChanged(sender, e)
|
||||
Catch ex As Exception
|
||||
Log(ex, "进行输入验证时出错", LogLevel.Assert)
|
||||
Logger.Error(ex, "进行输入验证时出错")
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -200,15 +236,13 @@
|
||||
|
||||
Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus
|
||||
Try
|
||||
'手动关闭动画
|
||||
If Not HasAnimation Then Exit Sub
|
||||
'不对 ComboBox 从属进行动画
|
||||
If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Exit Sub
|
||||
If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Return
|
||||
'判断当前颜色
|
||||
Dim ForeColorName As String, BackColorName As String
|
||||
Dim AnimationTime As Integer
|
||||
If IsEnabled Then
|
||||
If ValidateResult = "" OrElse Not IsTextChanged Then
|
||||
If IsValidated OrElse Not IsTextChanged Then
|
||||
If IsFocused Then
|
||||
ForeColorName = "ColorBrush3"
|
||||
BackColorName = "ColorBrush7"
|
||||
@@ -232,11 +266,12 @@
|
||||
BackColorName = "ColorBrushGray6"
|
||||
AnimationTime = 200
|
||||
End If
|
||||
If Not HasBackground Then BackColorName = "ColorBrushTransparent"
|
||||
'触发颜色动画
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
'有动画
|
||||
AniStart({
|
||||
AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
|
||||
AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
|
||||
AaColor(Me, BackgroundProperty, BackColorName, AnimationTime)
|
||||
}, "MyTextBox Color " & Uuid)
|
||||
Else
|
||||
@@ -247,12 +282,12 @@
|
||||
End If
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "文本框颜色改变出错")
|
||||
Logger.Warn(ex, "文本框颜色改变出错")
|
||||
End Try
|
||||
End Sub
|
||||
Private Sub RefreshTextColor() Handles Me.IsEnabledChanged
|
||||
Dim NewColor As MyColor = If(IsEnabled, ColorGray1, ColorGray4)
|
||||
If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Exit Sub
|
||||
If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Return
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not Text = "" Then
|
||||
'有动画
|
||||
AniStart({AaColor(Me, ForegroundProperty, If(IsEnabled, "ColorBrushGray1", "ColorBrushGray4"), 200)}, "MyTextBox TextColor " & Uuid)
|
||||
@@ -263,4 +298,25 @@
|
||||
End If
|
||||
End Sub
|
||||
|
||||
'在按下回车时触发自定义事件
|
||||
Private Sub MyTextBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
|
||||
If e.Key = Key.Enter Then RaiseCustomEvent()
|
||||
End Sub
|
||||
|
||||
#Region "设置"
|
||||
|
||||
Private Sub RefreshSetting(NewValue As String) Implements ISettingControl.RefreshSetting
|
||||
Text = NewValue
|
||||
End Sub
|
||||
|
||||
Private Function GetCurrentSetting() As String Implements ISettingControl.GetCurrentSetting
|
||||
Return Text
|
||||
End Function
|
||||
|
||||
Private Sub SaveSetting() Handles Me.ValidatedTextChanged
|
||||
SettingService.SaveSetting(Me)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class MyTextButton
|
||||
Public Class MyTextButton
|
||||
Inherits Label
|
||||
|
||||
Public Event Click(sender As Object, e As EventArgs)
|
||||
@@ -22,15 +22,15 @@
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly TextProperty As DependencyProperty =
|
||||
DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("", Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs)
|
||||
If Not e.OldValue = e.NewValue Then
|
||||
AniStart({
|
||||
AaOpacity(sender, -sender.Opacity, 50),
|
||||
AaCode(Sub() sender.Content = e.NewValue,, True),
|
||||
AaOpacity(sender, 1, 170)
|
||||
}, "MyTextButton Text " & sender.Uuid)
|
||||
End If
|
||||
End Sub))
|
||||
DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("",
|
||||
Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs)
|
||||
If e.OldValue = e.NewValue Then Return
|
||||
AniStart({
|
||||
AaOpacity(sender, -sender.Opacity, 50),
|
||||
AaCode(Sub() sender.Content = e.NewValue,, True),
|
||||
AaOpacity(sender, 1, 170)
|
||||
}, "MyTextButton Text " & sender.Uuid)
|
||||
End Sub))
|
||||
|
||||
'鼠标事件
|
||||
|
||||
@@ -43,13 +43,12 @@
|
||||
IsMouseDown = False
|
||||
End Sub
|
||||
Private Sub MyTextButton_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp
|
||||
If IsMouseDown Then
|
||||
IsMouseDown = False
|
||||
Log("[Control] 按下文本按钮:" & Text)
|
||||
RaiseEvent Click(Me, Nothing)
|
||||
ModEvent.TryStartEvent(EventType, EventData)
|
||||
e.Handled = True
|
||||
End If
|
||||
If Not IsMouseDown Then Return
|
||||
IsMouseDown = False
|
||||
Logger.Info($"按下文本按钮:{Text}")
|
||||
RaiseEvent Click(Me, Nothing)
|
||||
RaiseCustomEvent()
|
||||
e.Handled = True
|
||||
End Sub
|
||||
|
||||
'指向动画
|
||||
@@ -57,7 +56,7 @@
|
||||
Private Const AnimationTimeIn As Integer = 100
|
||||
Private Const AnimationTimeOut As Integer = 200
|
||||
Private ColorName As String
|
||||
Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
|
||||
Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp
|
||||
'判断当前颜色
|
||||
Dim ForeName As String
|
||||
Dim Time As Integer
|
||||
@@ -72,7 +71,7 @@
|
||||
Time = AnimationTimeOut
|
||||
End If
|
||||
'重复性验证
|
||||
If ColorName = ForeName Then Exit Sub
|
||||
If ColorName = ForeName Then Return
|
||||
ColorName = ForeName
|
||||
'触发颜色动画
|
||||
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
|
||||
@@ -85,26 +84,4 @@
|
||||
End If
|
||||
End Sub
|
||||
|
||||
'实现自定义事件
|
||||
Public Property EventType As String
|
||||
Get
|
||||
Return GetValue(EventTypeProperty)
|
||||
End Get
|
||||
|
||||
Set(value As String)
|
||||
SetValue(EventTypeProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
|
||||
Public Property EventData As String
|
||||
Get
|
||||
Return GetValue(EventDataProperty)
|
||||
End Get
|
||||
|
||||
Set(value As String)
|
||||
SetValue(EventDataProperty, value)
|
||||
End Set
|
||||
End Property
|
||||
Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
|
||||
|
||||
End Class
|
||||
@@ -0,0 +1,70 @@
|
||||
Public Class MyVirtualizingElement(Of T As FrameworkElement)
|
||||
Inherits FrameworkElement
|
||||
|
||||
Private Initializer As Func(Of T)
|
||||
Public Sub New(Initializer As Func(Of T))
|
||||
Me.Initializer = Initializer
|
||||
LazyLoadBehavior.OnFirstEnterScrollViewerViewport(Me, AddressOf Init)
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 实例化此控件。
|
||||
''' </summary>
|
||||
Public Function Init() As T
|
||||
Dim Element As T = Initializer()
|
||||
If Parent IsNot Nothing Then
|
||||
If TypeOf Parent IsNot Panel Then Throw New Exception("MyVirtualizingElement 的父级必须是一个 Panel")
|
||||
Dim ParentPanel As Panel = Parent
|
||||
Dim CurrentIndex As Integer = ParentPanel.Children.IndexOf(Me)
|
||||
ParentPanel.Children.RemoveAt(CurrentIndex)
|
||||
ParentPanel.Children.Insert(CurrentIndex, Element)
|
||||
End If
|
||||
Return Element
|
||||
End Function
|
||||
Public Shared Widening Operator CType(Virtualized As MyVirtualizingElement(Of T)) As T
|
||||
Return Virtualized.Init()
|
||||
End Operator
|
||||
|
||||
End Class
|
||||
|
||||
'非泛型形式
|
||||
Public Class MyVirtualizingElement
|
||||
Inherits FrameworkElement
|
||||
|
||||
Private Initializer As Func(Of FrameworkElement)
|
||||
Public Sub New(Initializer As Func(Of FrameworkElement))
|
||||
Me.Initializer = Initializer
|
||||
LazyLoadBehavior.OnFirstEnterScrollViewerViewport(Me, AddressOf Init)
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 实例化此控件。
|
||||
''' </summary>
|
||||
Public Function Init() As FrameworkElement
|
||||
Dim Element As FrameworkElement = Initializer()
|
||||
If Parent IsNot Nothing Then
|
||||
If TypeOf Parent IsNot Panel Then Throw New Exception("MyVirtualizingElement 的父级必须是一个 Panel")
|
||||
Dim ParentPanel As Panel = Parent
|
||||
Dim CurrentIndex As Integer = ParentPanel.Children.IndexOf(Me)
|
||||
ParentPanel.Children.RemoveAt(CurrentIndex)
|
||||
ParentPanel.Children.Insert(CurrentIndex, Element)
|
||||
End If
|
||||
Return Element
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 获取实例化后的控件。
|
||||
''' 如果该控件没有实例化,则会立即实例化。
|
||||
''' 如果类型错误,则返回原值。
|
||||
''' </summary>
|
||||
Public Shared Function TryInit(Element As FrameworkElement) As FrameworkElement
|
||||
If GeneralUtils.IsGenericInstanceOf(Element, GetType(MyVirtualizingElement(Of ))) Then
|
||||
Return CType(Element, Object).Init()
|
||||
ElseIf TypeOf Element Is MyVirtualizingElement Then
|
||||
Return CType(Element, MyVirtualizingElement).Init()
|
||||
Else
|
||||
Return Element
|
||||
End If
|
||||
End Function
|
||||
|
||||
End Class
|
||||
@@ -1,4 +1,4 @@
|
||||
<Window
|
||||
<Window
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PCL"
|
||||
@@ -95,16 +95,15 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Canvas x:Name="ImgBack" IsHitTestVisible="False" Grid.Row="1" RenderOptions.BitmapScalingMode="LowQuality" />
|
||||
<Canvas x:Name="ImgBack" IsHitTestVisible="False" Grid.Row="1" RenderOptions.BitmapScalingMode="LowQuality"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
|
||||
<Grid x:Name="PanTitle" Height="48">
|
||||
<Grid.Background>
|
||||
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0">
|
||||
<GradientStop Color="{StaticResource ColorObject3}" Offset="0"/>
|
||||
<GradientStop Color="{StaticResource ColorObject2}" Offset="0.5"/>
|
||||
<GradientStop Color="{StaticResource ColorObject3}" Offset="1"/>
|
||||
<GradientStop Color="{StaticResource ColorObject4}" Offset="0"/>
|
||||
</LinearGradientBrush>
|
||||
</Grid.Background>
|
||||
<Image x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
|
||||
<local:MyImage x:Name="ImgTitle" IsHitTestVisible="False" Stretch="UniformToFill" IsEnabled="False" RenderOptions.BitmapScalingMode="LowQuality" />
|
||||
<local:MyIconButton x:Name="BtnTitleClose" HorizontalAlignment="Right" Height="28" VerticalAlignment="Center" Width="28" Margin="0,0,12,0" Theme="White"
|
||||
LogoScale="0.72" Logo="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" />
|
||||
<local:MyIconButton x:Name="BtnTitleMin" HorizontalAlignment="Right" Height="28" VerticalAlignment="Center" Width="28" Margin="0,0,44,0" Theme="White"
|
||||
@@ -115,25 +114,25 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M61,6.5 a10,11.5 180 1 0 0,18 M68,2.5 v24.5 h12 M98,2 v27 M107,2 v27" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="1.9" Width="53" />
|
||||
<Path x:Name="ShapeTitleLogo" Data="M26,29 v-25 h6 a7,7 180 0 1 0,14 h-6 M62.5,6.5 a10,11.5 180 1 0 0,18 M71,2.5 v24.5 h13.5" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="19,15.5,0,15.5" Stretch="fill" Stroke="White" StrokeThickness="2.2" Width="39" />
|
||||
<TextBlock x:Name="LabTitleLogo" Visibility="Collapsed" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="18,0,0,0" Foreground="White" FontSize="17" VerticalAlignment="Center" />
|
||||
<Image x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
<local:MyImage x:Name="ImageTitleLogo" SnapsToDevicePixels="False" UseLayoutRounding="False" IsHitTestVisible="False" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
<StackPanel Grid.Column="1" x:Name="PanTitleSelect" HorizontalAlignment="Left" Height="27" VerticalAlignment="Center" Orientation="Horizontal" Margin="13,0">
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect0" Text="{DynamicResource LangTitleHome}" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
|
||||
LogoScale="0.95" Logo="M985.403 552.96C985.403 812.702 773.041 1024 512 1024S38.597 812.702 38.597 552.96c0-158.208 79.163-304.955 211.771-392.625 31.074-20.52 72.94-11.973 93.46 19.101s12.012 72.94-19.062 93.46c-94.681 62.622-151.237 167.306-151.237 280.064 0 185.344 151.788 336.108 338.471 336.108S850.471 738.304 850.471 552.96c0-114.649-57.974-220.278-155.096-282.585-31.35-20.125-40.448-61.833-20.362-93.184s61.794-40.448 93.184-20.4c135.995 87.236 217.206 235.362 217.206 396.169zM512 560.128c37.258 0 67.466-30.208 67.466-67.466V67.466C579.466 30.208 549.258 0 512 0s-67.466 30.208-67.466 67.466v425.196c0 37.258 30.208 67.466 67.466 67.466z" />
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect1" Text="{DynamicResource LangTitleDownload}" Tag="1" Margin="5,0" Padding="2,0"
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect0" Text="启动" Tag="0" Margin="5,0" Checked="True" Padding="2,0"
|
||||
LogoScale="0.9" Logo="M52.1,164.5c-1.4,0-3.1-0.5-4.2-1.3c-2.6-1.7-4-4.2-4-7V43.8c0-2.9,1.6-5.8,4.1-7c1.2-0.8,2.7-1.2,4.1-1.2c1.5,0,2.9,0.4,4.2,1.2L153.1,93c0,0,0.1,0,0.1,0.1c2.6,1.7,4,4.2,4,7c0,3-1.7,5.8-4.2,7.1l-96.8,56.2C55.1,164,53.5,164.5,52.1,164.5z M60.4,142.1l72.1-42.1L60.4,58.2V142.1z" />
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect1" Text="下载" Tag="1" Margin="5,0" Padding="2,0"
|
||||
LogoScale="0.9" Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect2" Text="联机" Tag="2" Margin="5,0" Padding="2,0"
|
||||
LogoScale="1.05" Logo="M512 817c-48.601 0-88-39.399-88-88s39.399-88 88-88 88 39.399 88 88-39.399 88-88 88zM237.671 565.74C308.335 474.58 397.369 429 504.774 429c118.433 0 214.225 55.421 287.377 166.264l-53.407 30.369c-13.84 7.87-31.362 4.367-41.114-8.219-50.291-64.911-114.577-97.367-192.856-97.367-86.851 0-156.835 40.318-209.95 120.953l-47.995-28.02c-15.263-8.91-20.412-28.507-11.502-43.77a32 32 0 0 1 2.344-3.47zM107.691 419.47C205.24 278.491 337.805 208 505.379 208c178.77 0 317.694 80.224 416.772 240.672l-56.54 31.73c-13.686 7.68-30.922 4.303-40.697-7.975C735.581 360.213 629.07 303.009 505.38 300.815 373.997 298.485 261.637 362.88 168.3 494l-50.116-28.505c-15.362-8.738-20.732-28.275-11.994-43.637a32 32 0 0 1 1.5-2.387z"/>
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect3" Text="{DynamicResource LangTitleSetup}" Tag="3" Margin="5,0" Padding="2,0"
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect3" Text="设置" Tag="3" Margin="5,0" Padding="2,0"
|
||||
LogoScale="1.1" Logo="M940.4 463.7L773.3 174.2c-17.3-30-49.2-48.4-83.8-48.4H340.2c-34.6 0-66.5 18.5-83.8 48.4L89.2 463.7c-17.3 30-17.3 66.9 0 96.8L256.4 850c17.3 30 49.2 48.4 83.8 48.4h349.2c34.6 0 66.5-18.5 83.8-48.4l167.2-289.5c17.3-29.9 17.3-66.8 0-96.8z m-94.6 96.8L725.9 768.1c-17.3 30-49.2 48.4-83.8 48.4H387.5c-34.6 0-66.5-18.5-83.8-48.4L183.9 560.5c-17.3-30-17.3-66.9 0-96.8l119.8-207.5c17.3-30 49.2-48.4 83.8-48.4h254.6c34.6 0 66.5 18.5 83.8 48.4l119.8 207.5c17.3 30 17.3 66.9 0.1 96.8z M522.3 321.2c-2.5-0.1-5-0.2-7.5-0.2-119.9 0-214 110.3-186.3 235 15.8 70.9 71.5 126.6 142.4 142.4 17.5 3.9 34.7 5.4 51.4 4.7 102.1-3.9 183.6-87.9 183.6-191 0.1-103-81.5-187-183.6-190.9z m68.6 269.1c-18.5 18-43 28.9-68.6 30.7l-6 0.3c-30.2 0.4-58.6-11.4-79.7-33-19.5-20.1-30.7-47-30.9-75-0.3-29.6 11.1-57.4 32-78.3 20.6-20.6 48-32 77.2-32 2.5 0 5 0.1 7.5 0.3 26.7 1.8 51.5 13.2 70.5 32.5 19.6 20 30.8 46.9 31.2 74.9 0.2 30.2-11.5 58.6-33.2 79.6z"/>
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect4" Text="{DynamicResource LangTitleOther}" Tag="4" Margin="5,0" Padding="2,0"
|
||||
<local:MyRadioButton x:Name="BtnTitleSelect4" Text="更多" Tag="4" Margin="5,0" Padding="2,0"
|
||||
LogoScale="0.93" Logo="M364 0h-273C40 0 0 40 0 91v273C0 414 40 455 91 455h273C414 455 455 414 455 364V91C455 40 414 0 364 0zM341 341H113V113h227v227zM933 0h-273C609 0 568 40 568 91v273c0 50 40 91 91 91h273C983 455 1024 414 1024 364V91c0-50-40-91-90-91zM910 341h-227V113h227v227zM364 568h-273C40 568 0 609 0 659v273c0 50 40 91 91 91h273C414 1024 455 983 455 932v-273C455 609 414 568 364 568zM341 910H113v-227h227v227zM933 568h-273c-50 0-91 40-91 91v273c0 50 40 91 91 91h273c50 0 90-40 90-91v-273c0-50-40-90-90-90zM910 910h-227v-227h227v227z"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid x:Name="PanTitleInner" Visibility="Collapsed" Margin="-16,0,0,0" Opacity="0" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=PanTitle, Mode=OneWay}">
|
||||
<local:MyIconButton HorizontalAlignment="Left" Theme="White" x:Name="BtnTitleInner" Margin="12,0,0,0" Height="28" Width="28"
|
||||
LogoScale="0.87" Logo="M1097.162313 584.707482 250.272424 584.707482 562.833701 896.95123C591.749274 925.859979 591.749274 972.659975 562.833701 1001.568724 533.91812 1030.477473 487.058315 1030.477473 458.142742 1001.568724L21.580946 565.451235C6.757449 550.631234-0.361728 531.131238 0.028357 511.679985L0.028357 511.631236 0.028357 511.582486C-0.361728 492.13124 6.757449 472.582488 21.580946 457.762487L458.142742 21.644997C487.058315-7.215002 533.91812-7.215002 562.833701 21.644997 591.749274 50.553746 591.749274 97.4025 562.833701 126.262499L250.272424 438.55499 1097.162313 438.55499C1137.585605 438.55499 1170.304578 471.266238 1170.304578 511.631236 1170.304578 551.947483 1137.585605 584.707482 1097.162313 584.707482L1097.162313 584.707482Z" />
|
||||
LogoScale="0.87" Logo="M1097 584 250 584 562 896C591 925 591 972 562 1001 533 1030 487 1030 458 1001L21 565C6 550-0 531 0 511L0 511 0 511C-0 492 6 472 21 457L458 21C487-7 533-7 562 21 591 50 591 97 562 126L250 438 1097 438C1137 438 1170 471 1170 511 1170 551 1137 584 1097 584L1097 584Z" />
|
||||
<TextBlock SnapsToDevicePixels="False" IsHitTestVisible="False" Name="LabTitleInner" VerticalAlignment="Center" Margin="47,1,60,0" HorizontalAlignment="Left" Foreground="{DynamicResource ColorBrush8}" FontSize="15" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -145,7 +144,7 @@
|
||||
<Rectangle x:Name="RectLeftBackground" Width="0" Fill="{DynamicResource ColorBrushBackgroundTransparentSidebar}" IsHitTestVisible="False" />
|
||||
<Rectangle x:Name="RectLeftShadow" Width="4" HorizontalAlignment="Left" IsHitTestVisible="False" Grid.Column="1" >
|
||||
<Rectangle.Fill>
|
||||
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0" Opacity="0.085">
|
||||
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0" Opacity="0.04">
|
||||
<GradientStop Color="#000000" Offset="0"/>
|
||||
<GradientStop Color="#00000000" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
@@ -166,12 +165,12 @@
|
||||
</Border>
|
||||
</Border>
|
||||
</Grid>
|
||||
<StackPanel x:Name="PanHint" IsHitTestVisible="False" UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="0,0,0,25" Grid.RowSpan="2" />
|
||||
<StackPanel x:Name="PanHint" IsHitTestVisible="False" UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="0,0,0,20" Grid.RowSpan="2" />
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Grid.Row="1" Margin="15">
|
||||
<local:MyExtraButton x:Name="BtnExtraBack" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="返回顶部" Visibility="Collapsed"
|
||||
Logo="M858.496 188.9024 173.1072 188.9024c-30.2848 0-54.8352-24.5504-54.8352-54.8352L118.272 106.6496c0-30.2848 24.5504-54.8352 54.8352-54.8352l685.3888 0c30.2848 0 54.8352 24.5504 54.8352 54.8352l0 27.4176C913.3312 164.352 888.7808 188.9024 858.496 188.9024L858.496 188.9024zM150.6048 550.8608c0 0 300.0064-240.3584 303.0272-243.328 13.9776-13.5936 31.1808-21.8624 48.8192-24.7552 1.7152-0.3072 3.4304-0.5888 5.1456-0.768 2.7392-0.3072 5.4528-0.3584 8.192-0.3328 2.7392-0.0256 5.4272 0.0256 8.1664 0.3328 1.7408 0.1792 3.4304 0.4864 5.1456 0.768 17.664 2.8928 34.8672 11.1616 48.8192 24.7552 3.0464 2.944 303.0016 243.328 303.0016 243.328 32.384 31.5136 29.6192 63.9744-2.7392 95.5136-32.3328 31.5392-75.648 2.9696-108.0064-28.544l-185.8816-147.1232 0 485.8368c0 30.3104-24.5248 54.8608-54.8352 54.8608l-27.392 0c-30.2848 0-54.8352-24.5504-54.8352-54.8608L447.232 470.7072l-185.8304 147.0976c-32.3584 31.5392-75.6992 60.1344-108.032 28.5696C121.0368 614.8352 118.272 582.3744 150.6048 550.8608L150.6048 550.8608zM150.6048 550.8608" />
|
||||
<local:MyExtraButton x:Name="BtnExtraDownload" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="下载管理" Visibility="Collapsed"
|
||||
Logo="M955 610h-59c-15 0-29 13-29 29v196c0 15-13 29-29 29h-649c-15 0-29-13-29-29v-196c0-15-13-29-29-29h-59c-15 0-29 13-29 29V905c0 43 35 78 78 78h787c43 0 78-35 78-78V640c0-15-13-29-29-29zM492 740c11 11 29 11 41 0l265-265c11-11 11-29 0-41l-41-41c-11-11-29-11-41 0l-110 110c-11 11-33 3-33-13V68C571 53 555 39 541 39h-59c-15 0-29 13-29 29v417c0 17-21 25-33 13l-110-110c-11-11-29-11-41 0L226 433c-11 11-11 29 0 41L492 740z" />
|
||||
<local:MyExtraButton x:Name="BtnExtraDownload" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="任务管理" Visibility="Collapsed"
|
||||
LogoScale="1.1" Logo="M484.693 225.28 314.027 389.12l-68.267-68.267c-13.653-13.653-40.96-13.653-47.787 0-13.653 13.654-13.653 34.134 0 47.787l95.574 95.573c13.653 13.654 34.133 13.654 47.786 0L532.48 273.067c13.653-13.654 13.653-34.134 0-47.787s-34.133-13.653-47.787 0M819.2 341.333H614.4c-20.48 0-34.133 13.654-34.133 34.134S593.92 409.6 614.4 409.6h204.8c20.48 0 34.133-13.653 34.133-34.133s-13.653-34.134-34.133-34.134m0 341.334H614.4c-20.48 0-34.133 13.653-34.133 34.133s13.653 34.133 34.133 34.133h204.8c20.48 0 34.133-13.653 34.133-34.133s-13.653-34.133-34.133-34.133m-477.867-102.4c-75.093 0-136.533 61.44-136.533 136.533s61.44 136.533 136.533 136.533 136.534-61.44 136.534-136.533-61.44-136.533-136.534-136.533m0 204.8c-40.96 0-68.266-27.307-68.266-68.267s27.306-68.267 68.266-68.267S409.6 675.84 409.6 716.8s-27.307 68.267-68.267 68.267" />
|
||||
<local:MyExtraButton x:Name="BtnExtraApril" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="投降" Visibility="Collapsed"
|
||||
Logo="M124.96 32c-28.64 0-60.064 23.2-60.064 51.904v856.192c0 28.576 30.688 51.904 59.296 51.904s60.064-23.328 60.064-51.904V83.904C184.256 55.2 153.536 32 124.96 32z m616.352 71.264c-168.416 0-168.416-69.024-336.8-69.024-97.216 0-172.256 45.344-172.256 45.344l-0.736 516.736s75.776-45.28 172.96-45.28c168.416 0 168.416 69.024 336.8 69.024 105.344 0 211.168-55.328 211.168-55.328V48.064c0.032 0-105.792 55.2-211.136 55.2z m0 0" />
|
||||
<local:MyExtraButton x:Name="BtnExtraShutdown" HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="关闭 Minecraft" Visibility="Collapsed"
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,4 @@
|
||||
' This file is used by Code Analysis to maintain SuppressMessage
|
||||
' This file is used by Code Analysis to maintain SuppressMessage
|
||||
' attributes that are applied to this project.
|
||||
' Project-level suppressions either have no target or are given
|
||||
' a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB |
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,8 @@
|
||||
'动画引擎模块
|
||||
'动画引擎模块
|
||||
'使用 Ani 作为方法或属性的开头,使用 Aa 作为单个动画对象的开头(便于自动补全)
|
||||
|
||||
Imports System.Windows.Media.Effects
|
||||
|
||||
Public Module ModAnimation
|
||||
|
||||
#Region "声明"
|
||||
@@ -8,7 +10,12 @@ Public Module ModAnimation
|
||||
''' <summary>
|
||||
''' 动画速度。最大为 200。
|
||||
''' </summary>
|
||||
Public AniSpeed As Double = 1
|
||||
Public ReadOnly Property AniSpeed As Double
|
||||
Get
|
||||
Dim Setting As Integer = Settings.Get(Of Integer)("SystemDebugAnim")
|
||||
Return If(Setting >= 30, 200, (Setting * 0.1 + 0.1).Clamp(0.1, 3))
|
||||
End Get
|
||||
End Property
|
||||
''' <summary>
|
||||
''' 动画组列表。
|
||||
''' </summary>
|
||||
@@ -72,7 +79,7 @@ Public Module ModAnimation
|
||||
''' 已经执行的动画长度。如果为负数则为延迟。
|
||||
''' </summary>
|
||||
''' <remarks></remarks>
|
||||
Public TimeFinished As Integer
|
||||
Public TimeFinished As Double
|
||||
''' <summary>
|
||||
''' 已经完成的百分比。
|
||||
''' </summary>
|
||||
@@ -107,7 +114,7 @@ Public Module ModAnimation
|
||||
Public ValueLast As Object
|
||||
|
||||
Public Overrides Function ToString() As String
|
||||
Return GetStringFromEnum(TypeMain) & " | " & TimeFinished & "/" & TimeTotal & "(" & Math.Round(TimePercent * 100) & "%)" & If(Obj Is Nothing, "", " | " & Obj.ToString & "(" & Obj.GetType.Name & ")")
|
||||
Return TypeMain.ToString() & " | " & TimeFinished & "/" & TimeTotal & "(" & Math.Round(TimePercent * 100) & "%)" & If(Obj Is Nothing, "", " | " & Obj.ToString & "(" & Obj.GetType.Name & ")")
|
||||
End Function
|
||||
|
||||
End Structure
|
||||
@@ -467,7 +474,7 @@ Public Module ModAnimation
|
||||
''' 按照 WPF 方式旋转控件的动画。
|
||||
''' </summary>
|
||||
''' <param name="Obj">动画的对象。它必须已经拥有了单一的 ScaleTransform 值。</param>
|
||||
''' <param name="Value">大小改变的百分比(如-0.6)。</param>
|
||||
''' <param name="Value">旋转角度(度)。</param>
|
||||
''' <param name="Time">动画长度(毫秒)。</param>
|
||||
''' <param name="Delay">动画延迟执行的时间(毫秒)。</param>
|
||||
''' <param name="Ease">插值器类型。</param>
|
||||
@@ -514,13 +521,14 @@ Public Module ModAnimation
|
||||
''' </summary>
|
||||
''' <remarks></remarks>
|
||||
Public Function AaStack(Stack As StackPanel, Optional Time As Integer = 100, Optional Delay As Integer = 25) As List(Of AniData)
|
||||
AaStack = New List(Of AniData)
|
||||
Dim Result = New List(Of AniData)
|
||||
Dim AniDelay As Integer = 0
|
||||
For Each Item In Stack.Children
|
||||
Item.Opacity = 0
|
||||
AaStack.Add(AaOpacity(Item, 1, Time, AniDelay))
|
||||
Result.Add(AaOpacity(Item, 1, Time, AniDelay))
|
||||
AniDelay += Delay
|
||||
Next
|
||||
Return Result
|
||||
End Function
|
||||
|
||||
#End Region
|
||||
@@ -577,10 +585,10 @@ Public Module ModAnimation
|
||||
Public Class AniEaseLinear
|
||||
Inherits AniEase
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
Return MathClamp(t, 0, 1)
|
||||
Return t.Clamp(0, 1)
|
||||
End Function
|
||||
Public Overrides Function GetDelta(t1 As Double, t0 As Double) As Double
|
||||
Return MathClamp(t1, 0, 1) - MathClamp(t0, 0, 1)
|
||||
Return t1.Clamp(0, 1) - t0.Clamp(0, 1)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
@@ -595,7 +603,7 @@ Public Module ModAnimation
|
||||
p = Power
|
||||
End Sub
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
Return MathClamp(t, 0, 1) ^ p
|
||||
Return t.Clamp(0, 1) ^ p
|
||||
End Function
|
||||
End Class
|
||||
''' <summary>
|
||||
@@ -608,7 +616,7 @@ Public Module ModAnimation
|
||||
p = Power
|
||||
End Sub
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
Return 1 - MathClamp(1 - t, 0, 1) ^ p
|
||||
Return 1 - (1 - t).Clamp(0, 1) ^ p
|
||||
End Function
|
||||
End Class
|
||||
''' <summary>
|
||||
@@ -624,6 +632,26 @@ Public Module ModAnimation
|
||||
Return Ease.GetValue(t)
|
||||
End Function
|
||||
End Class
|
||||
''' <summary>
|
||||
''' 以特定速度开始的平滑结束。
|
||||
''' </summary>
|
||||
Public Class AniEaseOutFluentWithInitial
|
||||
Inherits AniEase
|
||||
Private ReadOnly alpha As Double '(初速度 / 平均速度) – 1
|
||||
''' <param name="InitialPixelPerSecond">初速度,px/s</param>
|
||||
''' <param name="TotalSecond">总时长,s</param>
|
||||
''' <param name="TotalDistance">总路程,px</param>
|
||||
Public Sub New(InitialPixelPerSecond As Double, TotalSecond As Double, TotalDistance As Double)
|
||||
Dim v0_norm As Double = InitialPixelPerSecond * TotalSecond / TotalDistance '归一化初速度
|
||||
alpha = v0_norm - 1.0
|
||||
If alpha < 0 Then alpha = 0 '初速度小于平均速度时,退化为线性
|
||||
End Sub
|
||||
Public Overrides Function GetValue(percent As Double) As Double
|
||||
Dim p As Double = percent.Clamp(0, 1)
|
||||
If alpha = 0 Then Return p '退化到线性
|
||||
Return (alpha + 1) * p / (1 + alpha * p)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
'Back / 回弹
|
||||
''' <summary>
|
||||
@@ -636,7 +664,7 @@ Public Module ModAnimation
|
||||
p = 3 - Power * 0.5
|
||||
End Sub
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
t = MathClamp(t, 0, 1)
|
||||
t = t.Clamp(0, 1)
|
||||
Return t ^ p * Math.Cos(1.5 * Math.PI * (1 - t))
|
||||
End Function
|
||||
End Class
|
||||
@@ -650,7 +678,7 @@ Public Module ModAnimation
|
||||
p = 3 - Power * 0.5
|
||||
End Sub
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
t = MathClamp(t, 0, 1)
|
||||
t = t.Clamp(0, 1)
|
||||
Return 1 - (1 - t) ^ p * Math.Cos(1.5 * Math.PI * t)
|
||||
End Function
|
||||
End Class
|
||||
@@ -694,7 +722,7 @@ Public Module ModAnimation
|
||||
p = Power + 4
|
||||
End Sub
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
t = MathClamp(t, 0, 1)
|
||||
t = t.Clamp(0, 1)
|
||||
Return t ^ ((p - 1) * 0.25) * Math.Cos((p - 3.5) * Math.PI * (1 - t) ^ 1.5)
|
||||
End Function
|
||||
End Class
|
||||
@@ -708,7 +736,7 @@ Public Module ModAnimation
|
||||
p = Power + 4
|
||||
End Sub
|
||||
Public Overrides Function GetValue(t As Double) As Double
|
||||
t = 1 - MathClamp(t, 0, 1)
|
||||
t = 1 - t.Clamp(0, 1)
|
||||
Return 1 - t ^ ((p - 1) * 0.25) * Math.Cos((p - 3.5) * Math.PI * (1 - t) ^ 1.5)
|
||||
End Function
|
||||
End Class
|
||||
@@ -723,9 +751,17 @@ Public Module ModAnimation
|
||||
''' <param name="AniGroup">由 Aa 开头的函数初始化的 AniData 对象集合。</param>
|
||||
''' <param name="Name">动画组的名称。如果重复会直接停止同名动画组。</param>
|
||||
Public Sub AniStart(AniGroup As IList, Optional Name As String = "", Optional RefreshTime As Boolean = False)
|
||||
If RefreshTime Then AniLastTick = GetTimeTick() '避免处理动画时已经造成了极大的延迟,导致动画突然结束
|
||||
If RefreshTime Then AniLastTick = GetTimeMs() '避免处理动画时已经造成了极大的延迟,导致动画突然结束
|
||||
'添加到正在执行的动画组
|
||||
Dim NewEntry As New AniGroupEntry With {.Data = GetFullList(Of AniData)(AniGroup), .StartTick = GetTimeTick()}
|
||||
Dim AllAnis = New List(Of AniData)
|
||||
For Each Element In AniGroup
|
||||
If TypeOf Element Is ICollection Then
|
||||
AllAnis.AddRange(Element)
|
||||
Else
|
||||
AllAnis.Add(Element)
|
||||
End If
|
||||
Next
|
||||
Dim NewEntry As New AniGroupEntry With {.Data = AllAnis, .StartTick = GetTimeMs()}
|
||||
If Name = "" Then
|
||||
Name = NewEntry.Uuid
|
||||
Else
|
||||
@@ -768,57 +804,58 @@ Public Module ModAnimation
|
||||
''' </summary>
|
||||
Public Sub AniStart()
|
||||
'初始化计时器
|
||||
AniLastTick = GetTimeTick()
|
||||
AniLastTick = GetTimeMs()
|
||||
AniFPSTimer = AniLastTick
|
||||
AniRunning = True '标记动画执行开始
|
||||
|
||||
RunInNewThread(Sub()
|
||||
Try
|
||||
Log("[Animation] 动画线程开始")
|
||||
Do While True
|
||||
'两帧之间的间隔时间
|
||||
Dim DeltaTime As Long = MathClamp(GetTimeTick() - AniLastTick, 0, 100000)
|
||||
If DeltaTime < 3 Then GoTo Sleeper
|
||||
AniLastTick = GetTimeTick()
|
||||
'记录 FPS
|
||||
If ModeDebug Then
|
||||
If MathClamp(AniLastTick - AniFPSTimer, 0, 100000) >= 500 Then
|
||||
AniFPS = AniFPSCounter
|
||||
AniFPSCounter = 0
|
||||
AniFPSTimer = AniLastTick
|
||||
End If
|
||||
AniFPSCounter += 2
|
||||
End If
|
||||
'执行动画
|
||||
RunInUiWait(Sub()
|
||||
AniCount = 0
|
||||
AniTimer(DeltaTime * AniSpeed)
|
||||
'#If DEBUG Then
|
||||
' FrmMain.Title = "F " & AniFPS & ", A " & AniCount & ", R " & NetManage.FileRemain
|
||||
'#Else
|
||||
' If ModeDebug Then FrmMain.Title = "FPS " & AniFPS & ", 动画 " & AniCount & ", 下载中 " & NetManage.FileRemain
|
||||
'#End If
|
||||
If RandomInteger(0, 64 * If(ModeDebug, 5, 30)) = 0 AndAlso ((AniFPS < 62 AndAlso AniFPS > 0) OrElse AniCount > 4 OrElse NetManager.FileRemain <> 0) Then
|
||||
Log("[Report] FPS " & AniFPS & ", 动画 " & AniCount & ", 下载中 " & NetManager.FileRemain & "(" & GetString(NetManager.Speed) & "/s)")
|
||||
End If
|
||||
End Sub)
|
||||
RunInNewThread(
|
||||
Sub()
|
||||
Try
|
||||
Logger.Info("动画线程开始")
|
||||
Do While True
|
||||
'两帧之间的间隔时间
|
||||
Dim DeltaTime As Long = (GetTimeMs() - AniLastTick).Clamp(0, 100000)
|
||||
If DeltaTime < 3 Then GoTo Sleeper
|
||||
AniLastTick = GetTimeMs()
|
||||
'记录 FPS
|
||||
If ModeDebug Then
|
||||
If (AniLastTick - AniFPSTimer).Clamp(0, 100000) >= 500 Then
|
||||
AniFPS = AniFPSCounter
|
||||
AniFPSCounter = 0
|
||||
AniFPSTimer = AniLastTick
|
||||
End If
|
||||
AniFPSCounter += 2
|
||||
End If
|
||||
'执行动画
|
||||
RunInUiWait(
|
||||
Sub()
|
||||
AniCount = 0
|
||||
AniTimer(DeltaTime * AniSpeed)
|
||||
If RandomInteger(0, 64 * If(ModeDebug, 5, 30)) = 0 AndAlso ((AniFPS < 62 AndAlso AniFPS > 0) OrElse AniCount > 4 OrElse NetManager.FileRemain <> 0) Then
|
||||
Logger.Info($"FPS {AniFPS}, 动画 {AniCount}, 下载中 {NetManager.FileRemain}({StringUtils.FormatByteSize(NetManager.Speed)}/s)")
|
||||
If NetManager.FileRemain < 5 Then
|
||||
For Each File In NetManager.AllFiles.Values
|
||||
If File.State < NetState.Finished Then Logger.Trace($"- {File.State} {StringUtils.FormatByteSize(File.Speed)}/s {File.LocalPath}")
|
||||
Next
|
||||
End If
|
||||
End If
|
||||
End Sub)
|
||||
Sleeper:
|
||||
'控制 FPS
|
||||
Thread.Sleep(1)
|
||||
Loop
|
||||
Catch ex As Exception
|
||||
Log(ex, "动画帧执行失败", LogLevel.Assert)
|
||||
End Try
|
||||
End Sub, "Animation", ThreadPriority.AboveNormal)
|
||||
'控制 FPS
|
||||
Thread.Sleep(1)
|
||||
Loop
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex, "动画帧执行失败", LogBehavior.AlertThenCrash)
|
||||
End Try
|
||||
End Sub, "Animation", ThreadPriority.AboveNormal)
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 动画定时器事件。
|
||||
''' </summary>
|
||||
Public Sub AniTimer(DeltaTick As Integer)
|
||||
Public Sub AniTimer(DeltaTick As Double)
|
||||
Try
|
||||
|
||||
If DeltaTick / AniSpeed > 200 Then Log("[Animation] 两个动画帧间隔 " & DeltaTick & " ms", LogLevel.Developer)
|
||||
If DeltaTick / AniSpeed > 100 Then Logger.Info($"两个动画帧间隔 {DeltaTick} ms")
|
||||
Dim i As Integer = -1
|
||||
'循环每个动画组
|
||||
Do While i + 1 < AniGroups.Count
|
||||
@@ -883,7 +920,7 @@ NextAni:
|
||||
Loop
|
||||
|
||||
Catch ex As Exception
|
||||
Log(ex, "动画刻执行失败", LogLevel.Hint)
|
||||
Logger.Error(ex, "动画刻执行失败", LogBehavior.Toast)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -896,15 +933,39 @@ NextAni:
|
||||
Select Case Ani.TypeMain
|
||||
|
||||
Case AniType.Number
|
||||
Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
|
||||
Dim Delta As Double = MathUtils.Lerp(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
|
||||
If Delta <> 0 Then
|
||||
Select Case Ani.TypeSub
|
||||
Case AniTypeSub.X
|
||||
DeltaLeft(Ani.Obj, Delta)
|
||||
If TypeOf Ani.Obj Is Window Then
|
||||
CType(Ani.Obj, Window).Left += Delta
|
||||
Else
|
||||
Select Case Ani.Obj.HorizontalAlignment
|
||||
Case HorizontalAlignment.Left, HorizontalAlignment.Stretch
|
||||
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left + Delta, Ani.Obj.Margin.Top, Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom)
|
||||
Case HorizontalAlignment.Right
|
||||
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left, Ani.Obj.Margin.Top, Ani.Obj.Margin.Right - Delta, Ani.Obj.Margin.Bottom)
|
||||
End Select
|
||||
End If
|
||||
Case AniTypeSub.Y
|
||||
DeltaTop(Ani.Obj, Delta)
|
||||
If TypeOf Ani.Obj Is Window Then
|
||||
CType(Ani.Obj, Window).Top += Delta
|
||||
Else
|
||||
Select Case Ani.Obj.VerticalAlignment
|
||||
Case VerticalAlignment.Top
|
||||
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left, Ani.Obj.Margin.Top + Delta, Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom)
|
||||
Case VerticalAlignment.Bottom
|
||||
Ani.Obj.Margin = New Thickness(Ani.Obj.Margin.Left, Ani.Obj.Margin.Top, Ani.Obj.Margin.Right, Ani.Obj.Margin.Bottom - Delta)
|
||||
End Select
|
||||
End If
|
||||
Case AniTypeSub.Opacity
|
||||
Ani.Obj.Opacity = MathClamp(Ani.Obj.Opacity + Delta, 0, 1)
|
||||
If TypeOf Ani.Obj Is UIElement Then
|
||||
Dim Obj As UIElement = Ani.Obj
|
||||
Obj.Opacity = (Obj.Opacity + Delta).Clamp(0, 1)
|
||||
ElseIf TypeOf Ani.Obj Is DropShadowEffect Then
|
||||
Dim Obj As DropShadowEffect = Ani.Obj
|
||||
Obj.Opacity = (Obj.Opacity + Delta).Clamp(0, 1)
|
||||
End If
|
||||
Case AniTypeSub.Width
|
||||
Dim Obj As FrameworkElement = Ani.Obj
|
||||
Obj.Width = Math.Max(If(Double.IsNaN(Obj.Width), Obj.ActualWidth, Obj.Width) + Delta, 0)
|
||||
@@ -936,7 +997,7 @@ NextAni:
|
||||
|
||||
Case AniType.Color
|
||||
'利用 Last 记录了余下的小数值
|
||||
Dim Delta As MyColor = MathPercent(New MyColor(0, 0, 0, 0), Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent)) + Ani.ValueLast
|
||||
Dim Delta As MyColor = MyColor.Lerp(New MyColor(0, 0, 0, 0), Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent)) + Ani.ValueLast
|
||||
Dim Obj As FrameworkElement = Ani.Obj(0)
|
||||
Dim Prop As DependencyProperty = Ani.Obj(1)
|
||||
Dim NewColor As MyColor = New MyColor(Obj.GetValue(Prop)) + Delta
|
||||
@@ -946,21 +1007,22 @@ NextAni:
|
||||
Case AniType.Scale
|
||||
Dim Obj As FrameworkElement = Ani.Obj
|
||||
Dim Delta As Double = Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent)
|
||||
Obj.Margin = New Thickness(Obj.Margin.Left + MathPercent(0, Ani.Value.Left, Delta), Obj.Margin.Top + MathPercent(0, Ani.Value.Top, Delta), Obj.Margin.Right + MathPercent(0, Ani.Value.Left, Delta), Obj.Margin.Bottom + MathPercent(0, Ani.Value.Top, Delta))
|
||||
Obj.Width = Math.Max(Obj.Width + MathPercent(0, Ani.Value.Width, Delta), 0)
|
||||
Obj.Height = Math.Max(Obj.Height + MathPercent(0, Ani.Value.Height, Delta), 0)
|
||||
Obj.Margin = New Thickness(Obj.Margin.Left + MathUtils.Lerp(0, Ani.Value.Left, Delta), Obj.Margin.Top + MathUtils.Lerp(0, Ani.Value.Top, Delta), Obj.Margin.Right + MathUtils.Lerp(0, Ani.Value.Left, Delta), Obj.Margin.Bottom + MathUtils.Lerp(0, Ani.Value.Top, Delta))
|
||||
Obj.Width = Math.Max(Obj.Width + MathUtils.Lerp(0, Ani.Value.Width, Delta), 0)
|
||||
Obj.Height = Math.Max(Obj.Height + MathUtils.Lerp(0, Ani.Value.Height, Delta), 0)
|
||||
|
||||
Case AniType.TextAppear
|
||||
Dim TextCount As Integer = If(Ani.Value(1), Ani.Value(0).ToString.Length, 0) +
|
||||
Math.Round(Ani.Value(0).ToString.Length * If(Ani.Value(1), -1, 1) * Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, 0))
|
||||
Dim NewText As String = Mid(Ani.Value(0), 1, TextCount)
|
||||
Dim Text As String = Ani.Value(0)
|
||||
Dim TextCount As Integer = If(Ani.Value(1), Text.Length, 0) +
|
||||
Math.Round(Text.Length * If(Ani.Value(1), -1, 1) * Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, 0))
|
||||
Dim NewText As String = Text.Substring(0, TextCount)
|
||||
'添加乱码
|
||||
If TextCount < Ani.Value(0).ToString.Length Then
|
||||
Dim NextText As String = Mid(Ani.Value(0), TextCount + 1, 1)
|
||||
If TextCount < Text.Length Then
|
||||
Dim NextText As String = Text.Substring(TextCount, 1)
|
||||
If Convert.ToInt32(Convert.ToChar(NextText)) >= Convert.ToInt32(Convert.ToChar(128)) Then
|
||||
NewText &= Encoding.GetEncoding("GB18030").GetString({RandomInteger(16 + 160, 87 + 160), RandomInteger(1 + 160, 89 + 160)})
|
||||
Else
|
||||
NewText &= RandomOne("0123456789./*-+\[]{};':/?,!@#$%^&*()_+-=qwwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM".ToCharArray)
|
||||
NewText &= RandomInteger(0, 85).ConvertRadix(86)
|
||||
End If
|
||||
End If
|
||||
'设置文本
|
||||
@@ -971,31 +1033,32 @@ NextAni:
|
||||
End If
|
||||
|
||||
Case AniType.Code
|
||||
Ani.TimeFinished = Ani.TimeTotal
|
||||
CType(Ani.Value, ThreadStart)()
|
||||
|
||||
Case AniType.ScaleTransform
|
||||
Dim Obj As FrameworkElement = Ani.Obj
|
||||
If TypeOf Obj.RenderTransform IsNot ScaleTransform Then
|
||||
Obj.RenderTransformOrigin = New Point(0.5, 0.5)
|
||||
If Obj.RenderTransformOrigin = New Point(0, 0) Then Obj.RenderTransformOrigin = New Point(0.5, 0.5)
|
||||
Obj.RenderTransform = New ScaleTransform(1, 1)
|
||||
End If
|
||||
Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
|
||||
Dim Delta As Double = MathUtils.Lerp(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
|
||||
CType(Obj.RenderTransform, ScaleTransform).ScaleX = Math.Max(CType(Obj.RenderTransform, ScaleTransform).ScaleX + Delta, 0)
|
||||
CType(Obj.RenderTransform, ScaleTransform).ScaleY = Math.Max(CType(Obj.RenderTransform, ScaleTransform).ScaleY + Delta, 0)
|
||||
|
||||
Case AniType.RotateTransform
|
||||
Dim Obj As FrameworkElement = Ani.Obj
|
||||
If TypeOf Obj.RenderTransform IsNot RotateTransform Then
|
||||
Obj.RenderTransformOrigin = New Point(0.5, 0.5)
|
||||
If Obj.RenderTransformOrigin = New Point(0, 0) Then Obj.RenderTransformOrigin = New Point(0.5, 0.5)
|
||||
Obj.RenderTransform = New RotateTransform(0)
|
||||
End If
|
||||
Dim Delta As Double = MathPercent(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
|
||||
Dim Delta As Double = MathUtils.Lerp(0, Ani.Value, Ani.Ease.GetDelta(Ani.TimeFinished / Ani.TimeTotal, Ani.TimePercent))
|
||||
CType(Obj.RenderTransform, RotateTransform).Angle = CType(Obj.RenderTransform, RotateTransform).Angle + Delta
|
||||
|
||||
End Select
|
||||
Ani.TimePercent = Ani.TimeFinished / Ani.TimeTotal '修改执行百分比
|
||||
Catch ex As Exception
|
||||
Log(ex, "执行动画失败:" & Ani.ToString, LogLevel.Hint)
|
||||
Logger.Error(ex, $"执行动画失败:{Ani}", LogBehavior.Toast)
|
||||
End Try
|
||||
Return Ani
|
||||
End Function
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,4 @@
|
||||
Public Module ModLoader
|
||||
Public Module ModLoader
|
||||
|
||||
'各类加载器
|
||||
''' <summary>
|
||||
@@ -37,7 +37,7 @@
|
||||
RealParent = RealParent.Parent
|
||||
End While
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取父加载器失败(" & Name & ")", LogLevel.Feedback)
|
||||
Logger.Error(ex, $"获取父加载器失败({Name})")
|
||||
Return Nothing
|
||||
End Try
|
||||
End Get
|
||||
@@ -85,23 +85,25 @@
|
||||
Return _State
|
||||
End Get
|
||||
Set(value As LoadState)
|
||||
If _State = value Then Exit Property
|
||||
If _State = value Then Return
|
||||
Dim OldState = _State
|
||||
If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
|
||||
If value = LoadState.Finished AndAlso Settings.Get(Of Boolean)("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
|
||||
_State = value
|
||||
Log("[Loader] 加载器 " & Name & " 状态改变:" & GetStringFromEnum(value))
|
||||
If value = LoadState.Canceled Then CancellationTokenSource?.Cancel()
|
||||
Logger.Log($"加载器 {[GetType].Name} {Name} 状态改变:{value}", If(value = LoadState.Failed, LogLevel.Error, LogLevel.Info))
|
||||
'实现 ILoadingTrigger 接口与 OnStateChanged 回调
|
||||
RunInUi(Sub()
|
||||
Select Case value
|
||||
Case LoadState.Loading
|
||||
LoadingState = MyLoading.MyLoadingState.Run
|
||||
Case LoadState.Failed
|
||||
LoadingState = MyLoading.MyLoadingState.Error
|
||||
Case Else
|
||||
LoadingState = MyLoading.MyLoadingState.Stop
|
||||
End Select
|
||||
RaiseEvent OnStateChangedUi(Me, value, OldState)
|
||||
End Sub)
|
||||
RunInUi(
|
||||
Sub()
|
||||
Select Case value
|
||||
Case LoadState.Loading
|
||||
LoadingState = MyLoading.MyLoadingState.Run
|
||||
Case LoadState.Failed
|
||||
LoadingState = MyLoading.MyLoadingState.Error
|
||||
Case Else
|
||||
LoadingState = MyLoading.MyLoadingState.Stop
|
||||
End Select
|
||||
RaiseEvent OnStateChangedUi(Me, value, OldState)
|
||||
End Sub)
|
||||
If HasOnStateChangedThread Then RunInThread(Sub() RaiseEvent OnStateChangedThread(Me, value, OldState))
|
||||
End Set
|
||||
End Property
|
||||
@@ -111,7 +113,7 @@
|
||||
Return _LoadingState
|
||||
End Get
|
||||
Set(value As MyLoading.MyLoadingState)
|
||||
If _LoadingState = value Then Exit Property
|
||||
If _LoadingState = value Then Return
|
||||
Dim OldState = _LoadingState
|
||||
_LoadingState = value
|
||||
RaiseEvent LoadingStateChanged(value, OldState)
|
||||
@@ -147,13 +149,13 @@
|
||||
Case LoadState.Waiting
|
||||
Return 0
|
||||
Case LoadState.Loading
|
||||
Return If(_Progress = -1, 0.02, _Progress)
|
||||
Return If(_Progress = -1, 0, _Progress)
|
||||
Case Else
|
||||
Return 1
|
||||
End Select
|
||||
End Get
|
||||
Set(value As Double)
|
||||
If _Progress = value Then Exit Property
|
||||
If _Progress = value Then Return
|
||||
Dim OldValue = _Progress
|
||||
_Progress = value
|
||||
RaiseEvent ProgressChanged(value, OldValue)
|
||||
@@ -162,13 +164,42 @@
|
||||
Private _Progress As Double = -1
|
||||
Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
|
||||
''' <summary>
|
||||
''' 创建一个 ProgressProvider,在其进度改变时,并将其进度单向映射到此加载器上。
|
||||
''' 这是 Loader 对新框架的兼容层。
|
||||
''' </summary>
|
||||
Public Function CreateSyncProgressProvider(Optional FromPercentage As Double = 0, Optional ToPercentage As Double = 1) As ProgressProvider
|
||||
Dim Provider As New ProgressProvider
|
||||
AddHandler Provider.ProgressChanged,
|
||||
Sub(ChangedProgress)
|
||||
Dim RawProgress = Provider.GetRaw()
|
||||
Progress = FromPercentage + (RawProgress.actual + RawProgress.skiped) * (ToPercentage - FromPercentage)
|
||||
End Sub
|
||||
Return Provider
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 计算总进度时的权重。它应该为预计时间(秒)。
|
||||
''' </summary>
|
||||
Public Property ProgressWeight As Double = 1
|
||||
|
||||
'状态变化
|
||||
Public MustOverride Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
Public MustOverride Sub Abort()
|
||||
Public MustOverride Sub Cancel()
|
||||
Public MustOverride Sub Failed(Ex As Exception)
|
||||
Private CancellationTokenSource As CancellationTokenSource
|
||||
''' <summary>
|
||||
''' 创建一个随此加载器取消的取消令牌;令牌取消时也会调用此加载器的 Cancel。
|
||||
''' 这是 Loader 对新框架的兼容层。
|
||||
''' </summary>
|
||||
Public Function CreateCancellationToken() As CancellationToken
|
||||
SyncLock LockState
|
||||
If CancellationTokenSource Is Nothing OrElse CancellationTokenSource.IsCancellationRequested Then
|
||||
CancellationTokenSource = New CancellationTokenSource
|
||||
CancellationTokenSource.Token.Register(AddressOf Cancel)
|
||||
End If
|
||||
If State = LoadState.Canceled Then CancellationTokenSource.Cancel()
|
||||
Return CancellationTokenSource.Token
|
||||
End SyncLock
|
||||
End Function
|
||||
|
||||
'等待结束
|
||||
Public Const WaitForExitTimeoutMessage As String = "等待加载器执行超时。"
|
||||
@@ -176,16 +207,19 @@
|
||||
''' <summary>
|
||||
''' 无限期地等待加载器完成,直到结束或抛出异常。若加载器尚未开始,则会开始执行。
|
||||
''' </summary>
|
||||
Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False,
|
||||
Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
|
||||
Start(Input, IsForceRestart)
|
||||
Do While State = LoadState.Loading
|
||||
If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
|
||||
p?.Set(Progress)
|
||||
c.ThrowIfCancellationRequested()
|
||||
Thread.Sleep(10)
|
||||
Loop
|
||||
If State = LoadState.Finished Then
|
||||
Exit Sub
|
||||
ElseIf State = LoadState.Aborted Then
|
||||
Throw New ThreadInterruptedException("加载器执行已中断。")
|
||||
Return
|
||||
ElseIf State = LoadState.Canceled Then
|
||||
Throw New OperationCanceledException("加载器执行已中断")
|
||||
ElseIf IsNothing([Error]) Then
|
||||
Throw New Exception("未知错误!")
|
||||
Else
|
||||
@@ -206,9 +240,9 @@
|
||||
If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
|
||||
Loop
|
||||
If State = LoadState.Finished Then
|
||||
Exit Sub
|
||||
ElseIf State = LoadState.Aborted Then
|
||||
Throw New ThreadInterruptedException("加载器执行已中断。")
|
||||
Return
|
||||
ElseIf State = LoadState.Canceled Then
|
||||
Throw New OperationCanceledException("加载器执行已中断")
|
||||
ElseIf IsNothing([Error]) Then
|
||||
Throw New Exception("未知错误!")
|
||||
Else
|
||||
@@ -226,28 +260,28 @@
|
||||
''' <summary>
|
||||
''' 用于异步执行并监控单一函数的加载器。
|
||||
''' </summary>
|
||||
Public Class LoaderTask(Of InputType, OutputType)
|
||||
Public Class LoaderTask(Of TIn, TOut)
|
||||
Inherits LoaderBase
|
||||
|
||||
'线程设定
|
||||
Protected Friend ThreadPriority As ThreadPriority
|
||||
'执行事件
|
||||
Protected Friend LoadDelegate As Action(Of LoaderTask(Of InputType, OutputType))
|
||||
Protected Friend LoadDelegate As Action(Of LoaderTask(Of TIn, TOut))
|
||||
Protected Friend InputDelegate As Func(Of Object)
|
||||
'状态指示
|
||||
''' <summary>
|
||||
''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
|
||||
''' </summary>
|
||||
Public ReadOnly Property IsAborted As Boolean
|
||||
Public ReadOnly Property IsCanceled As Boolean
|
||||
Get
|
||||
Return IsAbortedWithThread(Thread.CurrentThread)
|
||||
Return IsCanceledWithThread(Thread.CurrentThread)
|
||||
End Get
|
||||
End Property
|
||||
''' <summary>
|
||||
''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
|
||||
''' </summary>
|
||||
Public Function IsAbortedWithThread(Thread As Thread) As Boolean
|
||||
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Aborted
|
||||
Public Function IsCanceledWithThread(Thread As Thread) As Boolean
|
||||
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Canceled
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 在输入相同时使用原有结果的超时,单位为毫秒。
|
||||
@@ -262,101 +296,117 @@
|
||||
''' </summary>
|
||||
Public LastRunningThread As Thread = Nothing
|
||||
'输入输出
|
||||
Public Input As InputType = Nothing
|
||||
Public Output As OutputType = Nothing
|
||||
Public Input As TIn = Nothing
|
||||
Public Output As TOut = Nothing
|
||||
|
||||
'获取输入
|
||||
Public Function StartGetInput(Optional Input As InputType = Nothing, Optional InputDelegate As Func(Of Object) = Nothing) As InputType 'InputDelegate 参数存在匿名调用
|
||||
Public Function StartGetInput(Optional Input As TIn = Nothing, Optional InputDelegate As Func(Of TIn) = Nothing) As TIn 'InputDelegate 参数存在匿名调用
|
||||
If InputDelegate Is Nothing Then InputDelegate = Me.InputDelegate
|
||||
Dim NewInput As InputType = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
|
||||
Dim NewInput As TIn = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
|
||||
If (Input Is Nothing OrElse (NewInput IsNot Nothing AndAlso Input.Equals(NewInput))) AndAlso InputDelegate IsNot Nothing Then
|
||||
RunInUiWait(Sub() Input = InputDelegate())
|
||||
End If
|
||||
Return Input
|
||||
End Function
|
||||
'代码执行
|
||||
Public Function ShouldStart(ByRef Input As Object, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
|
||||
Public Function ShouldStart(ByRef Input As TIn, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
|
||||
'获取输入
|
||||
Try
|
||||
Input = StartGetInput(Input)
|
||||
Catch ex As Exception
|
||||
Log(ex, "加载输入获取失败(" & Name & ")", LogLevel.Hint)
|
||||
Logger.Error(ex, $"加载输入获取失败({Name})", LogBehavior.Toast)
|
||||
[Error] = ex
|
||||
SyncLock LockState
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
End Try
|
||||
'检验输入以确定情况
|
||||
If (Not IsForceRestart) AndAlso '不要求强行重启
|
||||
((Input IsNot Nothing AndAlso Input.Equals(Me.Input)) OrElse (Input Is Nothing AndAlso Me.Input Is Nothing)) AndAlso '输入相同
|
||||
(State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已加载成功
|
||||
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时
|
||||
'如果输入相同且未出错则不重试
|
||||
Return False
|
||||
If IsForceRestart Then Return True '强制要求重启
|
||||
If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
|
||||
|
||||
If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
|
||||
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
|
||||
LastFinishedTime = 0 OrElse GetTimeMs() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
|
||||
Return False '…则不重试
|
||||
Else
|
||||
'需要开始
|
||||
Return True
|
||||
Return True '否则需要重启
|
||||
End If
|
||||
End Function
|
||||
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
'确认是否开始加载
|
||||
If ShouldStart(Input, IsForceRestart) Then
|
||||
'输入不同或失败,开始加载
|
||||
If State = LoadState.Loading Then TriggerThreadAbort()
|
||||
If State = LoadState.Loading Then TriggerThreadInterrupt()
|
||||
Me.Input = Input
|
||||
SyncLock LockState
|
||||
State = LoadState.Loading
|
||||
Progress = -1
|
||||
End SyncLock
|
||||
Else
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
|
||||
LastRunningThread = New Thread(
|
||||
Sub()
|
||||
Try
|
||||
IsForceRestarting = IsForceRestart
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已启动")
|
||||
LoadDelegate(Me)
|
||||
If IsAborted Then
|
||||
Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已中断但线程正常运行至结束,输出被弃用(最新线程:" & If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId) & ")", LogLevel.Developer)
|
||||
Else
|
||||
RaisePreviewFinish()
|
||||
State = LoadState.Finished
|
||||
LastFinishedTime = GetTimeTick() '未中断,本次输出有效
|
||||
End If
|
||||
Catch ex As ThreadInterruptedException
|
||||
If ModeDebug Then Log(ex, "加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已触发线程中断")
|
||||
'如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted
|
||||
'如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted
|
||||
If Not IsAborted Then State = LoadState.Aborted
|
||||
Catch ex As Exception
|
||||
If IsAborted Then Exit Sub
|
||||
Log(ex, "加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 发生运行时错误", LogLevel.Developer)
|
||||
[Error] = ex
|
||||
State = LoadState.Failed
|
||||
End Try
|
||||
End Sub) With {.Name = Name, .Priority = ThreadPriority}
|
||||
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
|
||||
Sub()
|
||||
Try
|
||||
IsForceRestarting = IsForceRestart
|
||||
Logger.Trace(Function() $"加载线程 {Name} 已{If(IsForceRestarting, "强制", "")}启动")
|
||||
LoadDelegate(Me)
|
||||
If IsCanceled Then
|
||||
Logger.Warn($"加载线程 {Name} 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})")
|
||||
Return
|
||||
End If
|
||||
Logger.Trace(Function() $"加载线程 {Name} 已完成")
|
||||
RaisePreviewFinish()
|
||||
State = LoadState.Finished
|
||||
LastFinishedTime = GetTimeMs() '未中断,本次输出有效
|
||||
Catch ex As Exception
|
||||
If ex.IsCanceled Then
|
||||
'如果线程是因为判断到 IsCanceled 而提前中止,则代表已有新线程被重启,此时不应当改为 Canceled
|
||||
'如果线程是在没有 IsCanceled 时手动引发了取消异常,则代表没有重启线程,这通常代表用户手动取消,应当改为 Canceled
|
||||
Logger.Trace(ex, $"加载线程 {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
|
||||
If Not IsCanceled Then State = LoadState.Canceled
|
||||
Else
|
||||
Failed(ex)
|
||||
End If
|
||||
End Try
|
||||
End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
|
||||
Try
|
||||
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsCanceled
|
||||
Catch ex As ThreadStateException '若遇到偶发的 “线程正在运行或被终止”,则等待后重试
|
||||
Thread.Sleep(500)
|
||||
LastRunningThread.Start()
|
||||
End Try
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
If State <> LoadState.Loading Then Exit Sub
|
||||
Public Overrides Sub Failed(ex As Exception)
|
||||
[Error] = ex
|
||||
SyncLock LockState
|
||||
State = LoadState.Aborted
|
||||
If IsCanceled OrElse State >= LoadState.Finished Then Return
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
TriggerThreadAbort()
|
||||
Logger.Warn(ex, $"加载线程 {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
|
||||
TriggerThreadInterrupt()
|
||||
End Sub
|
||||
Private Sub TriggerThreadAbort()
|
||||
If LastRunningThread Is Nothing Then Exit Sub
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & LastRunningThread.ManagedThreadId & ") 已中断")
|
||||
If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
|
||||
Public Overrides Sub Cancel()
|
||||
SyncLock LockState
|
||||
If State <> LoadState.Loading Then Return
|
||||
State = LoadState.Canceled
|
||||
End SyncLock
|
||||
TriggerThreadInterrupt()
|
||||
End Sub
|
||||
Private Sub TriggerThreadInterrupt()
|
||||
If LastRunningThread Is Nothing Then Return
|
||||
If LastRunningThread.IsAlive Then
|
||||
LastRunningThread.Interrupt()
|
||||
Logger.Trace(Function() $"加载线程 {Name} 已中断")
|
||||
End If
|
||||
LastRunningThread = Nothing
|
||||
End Sub
|
||||
|
||||
Public Sub New()
|
||||
'仅仅是为了避免一些智障报错(继承类必须重写 New 的情况)
|
||||
End Sub
|
||||
Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of InputType, OutputType)), Optional InputDelegate As Func(Of InputType) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
|
||||
Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of TIn, TOut)), Optional InputDelegate As Func(Of TIn) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
|
||||
Me.Name = Name
|
||||
Me.LoadDelegate = LoadDelegate
|
||||
Me.InputDelegate = InputDelegate
|
||||
@@ -364,6 +414,53 @@
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 新 Worker 对老 Loader 的兼容包装类。
|
||||
''' </summary>
|
||||
Public Class LoaderWorker
|
||||
Inherits LoaderBase
|
||||
Public WithEvents Worker As IWorker
|
||||
Public Sub New(Name As String, Worker As IWorker)
|
||||
Me.Name = Name
|
||||
Me.Worker = Worker
|
||||
AddHandler Worker.Progress.ProgressChanged, Sub(Raw) Progress = Raw.actual + Raw.skiped
|
||||
End Sub
|
||||
|
||||
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
SyncLock LockState
|
||||
State = LoadState.Loading
|
||||
End SyncLock
|
||||
Logger.Trace(Function() $"Worker {Name} 已启动")
|
||||
IsForceRestarting = IsForceRestart
|
||||
Worker.Start()
|
||||
End Sub
|
||||
Private Sub Worker_Succeeded() Handles Worker.Succeeded
|
||||
Logger.Trace(Function() $"Worker {Name} 已完成")
|
||||
RaisePreviewFinish()
|
||||
SyncLock LockState
|
||||
State = LoadState.Finished
|
||||
End SyncLock
|
||||
End Sub
|
||||
Public Overrides Sub Cancel() Handles Worker.Canceled
|
||||
SyncLock LockState
|
||||
If State <> LoadState.Loading Then Return
|
||||
State = LoadState.Canceled
|
||||
End SyncLock
|
||||
Logger.Trace(Function() $"Worker {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
|
||||
Worker.Cancel()
|
||||
End Sub
|
||||
Public Overrides Sub Failed(Ex As Exception) Handles Worker.Failed
|
||||
SyncLock LockState
|
||||
If State >= LoadState.Finished Then Return
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
Logger.Warn(Ex, $"Worker {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
|
||||
[Error] = Ex
|
||||
Worker.Cancel()
|
||||
End Sub
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 支持多个加载器连续运作的复合加载器。
|
||||
''' </summary>
|
||||
@@ -417,7 +514,7 @@
|
||||
'改变状态
|
||||
SyncLock LockState
|
||||
If State = LoadState.Loading Then
|
||||
Exit Sub
|
||||
Return
|
||||
Else
|
||||
State = LoadState.Loading
|
||||
End If
|
||||
@@ -431,21 +528,33 @@
|
||||
End If
|
||||
RunInThread(AddressOf Update)
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
Public Overrides Sub Cancel()
|
||||
'改变状态
|
||||
SyncLock LockState
|
||||
If State = LoadState.Loading OrElse State = LoadState.Waiting Then
|
||||
State = LoadState.Aborted
|
||||
State = LoadState.Canceled
|
||||
Else
|
||||
Exit Sub
|
||||
Return
|
||||
End If
|
||||
End SyncLock
|
||||
RunInThread(Sub()
|
||||
'中断加载器
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Next
|
||||
End Sub)
|
||||
RunInThread(
|
||||
Sub()
|
||||
'中断加载器
|
||||
For Each Loader In Loaders
|
||||
Loader.Cancel()
|
||||
Next
|
||||
End Sub)
|
||||
End Sub
|
||||
Public Overrides Sub Failed(Ex As Exception)
|
||||
[Error] = Ex '先设置错误再调整状态,防止父加载器获取不到异常
|
||||
SyncLock LockState
|
||||
If State >= LoadState.Finished Then Return
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
For Each Loader In Loaders
|
||||
Loader.Cancel()
|
||||
Next
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
@@ -461,74 +570,68 @@
|
||||
Case LoadState.Finished
|
||||
'正常结束,触发刷新
|
||||
Update()
|
||||
Case LoadState.Aborted
|
||||
Case LoadState.Canceled
|
||||
'被中断,这个任务也中断
|
||||
Abort()
|
||||
Case Else
|
||||
Cancel()
|
||||
Case LoadState.Failed
|
||||
'完蛋,出错了
|
||||
SyncLock LockState
|
||||
If State >= LoadState.Finished Then Exit Sub
|
||||
[Error] = New Exception(Loader.Name & "失败", Loader.Error)
|
||||
State = Loader.State
|
||||
End SyncLock
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Next
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
Exit Sub
|
||||
Failed(New Exception(Loader.Name & "失败", Loader.Error))
|
||||
End Select
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 触发一次更新,以启动新加载器或完成。
|
||||
''' </summary>
|
||||
Private Sub Update()
|
||||
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Exit Sub
|
||||
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Canceled Then Return
|
||||
Dim IsFinished As Boolean = True
|
||||
Dim Ignore As Boolean = False
|
||||
Dim Blocked As Boolean = False
|
||||
Dim Input As Object = Me.Input
|
||||
For Each Loader In Loaders
|
||||
Select Case Loader.State
|
||||
Case LoadState.Finished
|
||||
'检查是否需要重启
|
||||
If Loader.GetType.Name.StartsWithF("LoaderTask") Then '类型名后面带有泛型,必须用 StartsWith
|
||||
'检查 LoaderTask 是否需要重启
|
||||
If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
|
||||
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
|
||||
If ModeDebug Then Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
|
||||
Logger.Info($"由于输入条件变更,重启已完成的加载器 {Loader.Name}")
|
||||
GoTo Restart
|
||||
End If
|
||||
'更新下一个加载器的输入
|
||||
Input = CType(Loader, Object).Output
|
||||
End If
|
||||
'如果不让继续启动,且已有加载器正在加载中,就不继续启动
|
||||
If Loader.Block AndAlso Not IsFinished Then Blocked = True
|
||||
Case LoadState.Loading
|
||||
'检查是否需要重启
|
||||
If Loader.GetType.Name.StartsWithF("LoaderTask") Then
|
||||
'检查 LoaderTask 是否需要重启
|
||||
If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
|
||||
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
|
||||
If ModeDebug Then Log("[Loader] 由于输入条件变更,重启进行中的加载器 " & Loader.Name, LogLevel.Developer)
|
||||
Logger.Warn($"由于输入条件变更,重启进行中的加载器 {Loader.Name}")
|
||||
GoTo Restart
|
||||
End If
|
||||
End If
|
||||
'已经有正在加载中的了,不需要再启动了
|
||||
IsFinished = False
|
||||
Ignore = True
|
||||
Blocked = True
|
||||
Case Else
|
||||
Restart:
|
||||
'未启动,则启动加载器
|
||||
IsFinished = False
|
||||
If Ignore Then Continue For
|
||||
If Blocked Then Continue For
|
||||
If Input IsNot Nothing Then
|
||||
'若输入类型与下一个加载器相同才继续
|
||||
Dim LoaderType As String = Loader.GetType.Name
|
||||
If LoaderType.StartsWithF("LoaderTask") OrElse LoaderType.StartsWithF("LoaderCombo") Then
|
||||
If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) OrElse GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderCombo(Of ))) Then
|
||||
Loader.Start(If(Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IsForceRestarting)
|
||||
ElseIf LoaderType.StartsWithF("LoaderDownload") Then
|
||||
ElseIf TypeOf Loader Is LoaderWorker Then
|
||||
Loader.Start(IsForceRestart:=IsForceRestarting)
|
||||
ElseIf TypeOf Loader Is LoaderDownload Then
|
||||
Loader.Start(If(TypeOf Input Is List(Of NetFile), Input, Nothing), IsForceRestarting)
|
||||
Else
|
||||
Throw New Exception("未知的加载器类型(" & LoaderType & ")")
|
||||
Throw New Exception("未知的加载器类型(" & Loader.GetType.Name & ")")
|
||||
End If
|
||||
Else
|
||||
Loader.Start(IsForceRestart:=IsForceRestarting)
|
||||
End If
|
||||
'如果不让继续启动,那就不启动呗
|
||||
If Loader.Block Then Ignore = True
|
||||
'阻止继续
|
||||
If Loader.Block Then Blocked = True
|
||||
End Select
|
||||
Next
|
||||
If IsFinished Then
|
||||
@@ -545,7 +648,7 @@ Restart:
|
||||
Public Shared Sub GetLoaderList(Loader As Object, ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
|
||||
For Each SubLoader In Loader.Loaders
|
||||
If SubLoader.Show OrElse Not RequireShow Then List.Add(SubLoader)
|
||||
If SubLoader.GetType.Name.StartsWithF("LoaderCombo") Then GetLoaderList(SubLoader, List)
|
||||
If GeneralUtils.IsGenericInstanceOf(SubLoader, GetType(LoaderCombo(Of ))) Then GetLoaderList(SubLoader, List)
|
||||
Next
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -566,31 +669,26 @@ Restart:
|
||||
End Class
|
||||
|
||||
'任务栏进度条
|
||||
Public LoaderTaskbar As New Concurrent.ConcurrentBag(Of LoaderBase)
|
||||
Public LoaderTaskbar As New ConcurrentList(Of LoaderBase)
|
||||
Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
|
||||
Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
|
||||
|
||||
Public Sub LoaderTaskbarAdd(Of T)(Loader As LoaderCombo(Of T))
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
|
||||
LoaderTaskbar.Add(Loader)
|
||||
Log($"[Taskbar] {Loader.Name} 已加入任务列表")
|
||||
Logger.Info($"{Loader.Name} 已加入任务列表")
|
||||
End Sub
|
||||
Public Sub LoaderTaskbarProgressRefresh()
|
||||
Try
|
||||
Dim NewState As Shell.TaskbarItemProgressState
|
||||
Dim NewProgress As Double = LoaderTaskbarProgressGet()
|
||||
'检查任务是否完成,若完成则移除
|
||||
'外显任务是否已经全部完成
|
||||
Dim IsAllDownloadTaskCompleted As Boolean = True
|
||||
For Each Loader In LoaderTaskbar
|
||||
If Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
|
||||
Next
|
||||
'若单个任务已中止或全部任务已完成,则刷新并移除
|
||||
For Each Task In LoaderTaskbar.ToList()
|
||||
If IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting Then
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task)
|
||||
LoaderTaskbar.TryTake(Task)
|
||||
Log($"[Taskbar] {Task.Name} 已移出任务列表")
|
||||
'若单个任务已中止,或全部任务已完成,则刷新并移除
|
||||
For Each Task In LoaderTaskbar
|
||||
If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
|
||||
(Task.State = LoadState.Waiting OrElse Task.State = LoadState.Canceled) Then
|
||||
FrmSpeedLeft?.TaskRefresh(Task)
|
||||
LoaderTaskbar.Remove(Task)
|
||||
Logger.Info($"{Task.Name} 已移出任务列表")
|
||||
End If
|
||||
Next
|
||||
'更新平滑后的进度
|
||||
@@ -615,15 +713,15 @@ Restart:
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新任务栏进度显示失败", LogLevel.Feedback)
|
||||
Logger.Error(ex, "刷新任务栏进度显示失败")
|
||||
End Try
|
||||
End Sub
|
||||
Public Function LoaderTaskbarProgressGet() As Double
|
||||
Try
|
||||
If Not LoaderTaskbar.Any Then Return 1
|
||||
Return MathClamp(LoaderTaskbar.Select(Function(e) e.Progress).Average(), 0, 1)
|
||||
Return LoaderTaskbar.Select(Function(l) l.Progress).Average().Clamp(0, 1)
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取任务栏进度出错", LogLevel.Feedback)
|
||||
Logger.Error(ex, "获取任务栏进度出错")
|
||||
Return 0.5
|
||||
End Try
|
||||
End Function
|
||||
@@ -651,11 +749,10 @@ Restart:
|
||||
''' </summary>
|
||||
''' <param name="ExtraPath">用于检查文件夹修改的额外路径。该路径不会传入加载器。</param>
|
||||
Public Function LoaderFolderRun(Loader As LoaderBase, FolderPath As String, Type As LoaderFolderRunType, Optional MaxDepth As Integer = 0, Optional ExtraPath As String = "", Optional WaitForExit As Boolean = False) As Boolean
|
||||
Dim FolderInfo As DirectoryInfo
|
||||
Dim Value As New LoaderFolderDictionaryEntry With {.FolderPath = FolderPath & ExtraPath, .LastCheckTime = Nothing}
|
||||
Try
|
||||
'获取数据
|
||||
FolderInfo = New DirectoryInfo(FolderPath & ExtraPath)
|
||||
Dim FolderInfo = DirectoryUtils.GetInfo(FolderPath & ExtraPath)
|
||||
Value.LastCheckTime = If(FolderInfo.Exists, GetActualLastWriteTimeUtc(FolderInfo, MaxDepth), DirectCast(Nothing, Date?))
|
||||
'如果已经检查过,则跳过
|
||||
If Type = LoaderFolderRunType.RunOnUpdated AndAlso LoaderFolderDictionary.ContainsKey(Loader) Then
|
||||
@@ -667,7 +764,7 @@ Restart:
|
||||
End If
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "文件夹加载器启动检测出错")
|
||||
Logger.Warn(ex, "文件夹加载器启动检测出错")
|
||||
End Try
|
||||
'写入检查数据
|
||||
LoaderFolderDictionary(Loader) = Value
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,11 +1,28 @@
|
||||
'提供不同的输入验证方法,名称以 Validate 开头
|
||||
'提供不同的输入验证方法,名称以 Validate 开头
|
||||
Public Module ModValidate
|
||||
|
||||
''' <summary>
|
||||
''' 进行输入验证,并返回错误原因。
|
||||
''' 如果没有错误则返回空字符串。
|
||||
''' </summary>
|
||||
Public Function Validate(Text As String, ValidateRules As IEnumerable(Of Validate)) As String
|
||||
Dim Result As String = ""
|
||||
For Each ValidateRule As Validate In ValidateRules
|
||||
Result = ValidateRule.Validate(Text)
|
||||
If Result Is Nothing Then Return ""
|
||||
If Result <> "" Then Return Result
|
||||
Next
|
||||
Return Result
|
||||
End Function
|
||||
|
||||
End Module
|
||||
|
||||
''' <summary>
|
||||
''' 输入验证规则基类。要查看所有的输入验证规则,可在输入 Validate 后查看自动补全。
|
||||
''' </summary>
|
||||
Public MustInherit Class Validate
|
||||
''' <summary>
|
||||
''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因。
|
||||
''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因;若需要中断检查并直接通过,返回 Nothing。
|
||||
''' </summary>
|
||||
Public MustOverride Function Validate(Str As String) As String
|
||||
End Class
|
||||
@@ -49,6 +66,20 @@ Public Class ValidateNullOrWhiteSpace
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 自定义的 Validate。
|
||||
''' </summary>
|
||||
Public Class ValidateFunc
|
||||
Inherits Validate
|
||||
Private ReadOnly _func As Func(Of String, String)
|
||||
Public Sub New(F As Func(Of String, String))
|
||||
_func = F
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
Return _func(Str)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 必须满足正则表达式。
|
||||
''' </summary>
|
||||
@@ -63,7 +94,7 @@ Public Class ValidateRegex
|
||||
Me.ErrorDescription = ErrorDescription
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Not RegexCheck(Str, Regex) Then Return ErrorDescription
|
||||
If Not Str.RegexCheck(Regex) Then Return ErrorDescription
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
@@ -77,7 +108,7 @@ Public Class ValidateHttp
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Str.EndsWithF("/") Then Str = Str.Substring(0, Str.Length - 1)
|
||||
If Not RegexCheck(Str, "^(http[s]?)\://") Then Return "输入的网址无效!"
|
||||
If Not Str.RegexCheck("^(http[s]?)\://") Then Return "输入的网址无效!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
@@ -97,10 +128,10 @@ Public Class ValidateInteger
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Str.Length > 9 Then Return "请输入一个大小合理的数字!"
|
||||
Dim Valed As Integer = Val(Str)
|
||||
If Valed.ToString <> Str Then Return "请输入一个整数!"
|
||||
If Val(Str) > Max Then Return "不可超过 " & Max & "!"
|
||||
If Val(Str) < Min Then Return "不可低于 " & Min & "!"
|
||||
Dim Valed As Integer
|
||||
If Not Integer.TryParse(Str, Valed) Then Return "请输入一个整数!"
|
||||
If Valed > Max Then Return "不可超过 " & Max & "!"
|
||||
If Valed < Min Then Return "不可低于 " & Min & "!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
@@ -119,9 +150,9 @@ Public Class ValidateLength
|
||||
Me.Max = Max
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Len(Str) <> Max AndAlso Max = Min Then Return "长度必须为 " & Max & " 个字符!"
|
||||
If Len(Str) > Max Then Return "长度最长为 " & Max & " 个字符!"
|
||||
If Len(Str) < Min Then Return "长度至少需 " & Min & " 个字符!"
|
||||
If Len(Str) <> Max AndAlso Max = Min Then Return $"长度必须为 {Max} 个字符!"
|
||||
If Len(Str) > Max Then Return $"长度最长为 {Max} 个字符!"
|
||||
If Len(Str) < Min Then Return $"长度至少需 {Min} 个字符!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
@@ -131,21 +162,24 @@ End Class
|
||||
''' </summary>
|
||||
Public Class ValidateExcept
|
||||
Inherits Validate
|
||||
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
|
||||
Public Property Excepts As New ObjectModel.Collection(Of String)
|
||||
Public Property ErrorMessage As String
|
||||
Public Sub New()
|
||||
ErrorMessage = "输入内容不能包含 %!"
|
||||
ErrorMessage = "输入内容不能包含 %"
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %!")
|
||||
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
|
||||
Me.Excepts = Excepts
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
End Sub
|
||||
Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能包含 %!")
|
||||
Me.Excepts = New ObjectModel.Collection(Of String)
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
For Each Data As String In Excepts
|
||||
Me.Excepts.Add(Data)
|
||||
Next
|
||||
Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
|
||||
Me.New(
|
||||
New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
|
||||
ErrorMessage)
|
||||
End Sub
|
||||
Public Sub New(Excepts As IEnumerable(Of Char), Optional ErrorMessage As String = "输入内容不能包含 %")
|
||||
Me.New(
|
||||
New ObjectModel.Collection(Of String)(Excepts.Select(Function(c) c.ToString).ToList),
|
||||
ErrorMessage)
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
For Each Ch As String In Excepts
|
||||
@@ -164,29 +198,31 @@ End Class
|
||||
''' </summary>
|
||||
Public Class ValidateExceptSame
|
||||
Inherits Validate
|
||||
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
|
||||
Public Property Excepts As New ObjectModel.Collection(Of String)
|
||||
Public Property ErrorMessage As String
|
||||
Public Property IgnoreCase As Boolean = False
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
|
||||
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
|
||||
Me.Excepts = Excepts
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
End Sub
|
||||
Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
|
||||
Me.Excepts = New ObjectModel.Collection(Of String)
|
||||
For Each Data As String In Excepts
|
||||
Me.Excepts.Add(Data)
|
||||
Next
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
|
||||
Me.New(
|
||||
New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
|
||||
ErrorMessage, IgnoreCase)
|
||||
End Sub
|
||||
Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
|
||||
Me.New(
|
||||
New ObjectModel.Collection(Of String) From {Except},
|
||||
ErrorMessage, IgnoreCase)
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
|
||||
For Each Ch As String In Excepts
|
||||
If IgnoreCase Then
|
||||
If Str.ToLower = Ch.ToLower Then Return ErrorMessage.Replace("%", Ch)
|
||||
If Str.Lower = Ch.Lower Then Return ErrorMessage.Replace("%", Ch)
|
||||
Else
|
||||
If Str.Equals(Ch) Then Return ErrorMessage.Replace("%", Ch) '使用 = 不确定是否会忽略大小写
|
||||
End If
|
||||
@@ -196,55 +232,80 @@ Public Class ValidateExceptSame
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 必须为特定字符串。
|
||||
''' </summary>
|
||||
Public Class ValidateSame
|
||||
Inherits Validate
|
||||
Public Property Required As String
|
||||
Public Property ErrorMessage As String
|
||||
Public Property IgnoreCase As Boolean = False
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(Required As String, Optional ErrorMessage As String = "输入内容有误", Optional IgnoreCase As Boolean = False)
|
||||
Me.Required = Required
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
Return If(String.Compare(Str, Required, IgnoreCase) = 0, "", ErrorMessage)
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 对文件夹名的粗略的特化检测。
|
||||
''' </summary>
|
||||
Public Class ValidateFolderName
|
||||
Inherits Validate
|
||||
Public Property Path As String
|
||||
Public Property Folder As String
|
||||
Public Property UseMinecraftCharCheck As Boolean = True
|
||||
Public Property IgnoreCase As Boolean = True
|
||||
Private ReadOnly PathIgnore As IEnumerable(Of DirectoryInfo)
|
||||
Private ReadOnly PathIgnore As IEnumerable(Of String)
|
||||
Private ReadOnly IgnoreList As List(Of String)
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(Path As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreCase As Boolean = True)
|
||||
Me.Path = Path
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
Public Sub New(Folder As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreList As List(Of String) = Nothing)
|
||||
Me.Folder = Folder
|
||||
Me.UseMinecraftCharCheck = UseMinecraftCharCheck
|
||||
On Error Resume Next
|
||||
PathIgnore = New DirectoryInfo(Path).EnumerateDirectories
|
||||
PathIgnore = DirectoryUtils.EnumerateDirectories(Folder)
|
||||
Me.IgnoreList = IgnoreList
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
Try
|
||||
'检查是否为空
|
||||
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
If LengthCheck <> "" Then Return LengthCheck
|
||||
'检查空格
|
||||
If Str.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
|
||||
If Str.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!"
|
||||
'检查长度
|
||||
LengthCheck = New ValidateLength(1, 100).Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
If LengthCheck <> "" Then Return LengthCheck
|
||||
'检查尾部小数点
|
||||
If Str.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!"
|
||||
'检查特殊字符
|
||||
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
|
||||
If Not CharactCheck = "" Then Return CharactCheck
|
||||
Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
|
||||
If CharactCheck <> "" Then Return CharactCheck
|
||||
'检查特殊字符串
|
||||
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
|
||||
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
|
||||
If InvalidStrCheck <> "" Then Return InvalidStrCheck
|
||||
'检查 NTFS 8.3 文件名(#4505)
|
||||
If Str.RegexCheck(".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
|
||||
'检查文件夹重名
|
||||
Dim Arr As New List(Of String)
|
||||
Dim folderName As String
|
||||
If PathIgnore IsNot Nothing Then
|
||||
For Each Folder As DirectoryInfo In PathIgnore
|
||||
Arr.Add(Folder.Name)
|
||||
For Each FolderPath In PathIgnore
|
||||
folderName = PathUtils.GetLastPart(FolderPath)
|
||||
If IgnoreList Is Nothing OrElse Not IgnoreList.Contains(folderName, StringComparer.OrdinalIgnoreCase) Then Arr.Add(folderName)
|
||||
Next
|
||||
End If
|
||||
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", IgnoreCase).Validate(Str)
|
||||
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", True).Validate(Str)
|
||||
If Not SameNameCheck = "" Then Return SameNameCheck
|
||||
Return ""
|
||||
Catch ex As Exception
|
||||
Log(ex, "检查文件夹名出错")
|
||||
Logger.Warn(ex, "检查文件夹名出错")
|
||||
Return "错误:" & ex.Message
|
||||
End Try
|
||||
End Function
|
||||
@@ -277,18 +338,20 @@ Public Class ValidateFileName
|
||||
If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!"
|
||||
'检查长度
|
||||
LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, ""))
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
If LengthCheck <> "" Then Return LengthCheck
|
||||
'检查尾部小数点
|
||||
If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!"
|
||||
'检查特殊字符
|
||||
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
|
||||
If Not CharactCheck = "" Then Return CharactCheck
|
||||
Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
|
||||
If CharactCheck <> "" Then Return CharactCheck
|
||||
'检查特殊字符串
|
||||
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
|
||||
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
|
||||
If InvalidStrCheck <> "" Then Return InvalidStrCheck
|
||||
'检查 NTFS 8.3 文件名(#4505)
|
||||
If Str.RegexCheck(".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
|
||||
'检查文件重名
|
||||
If ParentFolder IsNot Nothing Then
|
||||
Dim DirInfo As New DirectoryInfo(ParentFolder)
|
||||
Dim DirInfo = DirectoryUtils.GetInfo(ParentFolder)
|
||||
If DirInfo.Exists Then
|
||||
Dim SameNameCheck = New ValidateExceptSame(DirInfo.EnumerateFiles("*").Select(Function(f) f.Name),
|
||||
"不可与现有文件重名!", IgnoreCase).Validate(Str)
|
||||
@@ -299,7 +362,7 @@ Public Class ValidateFileName
|
||||
End If
|
||||
Return ""
|
||||
Catch ex As Exception
|
||||
Log(ex, "检查文件名出错")
|
||||
Logger.Warn(ex, "检查文件名出错")
|
||||
Return "错误:" & ex.Message
|
||||
End Try
|
||||
End Function
|
||||
@@ -319,18 +382,22 @@ Public Class ValidateFolderPath
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
'去除尾部斜线,统一为 \
|
||||
Str = Str.Replace("/", "\")
|
||||
If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
|
||||
If Not PathUtils.RemoveSlashSuffix(Str).EndsWithF(":") Then Str = PathUtils.RemoveSlashSuffix(Str)
|
||||
'检查是否为空
|
||||
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
If LengthCheck <> "" Then Return LengthCheck
|
||||
'检查长度
|
||||
LengthCheck = New ValidateLength(1, 254).Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
If LengthCheck <> "" Then Return LengthCheck
|
||||
'检查 NTFS 8.3 文件名(#4505)
|
||||
Dim Match = Str.RegexSeek(".{2,}~\d")
|
||||
If Match IsNot Nothing Then Return $"文件夹名不能包含 {Match} 这一特殊格式!"
|
||||
'检查开头
|
||||
If Str.StartsWithF("\\Mac\") Then GoTo Fin
|
||||
For Each Drive As DriveInfo In My.Computer.FileSystem.Drives
|
||||
If Str.ToUpper = Drive.Name Then Return ""
|
||||
If Str.StartsWithF(Drive.Name, True) Then GoTo Fin
|
||||
For Each Drive In Environment.GetLogicalDrives().Where(Function(p) DirectoryUtils.Exists(p))
|
||||
Dim DriveName As String = Drive.Upper.First & ":\"
|
||||
If Str.Upper = DriveName Then Return ""
|
||||
If Str.StartsWithF(DriveName, True) Then GoTo Fin
|
||||
Next
|
||||
Return "文件夹路径头存在错误!"
|
||||
Fin:
|
||||
@@ -341,7 +408,7 @@ Fin:
|
||||
Dim SubLengthCheck As String = New ValidateNullOrWhiteSpace().Validate(SubStr)
|
||||
If Not SubLengthCheck = "" Then Return "文件夹路径存在错误!"
|
||||
'检查特殊字符
|
||||
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "路径中存在无效字符!").Validate(SubStr)
|
||||
Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "路径中存在无效字符!").Validate(SubStr)
|
||||
If Not CharactCheck = "" Then Return CharactCheck
|
||||
'检查头部空格
|
||||
If SubStr.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
'一个万能的自动图片类型转换工具类
|
||||
'一个万能的自动图片类型转换工具类
|
||||
|
||||
Imports System.Drawing.Imaging
|
||||
|
||||
Public Class MyBitmap
|
||||
|
||||
''' <summary>
|
||||
''' 位图缓存。
|
||||
''' </summary>
|
||||
Public Shared BitmapCache As New Dictionary(Of String, MyBitmap)
|
||||
|
||||
''' <summary>
|
||||
''' 存储的图片
|
||||
''' </summary>
|
||||
@@ -17,35 +12,46 @@ Public Class MyBitmap
|
||||
'自动类型转换
|
||||
'支持的类:Image,ImageSource,Bitmap,ImageBrush,BitmapSource
|
||||
Public Shared Widening Operator CType(Image As System.Drawing.Image) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Image
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return Image.Pic
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As ImageSource) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
|
||||
Dim Bitmap = Image.Pic
|
||||
Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
|
||||
Dim bitmapData = Bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Dim BitmapPic As System.Drawing.Bitmap = Image.Pic
|
||||
Dim rect = New System.Drawing.Rectangle(0, 0, BitmapPic.Width, BitmapPic.Height)
|
||||
Dim bitmapData = BitmapPic.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
|
||||
Try
|
||||
Dim size = rect.Width * rect.Height * 4
|
||||
Return BitmapSource.Create(Bitmap.Width, Bitmap.Height, Bitmap.HorizontalResolution, Bitmap.VerticalResolution, PixelFormats.Bgra32, Nothing, bitmapData.Scan0, size, bitmapData.Stride)
|
||||
Dim Result = BitmapSource.Create(BitmapPic.Width, BitmapPic.Height, BitmapPic.HorizontalResolution, BitmapPic.VerticalResolution,
|
||||
PixelFormats.Bgra32, Nothing, bitmapData.Scan0,
|
||||
rect.Width * rect.Height * 4, bitmapData.Stride)
|
||||
Result.Freeze()
|
||||
Return Result
|
||||
Finally
|
||||
Bitmap.UnlockBits(bitmapData)
|
||||
BitmapPic.UnlockBits(bitmapData)
|
||||
End Try
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As System.Drawing.Bitmap) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Bitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return Image.Pic
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As ImageBrush) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As ImageBrush
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New ImageBrush(New MyBitmap(Image.Pic))
|
||||
End Operator
|
||||
|
||||
@@ -57,27 +63,41 @@ Public Class MyBitmap
|
||||
FilePathOrResourceName = FilePathOrResourceName.Replace("pack://application:,,,/images/", PathImage)
|
||||
If FilePathOrResourceName.StartsWithF(PathImage) Then
|
||||
'使用缓存
|
||||
If BitmapCache.ContainsKey(FilePathOrResourceName) Then
|
||||
Pic = BitmapCache(FilePathOrResourceName).Pic
|
||||
Static Cache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap)
|
||||
If Cache.ContainsKey(FilePathOrResourceName) Then
|
||||
Pic = Cache(FilePathOrResourceName).Pic
|
||||
Else
|
||||
Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
|
||||
BitmapCache.Add(FilePathOrResourceName, Pic)
|
||||
Cache.TryAdd(FilePathOrResourceName, Pic)
|
||||
End If
|
||||
Else
|
||||
'使用这种自己接管 FileStream 的方法加载才能解除文件占用
|
||||
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open)
|
||||
Pic = New System.Drawing.Bitmap(InputStream)
|
||||
InputStream.Dispose()
|
||||
Using InputStream = FileUtils.ReadAsStream(FilePathOrResourceName)
|
||||
'判断是否为 WebP 文件头
|
||||
Dim Header(1) As Byte
|
||||
InputStream.Read(Header, 0, 2)
|
||||
InputStream.Seek(0, SeekOrigin.Begin)
|
||||
If Header(0) = 82 AndAlso Header(1) = 73 Then
|
||||
'读取 WebP
|
||||
Dim FileBytes(InputStream.Length - 1) As Byte
|
||||
InputStream.Read(FileBytes, 0, FileBytes.Length)
|
||||
Pic = WebPDecoder.DecodeFromBytes(FileBytes) '将代码隔离在另外一个类中,这样只要不走进这个分支就不会加载 Imazen.WebP.dll
|
||||
Else
|
||||
Pic = New System.Drawing.Bitmap(InputStream)
|
||||
End If
|
||||
End Using
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Pic = My.Application.TryFindResource(FilePathOrResourceName)
|
||||
If Pic Is Nothing Then
|
||||
Pic = New System.Drawing.Bitmap(1, 1)
|
||||
Log(ex, $"加载位图失败({FilePathOrResourceName})")
|
||||
Throw
|
||||
If TypeOf ex Is ArgumentException Then
|
||||
Throw New Exception($"图片格式不支持,或图片文件损坏({FilePathOrResourceName})", ex)
|
||||
Else
|
||||
Throw New Exception($"加载 MyBitmap 意外失败({FilePathOrResourceName})", ex)
|
||||
End If
|
||||
Else
|
||||
Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer)
|
||||
Logger.Warn(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})")
|
||||
Exit Try
|
||||
End If
|
||||
End Try
|
||||
@@ -104,6 +124,11 @@ Public Class MyBitmap
|
||||
Pic = New System.Drawing.Bitmap(MS)
|
||||
End Using
|
||||
End Sub
|
||||
Private Class WebPDecoder '将代码隔离在另外一个类中,这样只要不调用这个方法就不会加载 Imazen.WebP.dll
|
||||
Public Shared Function DecodeFromBytes(Bytes As Byte()) As System.Drawing.Bitmap
|
||||
Return New Imazen.WebP.SimpleDecoder().DecodeFromBytes(Bytes, Bytes.Length)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
|
||||
@@ -135,7 +160,7 @@ Public Class MyBitmap
|
||||
Public Sub Save(FilePath As String)
|
||||
Dim encoder As BitmapEncoder = New PngBitmapEncoder()
|
||||
encoder.Frames.Add(BitmapFrame.Create(Me))
|
||||
Using fileStream As New FileStream(FilePath, FileMode.Create)
|
||||
Using fileStream = FileUtils.CreateAsStream(FilePath)
|
||||
encoder.Save(fileStream)
|
||||
End Using
|
||||
End Sub
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
Public Class PclLogger
|
||||
Inherits FileLogger
|
||||
|
||||
''' <inheritdoc/>
|
||||
Public Overrides Function Format(Text As String, Level As LogLevel, FilePath As String, Ex As Exception) As String
|
||||
Text = MyBase.Format(Text, Level, FilePath, Ex)
|
||||
Text = FilterUserName(FilterAccessToken(Text, "*"c), "*"c)
|
||||
Return Text
|
||||
End Function
|
||||
|
||||
''' <inheritdoc/>
|
||||
Public Overrides Sub HandleBehavior(RawMessage As String, FormattedMessage As String, Behavior As LogBehavior, Ex As Exception)
|
||||
If IsProgramEnding Then Return
|
||||
MyBase.HandleBehavior(RawMessage, FormattedMessage, Behavior, Ex)
|
||||
Dim BriefText = If(Ex Is Nothing, RawMessage, If(RawMessage Is Nothing, "", $"{RawMessage}:") & Ex.GetDisplay(False))
|
||||
BriefText = FilterUserName(FilterAccessToken(BriefText, "*"c), "*"c)
|
||||
Dim DetailText = If(Ex Is Nothing, RawMessage, If(RawMessage Is Nothing, "", $"{RawMessage}:") & Ex.GetDisplay(True))
|
||||
DetailText = FilterUserName(FilterAccessToken(DetailText, "*"c), "*"c)
|
||||
Select Case Behavior
|
||||
Case LogBehavior.None
|
||||
'啥也不干
|
||||
Case LogBehavior.ToastIfDebug
|
||||
If BuildType = BuildTypes.Debug OrElse ModeDebug Then Hint("[调试模式] " & BriefText, HintType.Blue, False)
|
||||
Case LogBehavior.Toast
|
||||
Hint(BriefText, HintType.Red, False)
|
||||
Case LogBehavior.Alert
|
||||
MyMsgBox(DetailText, "错误", IsWarn:=True)
|
||||
Case LogBehavior.AlertThenFeedback
|
||||
If PageSetupSystem.IsLauncherNewest Then
|
||||
If MyMsgBox(DetailText & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", "错误", "反馈", "取消", IsWarn:=True) = 1 Then Feedback(False, True)
|
||||
Else
|
||||
MyMsgBox(DetailText & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", "错误", IsWarn:=True)
|
||||
End If
|
||||
Case LogBehavior.AlertThenCrash
|
||||
Static FirstTrigger As Boolean = True
|
||||
If FirstTrigger Then
|
||||
'首次触发
|
||||
FirstTrigger = False
|
||||
If PageSetupSystem.IsLauncherNewest Then
|
||||
If MsgBox(DetailText & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, "错误") = MsgBoxResult.Yes Then Feedback(False, True)
|
||||
Else
|
||||
MsgBox(DetailText & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, "错误")
|
||||
End If
|
||||
Else
|
||||
'多次触发,直接使程序崩溃(这通常代表着在其他线程循环触发严重异常)
|
||||
Thread.Sleep(2000)
|
||||
End If
|
||||
FormMain.EndProgramForce(ProcessReturnValues.Exception)
|
||||
End Select
|
||||
'遥测
|
||||
If Behavior >= LogBehavior.Toast Then Telemetry("错误日志", "Exception", DetailText)
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
Loaded 100 of 286 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user