Compare commits
674 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2097a1250b | |||
| 22025f9b00 | |||
| 80c1badba5 | |||
| be308ba280 | |||
| fa7f3d44e4 | |||
| 9bb00489fe | |||
| 33948c604c | |||
| b8fe5bbd66 | |||
| 3fcc01c253 | |||
| 658d48c17b | |||
| 8d76c014c8 | |||
| 26cd125534 | |||
| 1bc23af61a | |||
| a2aa6b48a8 | |||
| 5784c974f7 | |||
| 80503dc42e | |||
| d76195f7ae | |||
| 2fe482b802 | |||
| 16283f4643 | |||
| cd7a801400 | |||
| f6d8558d07 | |||
| de20b506f0 | |||
| ea1d52292e | |||
| 463e506ca3 | |||
| 3bae64a2c7 | |||
| 5d13c8b543 | |||
| c6c0789794 | |||
| d7df39290f | |||
| b64cefad46 | |||
| 4690ab3c30 | |||
| b61c7490b3 | |||
| 9e511d29a6 | |||
| a9cdc36967 | |||
| 96c51cd7ff | |||
| 636769c0ef | |||
| 7beb2a3cc5 | |||
| 52d318054c | |||
| 5ee2ad6f3d | |||
| 097d414d0d | |||
| e0ce119374 | |||
| da9a5242cb | |||
| 0211b38be9 | |||
| 512e9b21cd | |||
| 9181ad55ae | |||
| a1cb9e1b28 | |||
| ea09a3e798 | |||
| 21fa146f6f | |||
| 1a3130041f | |||
| 7095a5890c | |||
| 4a86d1aa05 | |||
| 19fe7223fb | |||
| 8867fde3f2 | |||
| 588d1822b1 | |||
| d2b3b38d9e | |||
| 206ae3d9ed | |||
| a67b7a2fd0 | |||
| f5a08b225c | |||
| c807f97c29 | |||
| 8382413137 | |||
| c6acafd3a6 | |||
| 9d43056361 | |||
| 52f8e24954 | |||
| dac05fec84 | |||
| cb4ed77572 | |||
| 3427cbdc2e | |||
| ec0fb0c3cf | |||
| 357983179c | |||
| 78b66c141e | |||
| 3ac88bb400 | |||
| 27493b857c | |||
| e5a976abcf | |||
| 9e89bf5303 | |||
| 54eb330711 | |||
| e9014c6f5d | |||
| 620bcf6fab | |||
| c03bbd9e13 | |||
| b428b4ec5b | |||
| 876cc116ea | |||
| 0ba00c08ef | |||
| d41ecd9d55 | |||
| 35d351ce6b | |||
| 5d3af58361 | |||
| 8554b92f42 | |||
| 76363b4263 | |||
| b4250b9161 | |||
| 441f40886f | |||
| e8e8ad5d63 | |||
| 454078ec82 | |||
| 63f29c2686 | |||
| 280445f613 | |||
| d7f6433b53 | |||
| 22da4cc408 | |||
| 4e185ef584 | |||
| fd9b4b4ba6 | |||
| 2f79bbcb0f | |||
| 2c70d243df | |||
| 7710b77255 | |||
| d1eed23399 | |||
| ff9ce4df44 | |||
| b7c52842f2 | |||
| 365459f649 | |||
| e6354f724f | |||
| 14eedca939 | |||
| 83529cfe09 | |||
| bd4e1c1810 | |||
| 5665fcc823 | |||
| 710a9014dd | |||
| 108c6b2b17 | |||
| 70735943c3 | |||
| d01a24f879 | |||
| ec2d5043ff | |||
| 15082c2c52 | |||
| 44278d68b4 | |||
| 40eeb9db66 | |||
| a5eb1dfca7 | |||
| 8719677f11 | |||
| 8f01b6c5fe | |||
| 9e63a3f49b | |||
| 84edb7bbe6 | |||
| c86ce00a17 | |||
| aba6c18a25 | |||
| 62c81e6d44 | |||
| 68ea323855 | |||
| f94d81ad20 | |||
| 063f8b1751 | |||
| 35cfd26ece | |||
| f331cb1b4d | |||
| 502e205071 | |||
| 975b563b8d | |||
| cc88630859 | |||
| cce0b930ec | |||
| 723c825df2 | |||
| 5b4c966354 | |||
| 69023f98d9 | |||
| dce5722e96 | |||
| cbdbc93274 | |||
| 042b153684 | |||
| 4054423721 | |||
| 2e63a6eaca | |||
| 9eca7eb2ee | |||
| 1489fb645e | |||
| 5c0ca841d7 | |||
| a4d3d3ff9c | |||
| c3b67f4a4b | |||
| cde5daf19a | |||
| 904b2c0988 | |||
| f9907e2ec6 | |||
| 7329c0097c | |||
| 0478949305 | |||
| 34172a54fe | |||
| 6dd629eda5 | |||
| 636dd2b8d5 | |||
| b20dbc5202 | |||
| ee45104eb9 | |||
| 152be89860 | |||
| 6b5a375542 | |||
| 03d049846d | |||
| 6ed084bb94 | |||
| f5332b63a9 | |||
| 87356215c3 | |||
| 07c7acc37a | |||
| 4a6d9dee67 | |||
| 52eb93e59c | |||
| 2245a018e6 | |||
| 8b327fd715 | |||
| 7a7289a4c8 | |||
| ceb259819f | |||
| fdfbaedbd7 | |||
| 9591fbf146 | |||
| b2a09dbf6d | |||
| 11a5a7fdbe | |||
| 745b798d89 | |||
| 7bac32e3c4 | |||
| 880ca08571 | |||
| 85d4d8a71e | |||
| abb8ed0bcc | |||
| bea0d10a6c | |||
| 80d943af23 | |||
| 90b85e469d | |||
| 0d419c1323 | |||
| f7a2ecc2e0 | |||
| 5fdbf83f32 | |||
| c513ad9291 | |||
| a2dfa55dbd | |||
| 78f8d9a271 | |||
| b25a5aac8c | |||
| 25cc70035e | |||
| e5d70d5a0e | |||
| 066b0d1f8a | |||
| 71b2e56187 | |||
| 457832fbbe | |||
| 00554e066b | |||
| 9f9775e585 | |||
| 9cf70ae74e | |||
| 4f046987c6 | |||
| 699666c23b | |||
| 6a8cdd0155 | |||
| ce61f0e2b7 | |||
| b3cb0bf93f | |||
| 07328eea2d | |||
| 5f7a6f2e07 | |||
| 39d54cc493 | |||
| d31f40408b | |||
| 7f88d9ae27 | |||
| 1c1dd81474 | |||
| 53a498c581 | |||
| bfb6346812 | |||
| bd2fa1e427 | |||
| 6c4bfeff29 | |||
| 299a77eea8 | |||
| 351a00fb1e | |||
| 2140e1ebe1 | |||
| 497a820ba2 | |||
| 7182f3554a | |||
| 506ba52502 | |||
| 90c1630af4 | |||
| 5934abd448 | |||
| 86f432ef01 | |||
| 4913019c5c | |||
| a6316797e6 | |||
| 00d7549bde | |||
| 8fc33f5649 | |||
| 729b544675 | |||
| 865415a6c0 | |||
| 32e8324275 | |||
| 842f6dd726 | |||
| 59f7d11df3 | |||
| dcd2f52c59 | |||
| b45413c232 | |||
| b4481ff680 | |||
| d7d7a3919f | |||
| f8e4732dcd | |||
| 1b92ed66b7 | |||
| 9ba74b9504 | |||
| 8cd49f12d1 | |||
| c48ca9ee89 | |||
| 43bcf71bf5 | |||
| ee48813df1 | |||
| f4a67e2822 | |||
| f03733da04 | |||
| 207560bcc7 | |||
| f38313ff2c | |||
| b5d9e21f37 | |||
| 5d42a8957e | |||
| b276c60909 | |||
| d03564bce9 | |||
| 41409c39d5 | |||
| aa9c5107d0 | |||
| 7a141822e7 | |||
| a4fd301d5a | |||
| b2ef8b96ef | |||
| baa9e1003e | |||
| 5d17a586d5 | |||
| 36b97b2adc | |||
| f13bffa834 | |||
| 4394566ed3 | |||
| de95c24f66 | |||
| 6058cb9cff | |||
| 6b20d3e268 | |||
| ce037a437a | |||
| 1349dab6d4 | |||
| d3d31925ee | |||
| d9fa77c86b | |||
| dad01235b0 | |||
| 52d9c26076 | |||
| 1a0236237d | |||
| 58bb7a5ebc | |||
| efbab58bca | |||
| eedbc7a863 | |||
| 626cda63ff | |||
| 7b7a9d93aa | |||
| f5c68dac61 | |||
| 2b6b106771 | |||
| 047586883e | |||
| 0c1a25dd6b | |||
| 7840a9a713 | |||
| 28f96ffcd3 | |||
| 04b2663183 | |||
| 1a11027871 | |||
| 854a00803e | |||
| 11ae5f157f | |||
| ecfe05139e | |||
| 4a392e03a7 | |||
| a66037f886 | |||
| 58c399dcbe | |||
| f33e617f44 | |||
| 3e976c1026 | |||
| c15c75075c | |||
| bf8d988c75 | |||
| 6fb7af3d46 | |||
| 1c2860c180 | |||
| fc41f10c37 | |||
| dedf366866 | |||
| edb10096d6 | |||
| ba42a1e6c9 | |||
| f5f989d140 | |||
| a0058c104d | |||
| 7f1f322d04 | |||
| 57ac8d8771 | |||
| bcd0509eff | |||
| 624af87795 | |||
| 616df56657 | |||
| 4efd6abb56 | |||
| 8c657a4ccf | |||
| db582f6c88 | |||
| a7b861f83c | |||
| 19b1c7ae8b | |||
| 192cec68c7 | |||
| 3541522fc6 | |||
| 4afa66f3f3 | |||
| 7fde157184 | |||
| a1935e8299 | |||
| 50e993fd89 | |||
| a25ec6b0af | |||
| efeb99aaac | |||
| f19118432d | |||
| b69eac2886 | |||
| 4efc4e7f34 | |||
| 4755eb06e2 | |||
| e4dc1c0b4e | |||
| 89f54c2b4a | |||
| abf52f0d49 | |||
| 044df3f09c | |||
| 271829f9c1 | |||
| 13625b37a8 | |||
| cceadd2a3d | |||
| 781191196f | |||
| eb0cf27218 | |||
| 1706341283 | |||
| 71f46b3bff | |||
| 4c39798682 | |||
| b861aa385d | |||
| 156e8a2686 | |||
| f88acf1375 | |||
| be770d4607 | |||
| 7565f624c9 | |||
| 65d56297fd | |||
| 86caac4a1d | |||
| d382ac4fa2 | |||
| f9ceeaad44 | |||
| 0691293973 | |||
| 9491b48eb6 | |||
| 6c7f63270f | |||
| a62f793288 | |||
| 5fc715b7d0 | |||
| 901b54b829 | |||
| 0efa1127a3 | |||
| 28fc53799a | |||
| bdc4af7cd4 | |||
| c68996ff20 | |||
| f517a63cda | |||
| fefb9b490e | |||
| 383e1d5368 | |||
| 4e4ce36e76 | |||
| 19983a113e | |||
| 8eaac465ff | |||
| 17b2d744ba | |||
| 166e0d400a | |||
| 5cf409ce0b | |||
| e37e847a8b | |||
| 32c77d3743 | |||
| 45e368d9e7 | |||
| 97496302fb | |||
| 675959e615 | |||
| f641b282b6 | |||
| e92a05683c | |||
| 93bef2e144 | |||
| 44e331ae96 | |||
| f295c85668 | |||
| 6f843bface | |||
| 2dea6076f0 | |||
| 78c6c7b571 | |||
| a50bec9326 | |||
| 7d193d7de6 | |||
| 5bbb3e6a6c | |||
| 32c1ddab46 | |||
| 15198c32ea | |||
| 476503338f | |||
| ba6303dca0 | |||
| 23432465ac | |||
| 1f0b5cebeb | |||
| 1f83c84a7e | |||
| e209af9c57 | |||
| a714d312a8 | |||
| 4a776c1fe8 | |||
| da86f07cbe | |||
| 48e0ca887d | |||
| 0f3b6c4cec | |||
| 9ce9631135 | |||
| c73a5c3a7e | |||
| 62d35127b1 | |||
| 09f17caabe | |||
| c5355d7497 | |||
| d5142ad82c | |||
| 428b278c78 | |||
| 33c1e04934 | |||
| 6c075d80ba | |||
| 38713108e0 | |||
| 6b75fea813 | |||
| de6fc84fec | |||
| ecd276a3a0 | |||
| 86fbd0cebc | |||
| 6a97919f7a | |||
| f2e7e17bd1 | |||
| eedd6cee1d | |||
| 25e11fa9de | |||
| 4742600b86 | |||
| b3e4850413 | |||
| 1085f6c57a | |||
| fd8d13447c | |||
| 758a3d3f99 | |||
| fd8b4d94d6 | |||
| fa7dae8177 | |||
| 21638218c6 | |||
| a29a414e8a | |||
| 251c7f399e | |||
| 1da55f2011 | |||
| 856125edc2 | |||
| 32ef30ebd8 | |||
| 8972e42fee | |||
| 91f206aad0 | |||
| 5626babcdf | |||
| 98175152db | |||
| 817ade3e34 | |||
| ab5493f8c4 | |||
| 796bd99377 | |||
| a0dc60a403 | |||
| a92e58abf1 | |||
| e8f0793feb | |||
| 2d7eff8205 | |||
| 6412892985 | |||
| 69ae0ffc71 | |||
| 854be23cfb | |||
| 02e143217e | |||
| 4feec82b03 | |||
| 938ca648f1 | |||
| ee018ea287 | |||
| 8ab60ad000 | |||
| 17eaf34500 | |||
| 2485958f6e | |||
| 7ac3a22fa6 | |||
| 285f211f50 | |||
| e9ec424625 | |||
| 1e96477127 | |||
| 87ffa48265 | |||
| 8b64df435c | |||
| dcf88ee510 | |||
| b21a15376d | |||
| 3e87ed4f9b | |||
| a6cd9c955b | |||
| 86b52b76ed | |||
| 853f380e5d | |||
| 027686a3b5 | |||
| 79b87e59be | |||
| 3a37e55162 | |||
| 1ff40c0016 | |||
| 4f7c1021c8 | |||
| e687c78ba8 | |||
| 2c2f46a0d8 | |||
| 29fa565258 | |||
| 51f3d410c9 | |||
| a0d159da9d | |||
| 9edb58ee27 | |||
| a8de65ab0e | |||
| ef8a894489 | |||
| 992f388a56 | |||
| a54e7206a0 | |||
| c4e74b21fc | |||
| 7b9e07bdda | |||
| 19f5204253 | |||
| c4ad5c5f81 | |||
| e888118a20 | |||
| 7afce136f6 | |||
| 71c4dadeee | |||
| c6a95c99ef | |||
| a6d44685b3 | |||
| a8ce61dad9 | |||
| 0f47d1473d | |||
| 2134ee516a | |||
| 6c60306bee | |||
| 816748833c | |||
| cac0fca3bb | |||
| 5ed28b121e | |||
| c4180eba6f | |||
| 0065a1f81f | |||
| e1f3a6ada4 | |||
| ed7ffbcb13 | |||
| 6a8d9db407 | |||
| 81140837f1 | |||
| 70232645a8 | |||
| 4d3b3ef3df | |||
| d203314424 | |||
| b95da2d8d5 | |||
| a8c5ccda17 | |||
| 86b996637c | |||
| 1480d4a14e | |||
| 8586735ca8 | |||
| b8013afd64 | |||
| 812bc83939 | |||
| d6829744bb | |||
| a7a03544b6 | |||
| ffa8729d10 | |||
| ce291d3a77 | |||
| 297dbad60c | |||
| 54982218f5 | |||
| c14f4df494 | |||
| f53f23aa4b | |||
| 10c59fb4b4 | |||
| a5cffc5ea4 | |||
| 200c299ce7 | |||
| 8547276549 | |||
| b09afcf725 | |||
| a2860be7a3 | |||
| de9d026e3a | |||
| 27a1baae7b | |||
| f2584309d1 | |||
| 831fd09615 | |||
| e0e22a09c0 | |||
| 60c6e0632d | |||
| 6da13c4fc0 | |||
| ec330aea69 | |||
| 318682b63a | |||
| dd53d7ff0a | |||
| b03207d287 | |||
| 7db87b7c36 | |||
| 54d0aaca04 | |||
| 352aa886c8 | |||
| 8fada629f0 | |||
| 34bb3cd0bd | |||
| 5555a67422 | |||
| 3631d35946 | |||
| 460d4a5ac1 | |||
| b7b5db18fd | |||
| 58d877af6b | |||
| ab20e0d8f2 | |||
| 42e55c51b0 | |||
| ca82c24c76 | |||
| 66874e7a85 | |||
| e365a94f71 | |||
| 81298e5980 | |||
| 4eb920553b | |||
| 1be5408302 | |||
| 1e9a7b037a | |||
| e79ed438ef | |||
| ff658409be | |||
| 9c26353fed | |||
| 2a3ce9549a | |||
| 8093f55b4f | |||
| 4a46892486 | |||
| 35fafc39a8 | |||
| 3976970bb5 | |||
| 1a3ab49849 | |||
| 32d75ae23d | |||
| 29c6844390 | |||
| 9c6eee59a8 | |||
| 4ff2be67d3 | |||
| 47d3eb13ce | |||
| 5de3043544 | |||
| 4b1d544c6b | |||
| 6cb8fe3a6e | |||
| 1acf2e044e | |||
| 9e9e960fa3 | |||
| e767873ff1 | |||
| b57762afed | |||
| 24526c5a48 | |||
| ed1b62ec50 | |||
| 98e727cf55 | |||
| 6c4ed850b0 | |||
| db6b9da945 | |||
| 52c930624a | |||
| b7ddcb5419 | |||
| 34f27c8e8d | |||
| 3f7055327b | |||
| 51fb13b448 | |||
| 5273990305 | |||
| a44badddaa | |||
| 3e8d186dc8 | |||
| 51f8a0541d | |||
| 808da0a3a7 | |||
| 1c522840cd | |||
| 6fb8506551 | |||
| 7bf0438960 | |||
| fe02ec1852 | |||
| 4020925af1 | |||
| 863b42f516 | |||
| 7f6b03be27 | |||
| 880770b718 | |||
| ed74f98919 | |||
| 4a1e42d5ee | |||
| b21e608eea | |||
| 671ec2fba6 | |||
| 293a89575a | |||
| 542812eb74 | |||
| e44cb715d0 | |||
| f7a1a1c851 | |||
| 757c08cd02 | |||
| 15fcd50151 | |||
| 867a747853 | |||
| 02dfbb54b6 | |||
| c42c6c8dfe | |||
| 316d568cbc | |||
| 0b72b1f1b3 | |||
| 25b52def92 | |||
| ab67ea48f0 | |||
| 912c1b91e4 | |||
| d4f1d21634 | |||
| 6c0e7c2e64 | |||
| e054a50b55 | |||
| ff005e6398 | |||
| 74cc8e7dda | |||
| 254d70a787 | |||
| 28b728822c | |||
| 5a53471bcd | |||
| a3fee5d77c | |||
| 6f961225d7 | |||
| 1b0b4f7505 | |||
| fac66fafbd | |||
| b9d6ac3047 | |||
| e7c723de46 | |||
| 10629b253c | |||
| dace6ad486 | |||
| 4c70f35714 | |||
| 7b4d13c16f | |||
| 0e561ad24e | |||
| d09d8223c9 | |||
| 6dafde9735 | |||
| a0f84bf017 | |||
| 7049c53889 | |||
| 7750fa799d | |||
| 5f828736de | |||
| 869c8ce31b | |||
| 0b7b55224f | |||
| 2a23be44f2 | |||
| fa0cbb4d3f | |||
| 38bc4decf6 | |||
| b5ec6e0d79 | |||
| 82486c707d | |||
| 5ff437bed5 | |||
| 2f7e0b85c0 | |||
| c9548af008 | |||
| b20e4a041f | |||
| 87f64ccc81 | |||
| 58028ea95c | |||
| 35fa062cc3 | |||
| 9de6555519 | |||
| a9baf47823 | |||
| a9b64d2899 | |||
| 4d9fa754e8 | |||
| 862ac27212 | |||
| 4ada8b05e7 | |||
| d250e83df9 | |||
| 8e8f4256ac | |||
| d6502251e1 | |||
| c3159c61ee | |||
| 0e5b31a8e4 | |||
| ad18277223 | |||
| 661fd21626 | |||
| d8fd231476 | |||
| 8d611a22a2 | |||
| 6cb8b188ec | |||
| d1d0e00959 | |||
| cbc317795e | |||
| ab88c34abc | |||
| 18b0556f7a | |||
| 7bea23005d | |||
| b46cbcc15f | |||
| 71ad15ce25 | |||
| 5a3101a182 | |||
| eba888082a | |||
| 43beffeadd | |||
| 3645906067 | |||
| 0206273200 | |||
| 0a50d15da0 | |||
| 85d98c0b70 |
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"projectName": "community",
|
||||
"projectOwner": "InkCanvasForClass",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "CJKmkp",
|
||||
"name": "CJK_mkp",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/113243675?v=4",
|
||||
"profile": "https://github.com/CJKmkp",
|
||||
"contributions": [
|
||||
"maintenance",
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Hydro11451",
|
||||
"name": "Hydrogen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/214308559?v=4",
|
||||
"profile": "http://hydro11451.qzz.io",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CreeperAWA",
|
||||
"name": "CreeperAWA",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/134939494?v=4",
|
||||
"profile": "https://github.com/CreeperAWA",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "2-2-3-trimethylpentane",
|
||||
"name": "2,2,3-三甲基戊烷",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/141403762?v=4",
|
||||
"profile": "https://github.com/2-2-3-trimethylpentane",
|
||||
"contributions": [
|
||||
"blog",
|
||||
"doc",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Alan-CRL",
|
||||
"name": "Alan-CRL",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/92425617?v=4",
|
||||
"profile": "https://github.com/Alan-CRL",
|
||||
"contributions": [
|
||||
"code",
|
||||
"infra",
|
||||
"doc",
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MKStoler1024",
|
||||
"name": "MKStoler1024",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/158786854?v=4",
|
||||
"profile": "https://github.com/MKStoler1024",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "awesome-iwb",
|
||||
"name": "Awesome Iwb",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/184760810?v=4",
|
||||
"profile": "https://github.com/awesome-iwb",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PrefacedCorg",
|
||||
"name": "PrefacedCorg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4",
|
||||
"profile": "https://github.com/PrefacedCorg",
|
||||
"contributions": [
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Jursin",
|
||||
"name": "Jursin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/127487914?v=4",
|
||||
"profile": "http://blog.jursin.top",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
## Reproduction
|
||||
|
||||
|
||||
## Expected behavior
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
## Additional context
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Bug 报告 | Bug Report
|
||||
description: 反馈软件缺陷或异常 | Report a bug to help us improve
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你的反馈!请详细填写以下内容,便于我们定位问题。
|
||||
Thank you for your feedback! Please fill out the following information to help us locate the issue.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 软件版本 | App Version
|
||||
description: 可在设置中的“关于”界面查看 | You can find it on the "About" interface in the settings
|
||||
placeholder: 例如 v1.2.3 | e.g. v1.2.3
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: 操作系统及版本 | OS & Version
|
||||
placeholder: 例如 Windows 10 22H2 64位 | e.g. Windows 10 22H2 64bit
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 | Description
|
||||
description: 简要描述遇到的问题 | Briefly describe the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: 复现步骤 | Steps to Reproduce
|
||||
description: 如何复现该问题?如有必要可附截图/录屏 | How to reproduce this bug? Screenshots/recordings if needed
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望结果 | Expected Behavior
|
||||
description: 你期望的正确行为或结果 | What did you expect to happen?
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: 其他补充信息 | Additional Info
|
||||
description: 其他相关信息(如日志、配置、特殊环境等)| Any other context, logs, configs, special environment, etc.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
@@ -0,0 +1,37 @@
|
||||
name: 功能请求 | Feature Request
|
||||
description: 提出你对本项目的功能建议 | Suggest an idea for this project
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你的建议!请详细描述你的需求。
|
||||
Thank you for your suggestion! Please describe your needs in detail.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述 | Description
|
||||
description: 请描述你希望添加的功能 | Describe the feature you want
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: 需求动机 | Motivation
|
||||
description: 为什么需要这个功能?| Why do you need this feature?
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: design
|
||||
attributes:
|
||||
label: 期望设计 | Expected Design
|
||||
description: (可选)描述或画出你期望的界面或交互 | (Optional) Describe or sketch the expected UI/UX
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: 其他补充信息 | Additional Info
|
||||
description: 其他补充说明或建议 | Any other context or suggestions
|
||||
validations:
|
||||
required: false
|
||||
+4
-3
@@ -1,3 +1,4 @@
|
||||
/Ink Canvas/obj
|
||||
/Ink Canvas/bin
|
||||
/.vs
|
||||
obj/
|
||||
bin/
|
||||
.vs/
|
||||
/Ink Canvas/obj
|
||||
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../alpha" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,183 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "MW_FloatingBarIcons.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs*",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs*",
|
||||
"ViewState": "AgIAAFgHAAAAAAAAAAAgwGcHAAAIAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-31T10:49:24.719Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeDocumentMoniker": "README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeToolTip": "README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-31T10:48:22.883Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "MainWindow.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow.xaml",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow.xaml",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2025-05-24T13:22:56.715Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "Microsoft.Common.CurrentVersion.targets",
|
||||
"DocumentMoniker": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeDocumentMoniker": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ToolTip": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeToolTip": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ViewState": "AgIAAGsJAAAAAAAAAAAQwIEJAAAEAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003801|",
|
||||
"WhenOpened": "2025-05-24T13:06:01.053Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "MW_PPT.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ViewState": "AgIAAFgAAAAAAAAAAAAUwHQAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-24T13:04:47.205Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeDocumentMoniker": "..\\icc-0610.2.3\\README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeToolTip": "..\\icc-0610.2.3\\README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.407Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "privacy.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeDocumentMoniker": "privacy.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeToolTip": "privacy.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.337Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Manual.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeDocumentMoniker": "Manual.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeToolTip": "Manual.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.986Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "LICENSE",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeDocumentMoniker": "LICENSE",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeToolTip": "LICENSE",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.902Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "Ink Canvas.sln.DotSettings.user",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeDocumentMoniker": "Ink Canvas.sln.DotSettings.user",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeToolTip": "Ink Canvas.sln.DotSettings.user",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003464|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.792Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_floatingbaricons.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:privacy.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Manual.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:LICENSE||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Ink Canvas.sln.DotSettings.user||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow_cs\\mw_ppt.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|c:\\users\\administrator\\desktop\\icc ce\\icc ce 1.2.5\\ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
|
||||
"RelativeMoniker": "D:0:0:{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}|Ink Canvas\\InkCanvasForClass.csproj|solutionrelative:ink canvas\\mainwindow.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets||{FA3CD31E-987B-443A-9B81-186104E8DAC1}|"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "MW_FloatingBarIcons.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_FloatingBarIcons.cs",
|
||||
"ViewState": "AgIAAFgHAAAAAAAAAAAgwGcHAAAIAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-31T10:49:24.719Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeDocumentMoniker": "README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\README.md",
|
||||
"RelativeToolTip": "README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-31T10:48:22.883Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "MainWindow.xaml",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow.xaml",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow.xaml",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow.xaml",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
|
||||
"WhenOpened": "2025-05-24T13:22:56.715Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "Microsoft.Common.CurrentVersion.targets",
|
||||
"DocumentMoniker": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeDocumentMoniker": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ToolTip": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"RelativeToolTip": "..\\..\\..\\..\\..\\Program Files\\Microsoft Visual Studio\\2022\\Community\\MSBuild\\Current\\Bin\\amd64\\Microsoft.Common.CurrentVersion.targets",
|
||||
"ViewState": "AgIAAGsJAAAAAAAAAAAQwIEJAAAEAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003801|",
|
||||
"WhenOpened": "2025-05-24T13:06:01.053Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "MW_PPT.cs",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeDocumentMoniker": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"RelativeToolTip": "Ink Canvas\\MainWindow_cs\\MW_PPT.cs",
|
||||
"ViewState": "AgIAAFgAAAAAAAAAAAAUwHQAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2025-05-24T13:04:47.205Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "README.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeDocumentMoniker": "..\\icc-0610.2.3\\README.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\README.md",
|
||||
"RelativeToolTip": "..\\icc-0610.2.3\\README.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.407Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "privacy.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeDocumentMoniker": "privacy.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\privacy.txt",
|
||||
"RelativeToolTip": "privacy.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:04:01.337Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Manual.md",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeDocumentMoniker": "Manual.md",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Manual.md",
|
||||
"RelativeToolTip": "Manual.md",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.986Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "LICENSE",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeDocumentMoniker": "LICENSE",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\LICENSE",
|
||||
"RelativeToolTip": "LICENSE",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.902Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "Ink Canvas.sln.DotSettings.user",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeDocumentMoniker": "Ink Canvas.sln.DotSettings.user",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\ICC CE 1.2.5\\Ink Canvas.sln.DotSettings.user",
|
||||
"RelativeToolTip": "Ink Canvas.sln.DotSettings.user",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003464|",
|
||||
"WhenOpened": "2025-05-24T13:04:00.792Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,290 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "System.ValueTuple.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:48.138Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "Settings.json",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.878Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": "OSVersionExt.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.837Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "Office.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.774Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "NHotkey.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.718Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "NHotkey.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.662Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "Newtonsoft.Json.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.589Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.932Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "MdXaml.Plugins.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.838Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "MdXaml.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.776Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 10,
|
||||
"Title": "iNKORE.UI.WPF.Modern.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.573Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"Title": "iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.432Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"Title": "iNKORE.UI.WPF.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:42.807Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "InkCanvasForClass.exe.config",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000601|",
|
||||
"WhenOpened": "2025-05-24T13:02:27.288Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"Title": "ICSharpCode.AvalonEdit.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.847Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"Title": "IAWinFX.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.816Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"Title": "IALoader.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.784Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"Title": "IACore.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.753Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"Title": "Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.113Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "icc.png - PNG [256x256, 32 \u4F4D, PNG]",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\icc.png",
|
||||
"RelativeDocumentMoniker": "icc.png",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\icc.png - PNG [256x256, 32 \u4F4D, PNG]",
|
||||
"RelativeToolTip": "icc.png - PNG [256x256, 32 \u4F4D, PNG]",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001533|",
|
||||
"WhenOpened": "2025-05-24T13:03:45.63Z",
|
||||
"EditorCaption": " - PNG [256x256, 32 \u4F4D, PNG]"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "AutomaticUpdateVersionControl.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeDocumentMoniker": "AutomaticUpdateVersionControl.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeToolTip": "AutomaticUpdateVersionControl.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:03:45.517Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": ".gitignore",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\.gitignore",
|
||||
"RelativeDocumentMoniker": ".gitignore",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.2\\.gitignore",
|
||||
"RelativeToolTip": ".gitignore",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:03:43.13Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 3,
|
||||
"Title": "System.ValueTuple.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\System.ValueTuple.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:48.138Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 4,
|
||||
"Title": "Settings.json",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Settings.json",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Settings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.878Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "OSVersionExt.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\OSVersionExt.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.837Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"Title": "Office.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Office.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Office.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.774Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 7,
|
||||
"Title": "NHotkey.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.718Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 8,
|
||||
"Title": "NHotkey.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\NHotkey.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\NHotkey.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.662Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 9,
|
||||
"Title": "Newtonsoft.Json.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Newtonsoft.Json.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:44.589Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 10,
|
||||
"Title": "Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Microsoft.Office.Interop.PowerPoint.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.932Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"Title": "MdXaml.Plugins.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.Plugins.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.838Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"Title": "MdXaml.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\MdXaml.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\MdXaml.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.776Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "iNKORE.UI.WPF.Modern.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.573Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"Title": "iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.Modern.Controls.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:43.432Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"Title": "iNKORE.UI.WPF.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\iNKORE.UI.WPF.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:42.807Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"Title": "InkCanvasForClass.exe.config",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\InkCanvasForClass.exe.config",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000601|",
|
||||
"WhenOpened": "2025-05-24T13:02:27.288Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"Title": "ICSharpCode.AvalonEdit.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\ICSharpCode.AvalonEdit.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.847Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"Title": "IAWinFX.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IAWinFX.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.816Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 19,
|
||||
"Title": "IALoader.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IALoader.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IALoader.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.784Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 20,
|
||||
"Title": "IACore.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\IACore.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\IACore.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.753Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 21,
|
||||
"Title": "Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeDocumentMoniker": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"RelativeToolTip": "..\\..\\InkCanvasForClass\\Hardcodet.NotifyIcon.Wpf.dll",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001697|",
|
||||
"WhenOpened": "2025-05-24T13:02:22.113Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "icc.png",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png",
|
||||
"RelativeDocumentMoniker": "icc.png",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png",
|
||||
"RelativeToolTip": "icc.png",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001533|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.619Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "AutomaticUpdateVersionControl.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeDocumentMoniker": "AutomaticUpdateVersionControl.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeToolTip": "AutomaticUpdateVersionControl.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.575Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": ".gitignore",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeDocumentMoniker": ".gitignore",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeToolTip": ".gitignore",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.025Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:icc.png||{177559E0-D141-11D0-92DF-00A0C9138C45}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AutomaticUpdateVersionControl.txt||{8B382828-6202-11D1-8870-0000F87579D2}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 1,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "icc.png - PNG [1328x1328, 32 \u4F4D, PNG]",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png",
|
||||
"RelativeDocumentMoniker": "icc.png",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\icc.png - PNG [1328x1328, 32 \u4F4D, PNG]",
|
||||
"RelativeToolTip": "icc.png - PNG [1328x1328, 32 \u4F4D, PNG]",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001533|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.619Z",
|
||||
"EditorCaption": " - PNG [1328x1328, 32 \u4F4D, PNG]"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "AutomaticUpdateVersionControl.txt",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeDocumentMoniker": "AutomaticUpdateVersionControl.txt",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\AutomaticUpdateVersionControl.txt",
|
||||
"RelativeToolTip": "AutomaticUpdateVersionControl.txt",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003109|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.575Z"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"Title": ".gitignore",
|
||||
"DocumentMoniker": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeDocumentMoniker": ".gitignore",
|
||||
"ToolTip": "C:\\Users\\Administrator\\Desktop\\ICC CE\\icc-0610.2.3\\.gitignore",
|
||||
"RelativeToolTip": ".gitignore",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
|
||||
"WhenOpened": "2025-05-24T13:12:49.025Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
5.0.1
|
||||
1.7.9.0
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
@@ -1,2 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">WARNING</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe</s:String>
|
||||
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">1114112</s:Int64></wpf:ResourceDictionary>
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -4,7 +4,7 @@
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
StartupUri="MainWindow.xaml">
|
||||
>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style TargetType="ui:ScrollViewerEx">
|
||||
|
||||
+1370
-29
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Windows;
|
||||
[assembly: AssemblyTitle("InkCanvasForClass")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Dubi906w")]
|
||||
[assembly: AssemblyCompany("CJK_mkp")]
|
||||
[assembly: AssemblyProduct("InkCanvasForClass")]
|
||||
[assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("5.0.2.0")]
|
||||
[assembly: AssemblyFileVersion("5.0.2.0")]
|
||||
[assembly: AssemblyVersion("1.7.9.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.9.0")]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura ExcludeAssemblies="IACore|IALoader|IAWinFX" />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,176 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableEventSubscription" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,726 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 改进的异步硬件加速墨迹平滑处理器,使用优化的三次贝塞尔曲线拟合
|
||||
/// </summary>
|
||||
public class AsyncAdvancedBezierSmoothing
|
||||
{
|
||||
private readonly SemaphoreSlim _processingSemaphore;
|
||||
private readonly ConcurrentDictionary<Stroke, CancellationTokenSource> _processingTasks;
|
||||
private readonly Dispatcher _uiDispatcher;
|
||||
|
||||
public AsyncAdvancedBezierSmoothing(Dispatcher uiDispatcher)
|
||||
{
|
||||
_uiDispatcher = uiDispatcher;
|
||||
_processingSemaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount);
|
||||
_processingTasks = new ConcurrentDictionary<Stroke, CancellationTokenSource>();
|
||||
}
|
||||
|
||||
public double SmoothingStrength { get; set; } = 0.4; // 适中的平滑强度
|
||||
public double ResampleInterval { get; set; } = 2.5; // 适中的重采样间隔
|
||||
public int InterpolationSteps { get; set; } = 12; // 增加插值步数提高精度
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||
public bool UseAdaptiveInterpolation { get; set; } = true; // 自适应插值
|
||||
public double CurveTension { get; set; } = 0.3; // 曲线张力参数
|
||||
|
||||
/// <summary>
|
||||
/// 异步平滑笔画
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeAsync(Stroke originalStroke,
|
||||
Action<Stroke, Stroke> onCompleted = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
// 取消之前对同一笔画的处理
|
||||
if (_processingTasks.TryGetValue(originalStroke, out var existingCts))
|
||||
{
|
||||
existingCts.Cancel();
|
||||
_processingTasks.TryRemove(originalStroke, out _);
|
||||
}
|
||||
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
_processingTasks[originalStroke] = cts;
|
||||
|
||||
try
|
||||
{
|
||||
await _processingSemaphore.WaitAsync(cts.Token);
|
||||
|
||||
var smoothedStroke = await Task.Run(() =>
|
||||
ProcessStrokeInternal(originalStroke, cts.Token), cts.Token);
|
||||
|
||||
// 在UI线程上执行回调
|
||||
if (onCompleted != null && !cts.Token.IsCancellationRequested)
|
||||
{
|
||||
await _uiDispatcher.InvokeAsync(() => onCompleted(originalStroke, smoothedStroke));
|
||||
}
|
||||
|
||||
return smoothedStroke;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_processingSemaphore.Release();
|
||||
_processingTasks.TryRemove(originalStroke, out _);
|
||||
cts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private Stroke ProcessStrokeInternal(Stroke stroke, CancellationToken cancellationToken)
|
||||
{
|
||||
var originalPoints = stroke.StylusPoints.ToArray();
|
||||
|
||||
// 如果点数太少,直接返回原始笔画
|
||||
if (originalPoints.Length < 3)
|
||||
return stroke;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 使用改进的贝塞尔曲线拟合
|
||||
var smoothedPoints = ApplyImprovedBezierSmoothing(originalPoints);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 严格控制点数,避免产生过多点
|
||||
if (smoothedPoints.Length > originalPoints.Length * 2)
|
||||
{
|
||||
// 如果点数增加太多,进行重采样
|
||||
smoothedPoints = ResampleEquidistantOptimized(smoothedPoints, ResampleInterval);
|
||||
}
|
||||
|
||||
// 最终检查:确保点数不会过多
|
||||
if (smoothedPoints.Length > originalPoints.Length * 1.5)
|
||||
{
|
||||
// 如果仍然太多点,使用原始笔画
|
||||
return stroke;
|
||||
}
|
||||
|
||||
// 创建平滑后的笔画
|
||||
var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||
};
|
||||
|
||||
return smoothedStroke;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 改进的贝塞尔曲线平滑处理
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyImprovedBezierSmoothing(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 4) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
|
||||
// 添加第一个点
|
||||
result.Add(points[0]);
|
||||
|
||||
// 使用非重叠的窗口进行贝塞尔曲线拟合
|
||||
for (int i = 0; i < points.Length - 3; i += 3) // 每次移动3个点,避免重叠
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[Math.Min(i + 1, points.Length - 1)];
|
||||
var p2 = points[Math.Min(i + 2, points.Length - 1)];
|
||||
var p3 = points[Math.Min(i + 3, points.Length - 1)];
|
||||
|
||||
// 计算改进的控制点
|
||||
var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3);
|
||||
|
||||
// 限制插值步数,避免点数爆炸
|
||||
int steps = Math.Min(UseAdaptiveInterpolation ?
|
||||
CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps, 16);
|
||||
|
||||
// 生成贝塞尔曲线点,但跳过第一个点避免重复
|
||||
for (int j = 1; j <= steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
var bezierPoint = CubicBezierWithControlPoints(controlPoints, t, p0, p3);
|
||||
result.Add(bezierPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加最后一个点
|
||||
result.Add(points[points.Length - 1]);
|
||||
|
||||
// 去重和优化点数
|
||||
return RemoveDuplicatePoints(result.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算改进的控制点
|
||||
/// </summary>
|
||||
private (Point cp1, Point cp2) CalculateImprovedControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 计算切线方向
|
||||
var tangent1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var tangent2 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||
|
||||
// 归一化切线
|
||||
if (tangent1.Length > 0) tangent1.Normalize();
|
||||
if (tangent2.Length > 0) tangent2.Normalize();
|
||||
|
||||
// 计算控制点距离(基于点间距离)
|
||||
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||
double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
|
||||
|
||||
double controlDist1 = dist1 * CurveTension;
|
||||
double controlDist2 = dist2 * CurveTension;
|
||||
|
||||
// 计算控制点
|
||||
var cp1 = new Point(
|
||||
p1.X + tangent1.X * controlDist1,
|
||||
p1.Y + tangent1.Y * controlDist1
|
||||
);
|
||||
|
||||
var cp2 = new Point(
|
||||
p2.X - tangent2.X * controlDist2,
|
||||
p2.Y - tangent2.Y * controlDist2
|
||||
);
|
||||
|
||||
return (cp1, cp2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自适应插值步数计算
|
||||
/// </summary>
|
||||
private int CalculateAdaptiveSteps(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 基于曲线长度和复杂度计算步数
|
||||
double totalLength = 0;
|
||||
totalLength += Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||
totalLength += Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y));
|
||||
totalLength += Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
|
||||
|
||||
// 计算曲率(简化版本)
|
||||
double curvature = CalculateCurvature(p0, p1, p2, p3);
|
||||
|
||||
// 基于长度和曲率计算步数
|
||||
int baseSteps = Math.Max(8, Math.Min(20, (int)(totalLength / 10)));
|
||||
int curvatureSteps = (int)(curvature * 10);
|
||||
|
||||
return Math.Max(InterpolationSteps, Math.Min(24, baseSteps + curvatureSteps));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算曲率(简化版本)
|
||||
/// </summary>
|
||||
private double CalculateCurvature(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 计算三个向量的角度变化
|
||||
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||
var v3 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||
|
||||
if (v1.Length == 0 || v2.Length == 0 || v3.Length == 0) return 0;
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
v3.Normalize();
|
||||
|
||||
// 计算角度变化
|
||||
double angle1 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v1, v2))));
|
||||
double angle2 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v2, v3))));
|
||||
|
||||
return (angle1 + angle2) / Math.PI; // 归一化到0-1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除重复和过近的点
|
||||
/// </summary>
|
||||
private StylusPoint[] RemoveDuplicatePoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 2) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
double minDistance = ResampleInterval * 0.5; // 最小距离阈值
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var lastPoint = result[result.Count - 1];
|
||||
var currentPoint = points[i];
|
||||
|
||||
// 计算距离
|
||||
double distance = Math.Sqrt(
|
||||
(currentPoint.X - lastPoint.X) * (currentPoint.X - lastPoint.X) +
|
||||
(currentPoint.Y - lastPoint.Y) * (currentPoint.Y - lastPoint.Y));
|
||||
|
||||
// 如果距离足够大,添加这个点
|
||||
if (distance >= minDistance)
|
||||
{
|
||||
result.Add(currentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用控制点的三次贝塞尔曲线计算
|
||||
/// </summary>
|
||||
private StylusPoint CubicBezierWithControlPoints((Point cp1, Point cp2) controlPoints, double t, StylusPoint p0, StylusPoint p3)
|
||||
{
|
||||
var p1 = controlPoints.cp1;
|
||||
var p2 = controlPoints.cp2;
|
||||
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
// 预计算系数
|
||||
double c0 = uuu;
|
||||
double c1 = 3 * uu * t;
|
||||
double c2 = 3 * u * tt;
|
||||
double c3 = ttt;
|
||||
|
||||
double x = c0 * p0.X + c1 * p1.X + c2 * p2.X + c3 * p3.X;
|
||||
double y = c0 * p0.Y + c1 * p1.Y + c2 * p2.Y + c3 * p3.Y;
|
||||
|
||||
// 插值压力值
|
||||
float pressure = (float)(p0.PressureFactor * u + p3.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
return new StylusPoint(x, y, pressure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 硬件加速的向量化指数平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyExponentialSmoothingVectorized(StylusPoint[] points, double alpha)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
var result = new StylusPoint[points.Length];
|
||||
result[0] = points[0];
|
||||
|
||||
double lastX = points[0].X;
|
||||
double lastY = points[0].Y;
|
||||
float lastPressure = points[0].PressureFactor;
|
||||
double oneMinusAlpha = 1.0 - alpha;
|
||||
|
||||
// 向量化处理,减少分支预测失败
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var p = points[i];
|
||||
lastX = alpha * p.X + oneMinusAlpha * lastX;
|
||||
lastY = alpha * p.Y + oneMinusAlpha * lastY;
|
||||
lastPressure = (float)(alpha * p.PressureFactor + oneMinusAlpha * lastPressure);
|
||||
lastPressure = Math.Max(lastPressure, 0.1f); // 避免分支
|
||||
result[i] = new StylusPoint(lastX, lastY, lastPressure);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的等距重采样
|
||||
/// </summary>
|
||||
private StylusPoint[] ResampleEquidistantOptimized(StylusPoint[] points, double interval)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
var result = new List<StylusPoint>(points.Length) { points[0] };
|
||||
double accumulated = 0;
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
double dx = curr.X - prev.X;
|
||||
double dy = curr.Y - prev.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist + accumulated >= interval)
|
||||
{
|
||||
double t = (interval - accumulated) / dist;
|
||||
double x = prev.X + t * dx;
|
||||
double y = prev.Y + t * dy;
|
||||
float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
result.Add(new StylusPoint(x, y, pressure));
|
||||
accumulated = 0;
|
||||
i--; // 重新处理当前点
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulated += dist;
|
||||
}
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 硬件加速的贝塞尔曲线拟合
|
||||
/// </summary>
|
||||
private StylusPoint[] SlidingBezierFitHardwareAccelerated(StylusPoint[] points, int window, int steps)
|
||||
{
|
||||
if (points.Length < window) return points;
|
||||
|
||||
var result = new List<StylusPoint>(points.Length * steps / window);
|
||||
|
||||
// 使用并行处理加速计算
|
||||
var segments = new List<StylusPoint[]>();
|
||||
|
||||
Parallel.For(0, points.Length - window + 1, i =>
|
||||
{
|
||||
var segmentPoints = new StylusPoint[steps];
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
|
||||
for (int j = 0; j < steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
segmentPoints[j] = CubicBezierOptimized(p0, p1, p2, p3, t);
|
||||
}
|
||||
|
||||
lock (segments)
|
||||
{
|
||||
segments.Add(segmentPoints);
|
||||
}
|
||||
});
|
||||
|
||||
// 合并结果
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
result.AddRange(segment);
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的单线程贝塞尔拟合
|
||||
/// </summary>
|
||||
private StylusPoint[] SlidingBezierFitOptimized(StylusPoint[] points, int window, int steps)
|
||||
{
|
||||
if (points.Length < window) return points;
|
||||
|
||||
var result = new List<StylusPoint>(points.Length * steps / window);
|
||||
|
||||
for (int i = 0; i <= points.Length - window; i++)
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
|
||||
for (int j = 0; j < steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
result.Add(CubicBezierOptimized(p0, p1, p2, p3, t));
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的三次贝塞尔曲线计算
|
||||
/// </summary>
|
||||
private StylusPoint CubicBezierOptimized(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
// 预计算系数
|
||||
double c0 = uuu;
|
||||
double c1 = 3 * uu * t;
|
||||
double c2 = 3 * u * tt;
|
||||
double c3 = ttt;
|
||||
|
||||
double x = c0 * p0.X + c1 * p1.X + c2 * p2.X + c3 * p3.X;
|
||||
double y = c0 * p0.Y + c1 * p1.Y + c2 * p2.Y + c3 * p3.Y;
|
||||
float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
return new StylusPoint(x, y, pressure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 兼容性方法:传统指数平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyExponentialSmoothing(StylusPoint[] points, double alpha)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
var result = new StylusPoint[points.Length];
|
||||
result[0] = points[0];
|
||||
|
||||
double lastX = points[0].X;
|
||||
double lastY = points[0].Y;
|
||||
float lastPressure = points[0].PressureFactor;
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var p = points[i];
|
||||
lastX = alpha * p.X + (1 - alpha) * lastX;
|
||||
lastY = alpha * p.Y + (1 - alpha) * lastY;
|
||||
lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure);
|
||||
lastPressure = Math.Max(lastPressure, 0.1f);
|
||||
result[i] = new StylusPoint(lastX, lastY, lastPressure);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有正在进行的处理任务
|
||||
/// </summary>
|
||||
public void CancelAllTasks()
|
||||
{
|
||||
foreach (var kvp in _processingTasks)
|
||||
{
|
||||
kvp.Value.Cancel();
|
||||
}
|
||||
_processingTasks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
CancelAllTasks();
|
||||
_processingSemaphore?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 原有的同步版本(保持向后兼容)
|
||||
/// </summary>
|
||||
public class AdvancedBezierSmoothing
|
||||
{
|
||||
public double SmoothingStrength { get; set; } = 0.3;
|
||||
public double ResampleInterval { get; set; } = 3.0;
|
||||
public int InterpolationSteps { get; set; } = 8;
|
||||
|
||||
public Stroke SmoothStroke(Stroke stroke)
|
||||
{
|
||||
if (stroke == null || stroke.StylusPoints.Count < 3)
|
||||
return stroke;
|
||||
|
||||
var originalPoints = stroke.StylusPoints.ToList();
|
||||
|
||||
// 简化处理:只进行轻度平滑
|
||||
var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑
|
||||
|
||||
// 检查点数是否合理
|
||||
if (smoothedPoints.Count > originalPoints.Count * 1.5)
|
||||
{
|
||||
return stroke; // 如果点数增加太多,返回原始笔画
|
||||
}
|
||||
|
||||
var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||
};
|
||||
return smoothedStroke;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轻度指数平滑
|
||||
/// </summary>
|
||||
private List<StylusPoint> ApplyLightExponentialSmoothing(List<StylusPoint> points, double alpha)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
if (points.Count == 0) return result;
|
||||
|
||||
result.Add(points[0]);
|
||||
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
|
||||
double x = alpha * curr.X + (1 - alpha) * prev.X;
|
||||
double y = alpha * curr.Y + (1 - alpha) * prev.Y;
|
||||
float pressure = (float)(alpha * curr.PressureFactor + (1 - alpha) * prev.PressureFactor);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
result.Add(new StylusPoint(x, y, pressure));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<StylusPoint> ApplyExponentialSmoothing(List<StylusPoint> points, double alpha)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
if (points.Count == 0) return result;
|
||||
result.Add(points[0]);
|
||||
double lastX = points[0].X;
|
||||
double lastY = points[0].Y;
|
||||
float lastPressure = points[0].PressureFactor;
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
var p = points[i];
|
||||
lastX = alpha * p.X + (1 - alpha) * lastX;
|
||||
lastY = alpha * p.Y + (1 - alpha) * lastY;
|
||||
lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure);
|
||||
if (lastPressure < 0.1f) lastPressure = 0.1f;
|
||||
result.Add(new StylusPoint(lastX, lastY, lastPressure));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<StylusPoint> ResampleEquidistant(List<StylusPoint> points, double interval = 2.0)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
if (points.Count == 0) return result;
|
||||
result.Add(points[0]);
|
||||
double accumulated = 0;
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
var prev = result.Last();
|
||||
var curr = points[i];
|
||||
double dx = curr.X - prev.X;
|
||||
double dy = curr.Y - prev.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
if (dist + accumulated >= interval)
|
||||
{
|
||||
double t = (interval - accumulated) / dist;
|
||||
double x = prev.X + t * dx;
|
||||
double y = prev.Y + t * dy;
|
||||
float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t);
|
||||
if (pressure < 0.1f) pressure = 0.1f;
|
||||
var newPoint = new StylusPoint(x, y, pressure);
|
||||
result.Add(newPoint);
|
||||
accumulated = 0;
|
||||
i--; // 重新处理当前点
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulated += dist;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<StylusPoint> SlidingBezierFit(List<StylusPoint> points, int window = 4, int steps = 48) // 从24增加到48
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
if (points.Count < window) return points;
|
||||
for (int i = 0; i <= points.Count - window; i++)
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
for (int j = 0; j < steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
var pt = CubicBezier(p0, p1, p2, p3, t);
|
||||
result.Add(pt);
|
||||
}
|
||||
}
|
||||
// 保证最后一个点被包含
|
||||
result.Add(points.Last());
|
||||
return result;
|
||||
}
|
||||
|
||||
private StylusPoint CubicBezier(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X;
|
||||
double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y;
|
||||
float pressure = (float)(p1.PressureFactor * (1 - t) + p2.PressureFactor * t);
|
||||
if (pressure < 0.1f) pressure = 0.1f;
|
||||
return new StylusPoint(x, y, pressure);
|
||||
}
|
||||
|
||||
private List<StylusPoint> SlidingWindowSmooth(List<StylusPoint> points, int window = 5)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
int half = window / 2;
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
{
|
||||
double sumX = 0, sumY = 0, sumP = 0;
|
||||
int count = 0;
|
||||
for (int j = Math.Max(0, i - half); j <= Math.Min(points.Count - 1, i + half); j++)
|
||||
{
|
||||
sumX += points[j].X;
|
||||
sumY += points[j].Y;
|
||||
sumP += points[j].PressureFactor;
|
||||
count++;
|
||||
}
|
||||
result.Add(new StylusPoint(sumX / count, sumY / count, (float)(sumP / count)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 性能监控器
|
||||
/// </summary>
|
||||
public class InkSmoothingPerformanceMonitor
|
||||
{
|
||||
private readonly Queue<TimeSpan> _processingTimes = new Queue<TimeSpan>();
|
||||
private readonly object _lock = new object();
|
||||
private const int MaxSamples = 100;
|
||||
|
||||
public void RecordProcessingTime(TimeSpan time)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_processingTimes.Enqueue(time);
|
||||
if (_processingTimes.Count > MaxSamples)
|
||||
_processingTimes.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public double GetAverageProcessingTimeMs()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _processingTimes.Count > 0 ?
|
||||
_processingTimes.Average(t => t.TotalMilliseconds) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public double GetMaxProcessingTimeMs()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _processingTimes.Count > 0 ?
|
||||
_processingTimes.Max(t => t.TotalMilliseconds) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSampleCount()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _processingTimes.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,23 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 防止窗口进入全屏状态的辅助类
|
||||
/// </summary>
|
||||
public static partial class AvoidFullScreenHelper
|
||||
public static class AvoidFullScreenHelper
|
||||
{
|
||||
private static readonly DependencyProperty IsAvoidFullScreenEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsAvoidFullScreenEnabled",
|
||||
typeof(bool),
|
||||
"IsAvoidFullScreenEnabled",
|
||||
typeof(bool),
|
||||
typeof(AvoidFullScreenHelper));
|
||||
|
||||
private static bool _isBoardMode = false;
|
||||
private static bool _isBoardMode;
|
||||
public static void SetBoardMode(bool isBoardMode)
|
||||
{
|
||||
_isBoardMode = isBoardMode;
|
||||
@@ -121,20 +121,20 @@ namespace Ink_Canvas.Helpers
|
||||
private static Rect GetWorkingArea(Rect windowRect)
|
||||
{
|
||||
// 获取所有显示器
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
|
||||
var screens = Screen.AllScreens;
|
||||
|
||||
// 确定窗口主要位于哪个显示器上
|
||||
System.Windows.Forms.Screen targetScreen = null;
|
||||
Screen targetScreen = null;
|
||||
double maxIntersection = 0;
|
||||
|
||||
|
||||
foreach (var screen in screens)
|
||||
{
|
||||
var screenRect = new Rect(
|
||||
screen.WorkingArea.X,
|
||||
screen.WorkingArea.Y,
|
||||
screen.WorkingArea.Width,
|
||||
screen.WorkingArea.X,
|
||||
screen.WorkingArea.Y,
|
||||
screen.WorkingArea.Width,
|
||||
screen.WorkingArea.Height);
|
||||
|
||||
|
||||
var intersection = Rect.Intersect(windowRect, screenRect);
|
||||
if (intersection.Width * intersection.Height > maxIntersection)
|
||||
{
|
||||
@@ -142,11 +142,11 @@ namespace Ink_Canvas.Helpers
|
||||
targetScreen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果没找到,使用主显示器
|
||||
if (targetScreen == null)
|
||||
targetScreen = System.Windows.Forms.Screen.PrimaryScreen;
|
||||
|
||||
targetScreen = Screen.PrimaryScreen;
|
||||
|
||||
return new Rect(
|
||||
targetScreen.WorkingArea.X,
|
||||
targetScreen.WorkingArea.Y,
|
||||
@@ -159,21 +159,21 @@ namespace Ink_Canvas.Helpers
|
||||
// 调整尺寸以适应工作区域
|
||||
if (windowRect.Width > workingArea.Width)
|
||||
windowRect.Width = workingArea.Width;
|
||||
|
||||
|
||||
if (windowRect.Height > workingArea.Height)
|
||||
windowRect.Height = workingArea.Height;
|
||||
|
||||
|
||||
// 调整位置以确保窗口完全在工作区域内
|
||||
if (windowRect.Left < workingArea.Left)
|
||||
windowRect.X = workingArea.Left;
|
||||
else if (windowRect.Right > workingArea.Right)
|
||||
windowRect.X = workingArea.Right - windowRect.Width;
|
||||
|
||||
|
||||
if (windowRect.Top < workingArea.Top)
|
||||
windowRect.Y = workingArea.Top;
|
||||
else if (windowRect.Bottom > workingArea.Bottom)
|
||||
windowRect.Y = workingArea.Bottom - windowRect.Height;
|
||||
|
||||
|
||||
return windowRect;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,125 +9,107 @@ namespace Ink_Canvas.Converter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value == true)
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value == true)
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
public class VisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "人";
|
||||
}
|
||||
|
||||
return ((double)value) + "人";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "人";
|
||||
}
|
||||
|
||||
return ((double)value) + "人";
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString2 : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "条";
|
||||
}
|
||||
|
||||
return ((double)value) + "条";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((double)value).ToString() + "条";
|
||||
}
|
||||
|
||||
return ((double)value) + "条";
|
||||
}
|
||||
}
|
||||
|
||||
public class IsEnabledToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool isChecked = (bool)value;
|
||||
if (isChecked == true)
|
||||
if (isChecked)
|
||||
{
|
||||
return 1d;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.35;
|
||||
}
|
||||
|
||||
return 0.35;
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); }
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class DelAutoSavedFiles {
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold) {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class DelAutoSavedFiles
|
||||
{
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold)
|
||||
{
|
||||
string[] extensionsToDel = { ".icstk", ".png" };
|
||||
if (Directory.Exists(directoryPath)) {
|
||||
if (Directory.Exists(directoryPath))
|
||||
{
|
||||
// 获取目录中的所有子目录
|
||||
string[] subDirectories = Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories);
|
||||
foreach (string subDirectory in subDirectories) {
|
||||
try {
|
||||
foreach (string subDirectory in subDirectories)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取子目录下的所有文件
|
||||
string[] files = Directory.GetFiles(subDirectory);
|
||||
foreach (string filePath in files) {
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
// 获取文件的创建日期
|
||||
DateTime creationDate = File.GetCreationTime(filePath);
|
||||
// 获取文件的扩展名
|
||||
string fileExtension = Path.GetExtension(filePath);
|
||||
// 如果文件的创建日期早于指定天数且是要删除的扩展名,则删除文件
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold)) {
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold))
|
||||
{
|
||||
if (Array.Exists(extensionsToDel, ext => ext.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase)) {
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex, LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
try { // 递归删除空文件夹
|
||||
try
|
||||
{ // 递归删除空文件夹
|
||||
DeleteEmptyFolders(directoryPath);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex, LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteEmptyFolders(string directoryPath) {
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath)) {
|
||||
private static void DeleteEmptyFolders(string directoryPath)
|
||||
{
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath))
|
||||
{
|
||||
DeleteEmptyFolders(dir);
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0) {
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0)
|
||||
{
|
||||
Directory.Delete(dir, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -18,10 +14,13 @@ namespace Ink_Canvas.Helpers
|
||||
/// <param name="inv">同步的對象,一般傳入控件,不需要可null</param>
|
||||
public void DebounceAction(int timeMs, ISynchronizeInvoke inv, Action action)
|
||||
{
|
||||
lock (this) {
|
||||
if (_timerDebounce == null) {
|
||||
lock (this)
|
||||
{
|
||||
if (_timerDebounce == null)
|
||||
{
|
||||
_timerDebounce = new Timer(timeMs) { AutoReset = false };
|
||||
_timerDebounce.Elapsed += (o, e) => {
|
||||
_timerDebounce.Elapsed += (o, e) =>
|
||||
{
|
||||
_timerDebounce.Stop(); _timerDebounce.Close(); _timerDebounce = null;
|
||||
InvokeAction(action, inv);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualBasic;
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -83,7 +71,7 @@ namespace Ink_Canvas.Helpers
|
||||
[FieldOffset(8)]
|
||||
private DateTime date;
|
||||
[FieldOffset(8)]
|
||||
private System.Runtime.InteropServices.ComTypes.FILETIME filetime;
|
||||
private FILETIME filetime;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private Blob blobVal;
|
||||
@@ -128,7 +116,7 @@ namespace Ink_Canvas.Helpers
|
||||
case VarEnum.VT_BLOB:
|
||||
return GetBlob();
|
||||
}
|
||||
throw new NotImplementedException("PropVariant " + ve.ToString());
|
||||
throw new NotImplementedException("PropVariant " + ve);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,17 +145,17 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region "Interfaces"
|
||||
|
||||
[ComImport(), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IPropertyStore
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetCount([Out(), In()] ref uint cProps);
|
||||
void GetCount([Out, In] ref uint cProps);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetAt([In()] uint iProp, ref PropertyKey pkey);
|
||||
void GetAt([In] uint iProp, ref PropertyKey pkey);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetValue([In()] ref PropertyKey key, ref PropVariant pv);
|
||||
void GetValue([In] ref PropertyKey key, ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetValue([In()] ref PropertyKey key, [In()] ref PropVariant pv);
|
||||
void SetValue([In] ref PropertyKey key, [In] ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Commit();
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -24,7 +25,8 @@ namespace Ink_Canvas.Helpers
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT {
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
@@ -34,7 +36,8 @@ namespace Ink_Canvas.Helpers
|
||||
public int Height => Bottom - Top;
|
||||
}
|
||||
|
||||
public static string WindowTitle() {
|
||||
public static string WindowTitle()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
const int nChars = 256;
|
||||
@@ -44,7 +47,8 @@ namespace Ink_Canvas.Helpers
|
||||
return windowTitle.ToString();
|
||||
}
|
||||
|
||||
public static string WindowClassName() {
|
||||
public static string WindowClassName()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
const int nChars = 256;
|
||||
@@ -54,7 +58,8 @@ namespace Ink_Canvas.Helpers
|
||||
return className.ToString();
|
||||
}
|
||||
|
||||
public static RECT WindowRect() {
|
||||
public static RECT WindowRect()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
RECT windowRect;
|
||||
@@ -63,15 +68,19 @@ namespace Ink_Canvas.Helpers
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
public static string ProcessName() {
|
||||
public static string ProcessName()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
uint processId;
|
||||
GetWindowThreadProcessId(foregroundWindowHandle, out processId);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
return process.ProcessName;
|
||||
} catch (ArgumentException) {
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Process with the given ID not found
|
||||
return "Unknown";
|
||||
}
|
||||
@@ -88,10 +97,21 @@ namespace Ink_Canvas.Helpers
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
return process.MainModule.FileName;
|
||||
}
|
||||
catch {
|
||||
catch
|
||||
{
|
||||
// Process with the given ID not found
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetTaskbarHeight(Screen screen, double dpiScaleY)
|
||||
{
|
||||
// 获取工作区和屏幕高度的差值
|
||||
var workingArea = screen.WorkingArea;
|
||||
var bounds = screen.Bounds;
|
||||
int taskbarHeight = bounds.Height - workingArea.Height;
|
||||
// 考虑 DPI 缩放
|
||||
return taskbarHeight / dpiScaleY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
// 由衷感謝 lindexi 提供的 《WPF 稳定的全屏化窗口方法》
|
||||
// 文章鏈接:https://blog.lindexi.com/post/WPF-%E7%A8%B3%E5%AE%9A%E7%9A%84%E5%85%A8%E5%B1%8F%E5%8C%96%E7%AA%97%E5%8F%A3%E6%96%B9%E6%B3%95.html
|
||||
@@ -83,7 +81,7 @@ namespace Ink_Canvas.Helpers
|
||||
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
public static IntPtr GetWindowLongPtr(IntPtr hWnd, GetWindowLongFields nIndex) =>
|
||||
GetWindowLongPtr(hWnd, (int) nIndex);
|
||||
GetWindowLongPtr(hWnd, (int)nIndex);
|
||||
|
||||
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
|
||||
{
|
||||
@@ -101,7 +99,7 @@ namespace Ink_Canvas.Helpers
|
||||
public static extern IntPtr GetWindowLongPtr_x64(IntPtr hWnd, int nIndex);
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, GetWindowLongFields nIndex, IntPtr dwNewLong) =>
|
||||
SetWindowLongPtr(hWnd, (int) nIndex, dwNewLong);
|
||||
SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong);
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
@@ -266,8 +264,8 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
get { return unchecked((int) (Right - Left)); }
|
||||
set { Right = unchecked((int) (Left + value)); }
|
||||
get { return unchecked(Right - Left); }
|
||||
set { Right = unchecked(Left + value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -275,8 +273,8 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
get { return unchecked((int) (Bottom - Top)); }
|
||||
set { Bottom = unchecked((int) (Top + value)); }
|
||||
get { return unchecked(Bottom - Top); }
|
||||
set { Bottom = unchecked(Top + value); }
|
||||
}
|
||||
|
||||
public bool Equals(Rectangle other)
|
||||
@@ -298,10 +296,10 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int) Left;
|
||||
hashCode = (hashCode * 397) ^ (int) Top;
|
||||
hashCode = (hashCode * 397) ^ (int) Right;
|
||||
hashCode = (hashCode * 397) ^ (int) Bottom;
|
||||
var hashCode = Left;
|
||||
hashCode = (hashCode * 397) ^ Top;
|
||||
hashCode = (hashCode * 397) ^ Right;
|
||||
hashCode = (hashCode * 397) ^ Bottom;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +67,12 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
//获取当前窗口的位置大小状态并保存
|
||||
var placement = new WINDOWPLACEMENT();
|
||||
placement.Size = (uint) Marshal.SizeOf(placement);
|
||||
placement.Size = (uint)Marshal.SizeOf(placement);
|
||||
Win32.User32.GetWindowPlacement(hwnd, ref placement);
|
||||
window.SetValue(BeforeFullScreenWindowPlacementProperty, placement);
|
||||
|
||||
//修改窗口样式
|
||||
var style = (WindowStyles) Win32.User32.GetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE);
|
||||
var style = (WindowStyles)Win32.User32.GetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE);
|
||||
window.SetValue(BeforeFullScreenWindowStyleProperty, style);
|
||||
//将窗口恢复到还原模式,在有标题栏的情况下最大化模式下无法全屏,
|
||||
//这里采用还原,不修改标题栏的方式
|
||||
@@ -81,7 +81,7 @@ namespace Ink_Canvas.Helpers
|
||||
//去掉WS_MAXIMIZEBOX,禁用最大化,如果最大化会退出全屏
|
||||
//去掉WS_MAXIMIZE,使窗口变成还原状态,不使用ShowWindow(hwnd, ShowWindowCommands.SW_RESTORE),避免看到窗口变成还原状态这一过程(也避免影响窗口的Visible状态)
|
||||
style &= (~(WindowStyles.WS_THICKFRAME | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_MAXIMIZE));
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE, (IntPtr) style);
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE, (IntPtr)style);
|
||||
|
||||
//禁用 DWM 过渡动画 忽略返回值,若DWM关闭不做处理
|
||||
Win32.Dwmapi.DwmSetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 1,
|
||||
@@ -95,8 +95,8 @@ namespace Ink_Canvas.Helpers
|
||||
//不能用 placement 的坐标,placement是工作区坐标,不是屏幕坐标。
|
||||
|
||||
//使用窗口当前的矩形调用下设置窗口位置和尺寸的方法,让Hook来进行调整窗口位置和尺寸到全屏模式
|
||||
Win32.User32.SetWindowPos(hwnd, (IntPtr) HwndZOrder.HWND_TOPMOST, rect.Left, rect.Top, rect.Width,
|
||||
rect.Height, (int) WindowPositionFlags.SWP_NOZORDER);
|
||||
Win32.User32.SetWindowPos(hwnd, (IntPtr)HwndZOrder.HWND_TOPMOST, rect.Left, rect.Top, rect.Width,
|
||||
rect.Height, (int)WindowPositionFlags.SWP_NOZORDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ namespace Ink_Canvas.Helpers
|
||||
//不要改变Style里的WS_MAXIMIZE,否则会使窗口变成最大化状态,但是尺寸不对
|
||||
//也不要设置回Style里的WS_MINIMIZE,否则会导致窗口最小化按钮显示成还原按钮
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE,
|
||||
(IntPtr) (style & (~(WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE))));
|
||||
(IntPtr)(style & (~(WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE))));
|
||||
|
||||
if ((style & WindowStyles.WS_MINIMIZE) != 0)
|
||||
{
|
||||
@@ -201,7 +201,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
//得到WINDOWPOS结构体
|
||||
var pos = (WindowPosition) Marshal.PtrToStructure(lParam, typeof(WindowPosition));
|
||||
var pos = (WindowPosition)Marshal.PtrToStructure(lParam, typeof(WindowPosition));
|
||||
|
||||
if ((pos.Flags & WindowPositionFlags.SWP_NOMOVE) != 0 &&
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOSIZE) != 0)
|
||||
@@ -245,7 +245,7 @@ namespace Ink_Canvas.Helpers
|
||||
//使用目标矩形获取显示器信息
|
||||
var monitor = Win32.User32.MonitorFromRect(targetRect, MonitorFlag.MONITOR_DEFAULTTOPRIMARY);
|
||||
var info = new MonitorInfo();
|
||||
info.Size = (uint) Marshal.SizeOf(info);
|
||||
info.Size = (uint)Marshal.SizeOf(info);
|
||||
if (Win32.User32.GetMonitorInfo(monitor, ref info))
|
||||
{
|
||||
//基于显示器信息设置窗口尺寸位置
|
||||
@@ -278,10 +278,7 @@ namespace Ink_Canvas.Helpers
|
||||
window.Width = logicalSize.X;
|
||||
window.Height = logicalSize.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
//这个hwnd是前面从Window来的,如果现在他不是Window...... 你信么
|
||||
}
|
||||
//这个hwnd是前面从Window来的,如果现在他不是Window...... 你信么
|
||||
}
|
||||
|
||||
//将修改后的结构体拷贝回去
|
||||
|
||||
@@ -0,0 +1,864 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using Newtonsoft.Json;
|
||||
using NHotkey.Wpf;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 全局快捷键管理器 - 使用NHotkey库实现全局快捷键功能
|
||||
/// </summary>
|
||||
public class GlobalHotkeyManager : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
private readonly Dictionary<string, HotkeyInfo> _registeredHotkeys;
|
||||
private readonly MainWindow _mainWindow;
|
||||
private bool _isDisposed;
|
||||
private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键
|
||||
|
||||
// 配置文件路径
|
||||
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "HotkeyConfig.json");
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public GlobalHotkeyManager(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
_registeredHotkeys = new Dictionary<string, HotkeyInfo>();
|
||||
_hotkeysShouldBeRegistered = true; // 启动时注册热键
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 注册全局快捷键
|
||||
/// </summary>
|
||||
/// <param name="hotkeyName">快捷键名称</param>
|
||||
/// <param name="key">按键</param>
|
||||
/// <param name="modifiers">修饰键</param>
|
||||
/// <param name="action">执行动作</param>
|
||||
/// <returns>是否注册成功</returns>
|
||||
public bool RegisterHotkey(string hotkeyName, Key key, ModifierKeys modifiers, Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isDisposed)
|
||||
return false;
|
||||
|
||||
// 如果快捷键已存在,先注销
|
||||
if (_registeredHotkeys.ContainsKey(hotkeyName))
|
||||
{
|
||||
UnregisterHotkey(hotkeyName);
|
||||
}
|
||||
|
||||
// 创建快捷键信息
|
||||
var hotkeyInfo = new HotkeyInfo
|
||||
{
|
||||
Name = hotkeyName,
|
||||
Key = key,
|
||||
Modifiers = modifiers,
|
||||
Action = action
|
||||
};
|
||||
|
||||
// 注册快捷键
|
||||
HotkeyManager.Current.AddOrReplace(hotkeyName, key, modifiers, (sender, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保在主线程中执行
|
||||
_mainWindow.Dispatcher.Invoke(() =>
|
||||
{
|
||||
action?.Invoke();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行快捷键 {hotkeyName} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
_registeredHotkeys[hotkeyName] = hotkeyInfo;
|
||||
// 成功注册全局快捷键
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册全局快捷键 {hotkeyName} 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销指定快捷键
|
||||
/// </summary>
|
||||
/// <param name="hotkeyName">快捷键名称</param>
|
||||
/// <returns>是否注销成功</returns>
|
||||
public bool UnregisterHotkey(string hotkeyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isDisposed || !_registeredHotkeys.ContainsKey(hotkeyName))
|
||||
return false;
|
||||
|
||||
HotkeyManager.Current.Remove(hotkeyName);
|
||||
_registeredHotkeys.Remove(hotkeyName);
|
||||
// 成功注销全局快捷键
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注销全局快捷键 {hotkeyName} 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销所有快捷键
|
||||
/// </summary>
|
||||
public void UnregisterAllHotkeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
foreach (var hotkeyName in _registeredHotkeys.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
HotkeyManager.Current.Remove(hotkeyName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注销快捷键 {hotkeyName} 时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
_registeredHotkeys.Clear();
|
||||
// 已注销所有全局快捷键,集合已清空
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注销所有快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查快捷键是否已注册
|
||||
/// </summary>
|
||||
/// <param name="hotkeyName">快捷键名称</param>
|
||||
/// <returns>是否已注册</returns>
|
||||
public bool IsHotkeyRegistered(string hotkeyName)
|
||||
{
|
||||
return _registeredHotkeys.ContainsKey(hotkeyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已注册的快捷键列表
|
||||
/// </summary>
|
||||
/// <returns>快捷键信息列表</returns>
|
||||
public List<HotkeyInfo> GetRegisteredHotkeys()
|
||||
{
|
||||
return new List<HotkeyInfo>(_registeredHotkeys.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置文件中的快捷键信息(不注册,仅用于显示)
|
||||
/// </summary>
|
||||
/// <returns>配置文件中的快捷键列表</returns>
|
||||
public List<HotkeyInfo> GetHotkeysFromConfigFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置文件不存在");
|
||||
return new List<HotkeyInfo>();
|
||||
}
|
||||
|
||||
// 读取配置文件内容
|
||||
string jsonContent = File.ReadAllText(HotkeyConfigFile, Encoding.UTF8);
|
||||
if (string.IsNullOrEmpty(jsonContent))
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置文件为空", LogHelper.LogType.Warning);
|
||||
return new List<HotkeyInfo>();
|
||||
}
|
||||
|
||||
// 反序列化配置
|
||||
var config = JsonConvert.DeserializeObject<HotkeyConfig>(jsonContent);
|
||||
if (config?.Hotkeys == null || config.Hotkeys.Count == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置为空或格式错误", LogHelper.LogType.Warning);
|
||||
return new List<HotkeyInfo>();
|
||||
}
|
||||
|
||||
// 转换为HotkeyInfo列表(不注册,仅用于显示)
|
||||
var hotkeyList = new List<HotkeyInfo>();
|
||||
foreach (var hotkeyConfig in config.Hotkeys)
|
||||
{
|
||||
hotkeyList.Add(new HotkeyInfo
|
||||
{
|
||||
Name = hotkeyConfig.Name,
|
||||
Key = hotkeyConfig.Key,
|
||||
Modifiers = hotkeyConfig.Modifiers,
|
||||
Action = null // 不设置动作,仅用于显示
|
||||
});
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"从配置文件读取到 {hotkeyList.Count} 个快捷键信息");
|
||||
return hotkeyList;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从配置文件读取快捷键信息时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return new List<HotkeyInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册默认快捷键集合
|
||||
/// </summary>
|
||||
public void RegisterDefaultHotkeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 开始注册默认快捷键集合
|
||||
|
||||
// 基本操作快捷键
|
||||
RegisterHotkey("Undo", Key.Z, ModifierKeys.Control, () => _mainWindow.SymbolIconUndo_MouseUp(null, null));
|
||||
RegisterHotkey("Redo", Key.Y, ModifierKeys.Control, () => _mainWindow.SymbolIconRedo_MouseUp(null, null));
|
||||
RegisterHotkey("Clear", Key.E, ModifierKeys.Control, () => _mainWindow.SymbolIconDelete_MouseUp(null, null));
|
||||
RegisterHotkey("Paste", Key.V, ModifierKeys.Control, () => _mainWindow.HandleGlobalPaste(null, null));
|
||||
|
||||
// 工具切换快捷键
|
||||
RegisterHotkey("SelectTool", Key.S, ModifierKeys.Alt, () => _mainWindow.SymbolIconSelect_MouseUp(null, null));
|
||||
RegisterHotkey("DrawTool", Key.D, ModifierKeys.Alt, () => _mainWindow.PenIcon_Click(null, null));
|
||||
RegisterHotkey("EraserTool", Key.E, ModifierKeys.Alt, () => _mainWindow.EraserIcon_Click(null, null));
|
||||
RegisterHotkey("BlackboardTool", Key.B, ModifierKeys.Alt, () => _mainWindow.ImageBlackboard_MouseUp(null, null));
|
||||
RegisterHotkey("QuitDrawTool", Key.Q, ModifierKeys.Alt, () => _mainWindow.KeyChangeToQuitDrawTool(null, null));
|
||||
|
||||
// 画笔快捷键 - 使用反射访问penType字段
|
||||
RegisterHotkey("Pen1", Key.D1, ModifierKeys.Alt, () => SwitchToPenType(0));
|
||||
RegisterHotkey("Pen2", Key.D2, ModifierKeys.Alt, () => SwitchToPenType(1));
|
||||
RegisterHotkey("Pen3", Key.D3, ModifierKeys.Alt, () => SwitchToPenType(2));
|
||||
RegisterHotkey("Pen4", Key.D4, ModifierKeys.Alt, () => SwitchToPenType(3));
|
||||
RegisterHotkey("Pen5", Key.D5, ModifierKeys.Alt, () => SwitchToPenType(4));
|
||||
|
||||
// 功能快捷键
|
||||
RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.BtnDrawLine_Click(null, null));
|
||||
RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
|
||||
RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
|
||||
|
||||
// 退出快捷键
|
||||
RegisterHotkey("Exit", Key.Escape, ModifierKeys.None, () => _mainWindow.KeyExit(null, null));
|
||||
|
||||
// 已注册默认全局快捷键集合
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册默认快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置文件加载快捷键
|
||||
/// </summary>
|
||||
public void LoadHotkeysFromSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 开始从配置文件加载快捷键设置
|
||||
|
||||
// 检查是否应该注册快捷键
|
||||
if (!_hotkeysShouldBeRegistered)
|
||||
{
|
||||
// 当前状态不允许注册快捷键,跳过加载
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试从配置文件加载
|
||||
if (LoadHotkeysFromConfigFile())
|
||||
{
|
||||
// 成功从配置文件加载快捷键设置
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
LogHelper.WriteLogToFile("成功从配置文件加载快捷键设置");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果配置文件不存在或加载失败,使用默认快捷键
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件不存在,注册默认快捷键");
|
||||
RegisterDefaultHotkeys();
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning);
|
||||
RegisterDefaultHotkeys();
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从设置加载快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 出错时不自动使用默认快捷键,保持当前状态
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存快捷键配置到设置
|
||||
/// </summary>
|
||||
public void SaveHotkeysToSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始保存快捷键配置到配置文件", LogHelper.LogType.Event);
|
||||
|
||||
if (SaveHotkeysToConfigFile())
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置已成功保存到配置文件", LogHelper.LogType.Event);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("保存快捷键配置失败", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存快捷键配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用快捷键注册功能
|
||||
/// 调用此方法后,快捷键将被允许注册
|
||||
/// </summary>
|
||||
public void EnableHotkeyRegistration()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_hotkeysShouldBeRegistered)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
LogHelper.WriteLogToFile("启用快捷键注册功能");
|
||||
|
||||
// 立即加载快捷键设置
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键注册功能已经启用,重新加载快捷键设置");
|
||||
// 即使已经启用,也要重新加载快捷键设置以确保快捷键正常工作
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用快捷键注册功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用快捷键注册功能
|
||||
/// 调用此方法后,快捷键将被注销
|
||||
/// </summary>
|
||||
public void DisableHotkeyRegistration()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_hotkeysShouldBeRegistered)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = false;
|
||||
LogHelper.WriteLogToFile("禁用快捷键注册功能");
|
||||
|
||||
// 注销所有快捷键
|
||||
UnregisterAllHotkeys();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键注册功能已经禁用");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用快捷键注册功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前工具模式更新快捷键状态
|
||||
/// 在工具切换时调用此方法
|
||||
/// </summary>
|
||||
/// <param name="isMouseMode">是否为鼠标模式(选择模式)</param>
|
||||
public void UpdateHotkeyStateForToolMode(bool isMouseMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isMouseMode)
|
||||
{
|
||||
// 鼠标模式下禁用快捷键,让键盘操作放行
|
||||
DisableHotkeyRegistration();
|
||||
LogHelper.WriteLogToFile("切换到鼠标模式,禁用快捷键以放行键盘操作");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非鼠标模式下启用快捷键
|
||||
EnableHotkeyRegistration();
|
||||
LogHelper.WriteLogToFile("切换到非鼠标模式,启用快捷键");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新快捷键状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新快捷键配置
|
||||
/// </summary>
|
||||
/// <param name="hotkeyName">快捷键名称</param>
|
||||
/// <param name="key">新按键</param>
|
||||
/// <param name="modifiers">新修饰键</param>
|
||||
/// <returns>是否更新成功</returns>
|
||||
public bool UpdateHotkey(string hotkeyName, Key key, ModifierKeys modifiers)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_registeredHotkeys.ContainsKey(hotkeyName))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"快捷键 {hotkeyName} 不存在,无法更新", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取原有的动作
|
||||
var originalAction = _registeredHotkeys[hotkeyName].Action;
|
||||
|
||||
// 注销原有快捷键
|
||||
UnregisterHotkey(hotkeyName);
|
||||
|
||||
// 注册新的快捷键
|
||||
var success = RegisterHotkey(hotkeyName, key, modifiers, originalAction);
|
||||
|
||||
if (success)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"成功更新快捷键 {hotkeyName}: {modifiers}+{key}", LogHelper.LogType.Event);
|
||||
// 自动保存配置
|
||||
SaveHotkeysToSettings();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新快捷键 {hotkeyName} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Helper Methods
|
||||
/// <summary>
|
||||
/// 切换到指定笔类型
|
||||
/// </summary>
|
||||
/// <param name="penTypeIndex">笔类型索引</param>
|
||||
private void SwitchToPenType(int penTypeIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通过反射访问主窗口的penType字段
|
||||
var penTypeField = _mainWindow.GetType().GetField("penType",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (penTypeField != null)
|
||||
{
|
||||
penTypeField.SetValue(_mainWindow, penTypeIndex);
|
||||
|
||||
// 调用CheckPenTypeUIState方法更新UI状态
|
||||
var checkPenTypeMethod = _mainWindow.GetType().GetMethod("CheckPenTypeUIState",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (checkPenTypeMethod != null)
|
||||
{
|
||||
checkPenTypeMethod.Invoke(_mainWindow, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换到笔类型{penTypeIndex}时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置文件加载快捷键设置
|
||||
/// </summary>
|
||||
/// <returns>是否加载成功</returns>
|
||||
private bool LoadHotkeysFromConfigFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取配置文件内容
|
||||
string jsonContent = File.ReadAllText(HotkeyConfigFile, Encoding.UTF8);
|
||||
if (string.IsNullOrEmpty(jsonContent))
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置文件为空", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 反序列化配置
|
||||
var config = JsonConvert.DeserializeObject<HotkeyConfig>(jsonContent);
|
||||
if (config?.Hotkeys == null || config.Hotkeys.Count == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置为空或格式错误", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 注册配置中的快捷键
|
||||
int successCount = 0;
|
||||
foreach (var hotkeyConfig in config.Hotkeys)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 根据快捷键名称获取对应的动作
|
||||
var action = GetActionByName(hotkeyConfig.Name);
|
||||
if (action != null)
|
||||
{
|
||||
if (RegisterHotkey(hotkeyConfig.Name, hotkeyConfig.Key, hotkeyConfig.Modifiers, action))
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"未找到快捷键 {hotkeyConfig.Name} 对应的动作", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册快捷键 {hotkeyConfig.Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功加载 {successCount}/{config.Hotkeys.Count} 个快捷键配置", LogHelper.LogType.Event);
|
||||
if (successCount > 0)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
}
|
||||
return successCount > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从配置文件加载快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存快捷键配置到配置文件
|
||||
/// </summary>
|
||||
/// <returns>是否保存成功</returns>
|
||||
private bool SaveHotkeysToConfigFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保配置目录存在
|
||||
string configDir = Path.GetDirectoryName(HotkeyConfigFile);
|
||||
if (!Directory.Exists(configDir))
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
|
||||
// 创建配置对象
|
||||
var config = new HotkeyConfig
|
||||
{
|
||||
Version = "1.0",
|
||||
LastModified = DateTime.Now,
|
||||
Hotkeys = new List<HotkeyConfigItem>()
|
||||
};
|
||||
|
||||
// 添加所有已注册的快捷键
|
||||
foreach (var hotkey in _registeredHotkeys.Values)
|
||||
{
|
||||
config.Hotkeys.Add(new HotkeyConfigItem
|
||||
{
|
||||
Name = hotkey.Name,
|
||||
Key = hotkey.Key,
|
||||
Modifiers = hotkey.Modifiers
|
||||
});
|
||||
}
|
||||
|
||||
// 序列化为JSON
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented
|
||||
};
|
||||
|
||||
string jsonContent = JsonConvert.SerializeObject(config, settings);
|
||||
|
||||
// 直接写入原文件,覆盖原有内容
|
||||
File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
|
||||
|
||||
LogHelper.WriteLogToFile($"快捷键配置已保存到: {HotkeyConfigFile}", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存快捷键配置到配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据快捷键名称获取对应的动作
|
||||
/// </summary>
|
||||
/// <param name="hotkeyName">快捷键名称</param>
|
||||
/// <returns>对应的动作,如果不存在则返回null</returns>
|
||||
private Action GetActionByName(string hotkeyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (hotkeyName)
|
||||
{
|
||||
case "Undo":
|
||||
return () => _mainWindow.SymbolIconUndo_MouseUp(null, null);
|
||||
case "Redo":
|
||||
return () => _mainWindow.SymbolIconRedo_MouseUp(null, null);
|
||||
case "Clear":
|
||||
return () => _mainWindow.SymbolIconDelete_MouseUp(null, null);
|
||||
case "Paste":
|
||||
return () => _mainWindow.HandleGlobalPaste(null, null);
|
||||
case "SelectTool":
|
||||
return () => _mainWindow.SymbolIconSelect_MouseUp(null, null);
|
||||
case "DrawTool":
|
||||
return () => _mainWindow.PenIcon_Click(null, null);
|
||||
case "EraserTool":
|
||||
return () => _mainWindow.EraserIcon_Click(null, null);
|
||||
case "BlackboardTool":
|
||||
return () => _mainWindow.ImageBlackboard_MouseUp(null, null);
|
||||
case "QuitDrawTool":
|
||||
return () => _mainWindow.KeyChangeToQuitDrawTool(null, null);
|
||||
case "Pen1":
|
||||
return () => SwitchToPenType(0);
|
||||
case "Pen2":
|
||||
return () => SwitchToPenType(1);
|
||||
case "Pen3":
|
||||
return () => SwitchToPenType(2);
|
||||
case "Pen4":
|
||||
return () => SwitchToPenType(3);
|
||||
case "Pen5":
|
||||
return () => SwitchToPenType(4);
|
||||
case "DrawLine":
|
||||
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
||||
case "Screenshot":
|
||||
return () => _mainWindow.SaveScreenShotToDesktop();
|
||||
case "Hide":
|
||||
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
||||
case "Exit":
|
||||
return () => _mainWindow.KeyExit(null, null);
|
||||
default:
|
||||
LogHelper.WriteLogToFile($"未知的快捷键名称: {hotkeyName}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取快捷键 {hotkeyName} 对应动作时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前是否处于鼠标模式(选择模式)
|
||||
/// </summary>
|
||||
/// <returns>如果处于鼠标模式则返回true(不应该注册快捷键),否则返回false(应该注册快捷键)</returns>
|
||||
private bool IsInSelectMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通过反射访问主窗口的FloatingbarSelectionBG字段
|
||||
var floatingbarSelectionBGField = _mainWindow.GetType().GetField("FloatingbarSelectionBG",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (floatingbarSelectionBGField != null)
|
||||
{
|
||||
var floatingbarSelectionBG = floatingbarSelectionBGField.GetValue(_mainWindow);
|
||||
if (floatingbarSelectionBG != null)
|
||||
{
|
||||
// 检查高光是否可见
|
||||
var visibilityProperty = floatingbarSelectionBG.GetType().GetProperty("Visibility");
|
||||
if (visibilityProperty != null)
|
||||
{
|
||||
var visibility = visibilityProperty.GetValue(floatingbarSelectionBG);
|
||||
if (visibility != null && visibility.ToString() == "Hidden")
|
||||
{
|
||||
// 高光隐藏,说明没有选中任何工具,此时应该注销快捷键以释放系统快捷键
|
||||
return true; // 返回true表示应该注销快捷键
|
||||
}
|
||||
}
|
||||
|
||||
// 通过反射访问Canvas.GetLeft方法来获取高光位置
|
||||
var canvasType = Type.GetType("System.Windows.Controls.Canvas, PresentationFramework");
|
||||
if (canvasType != null)
|
||||
{
|
||||
var getLeftMethod = canvasType.GetMethod("GetLeft", BindingFlags.Public | BindingFlags.Static);
|
||||
if (getLeftMethod != null)
|
||||
{
|
||||
var leftPosition = getLeftMethod.Invoke(null, new[] { floatingbarSelectionBG });
|
||||
if (leftPosition != null)
|
||||
{
|
||||
var position = Convert.ToDouble(leftPosition);
|
||||
|
||||
// 根据高光位置判断当前选中的工具
|
||||
// 位置计算基于SetFloatingBarHighlightPosition方法中的逻辑
|
||||
bool isMouseMode;
|
||||
|
||||
// 简化判断:如果位置接近0,说明是鼠标模式
|
||||
// 如果位置接近28,说明是批注模式
|
||||
// 如果位置更大,说明是其他工具
|
||||
if (position < 5) // 鼠标模式:marginOffset + (cursorWidth - actualHighlightWidth) / 2 ≈ 0
|
||||
{
|
||||
isMouseMode = true;
|
||||
}
|
||||
else if (position < 35) // 批注模式:marginOffset + cursorWidth + (penWidth - actualHighlightWidth) / 2 ≈ 28
|
||||
{
|
||||
isMouseMode = false;
|
||||
}
|
||||
else // 其他工具(橡皮擦、选择等)
|
||||
{
|
||||
isMouseMode = false;
|
||||
}
|
||||
|
||||
return isMouseMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法获取高光状态,则回退到inkCanvas.EditingMode判断
|
||||
|
||||
// 通过反射访问主窗口的inkCanvas字段
|
||||
var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (inkCanvasField != null)
|
||||
{
|
||||
var inkCanvas = inkCanvasField.GetValue(_mainWindow);
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
// 通过反射访问inkCanvas的EditingMode属性
|
||||
var editingModeProperty = inkCanvas.GetType().GetProperty("EditingMode");
|
||||
if (editingModeProperty != null)
|
||||
{
|
||||
var editingMode = editingModeProperty.GetValue(inkCanvas);
|
||||
if (editingMode != null)
|
||||
{
|
||||
// 检查是否为批注模式
|
||||
var isInkMode = editingMode.ToString().Contains("Ink");
|
||||
var isSelectMode = editingMode.ToString().Contains("Select");
|
||||
|
||||
// 如果是批注模式或选择模式,则应该注册快捷键(返回false)
|
||||
// 如果是橡皮擦模式或其他模式,则不应该注册快捷键(返回true)
|
||||
var shouldNotRegisterHotkeys = !isInkMode && !isSelectMode;
|
||||
|
||||
return shouldNotRegisterHotkeys;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果无法获取任何状态信息,则回退到原来的判断逻辑
|
||||
|
||||
// 通过反射访问主窗口的currentMode字段(作为最后的备用方案)
|
||||
var currentModeField = _mainWindow.GetType().GetField("currentMode",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
if (currentModeField != null)
|
||||
{
|
||||
var currentMode = currentModeField.GetValue(_mainWindow);
|
||||
if (currentMode != null)
|
||||
{
|
||||
var modeValue = currentMode.ToString();
|
||||
// 注意:这里的逻辑需要修正
|
||||
// currentMode == 0 表示屏幕模式(PPT放映),此时应该允许快捷键
|
||||
// currentMode == 1 表示黑板/白板模式,此时也应该允许快捷键
|
||||
var isSelectMode = false; // 修正:所有模式都应该允许快捷键
|
||||
return isSelectMode;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // 默认允许快捷键
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查鼠标模式状态时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return false; // 出错时默认允许快捷键
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Nested Classes
|
||||
/// <summary>
|
||||
/// 快捷键信息类
|
||||
/// </summary>
|
||||
public class HotkeyInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Key Key { get; set; }
|
||||
public ModifierKeys Modifiers { get; set; }
|
||||
public Action Action { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var modifiersText = Modifiers == ModifierKeys.None ? "" : $"{Modifiers}+";
|
||||
return $"{modifiersText}{Key}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快捷键配置类
|
||||
/// </summary>
|
||||
private class HotkeyConfig
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public List<HotkeyConfigItem> Hotkeys { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快捷键配置项类
|
||||
/// </summary>
|
||||
private class HotkeyConfigItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Key Key { get; set; }
|
||||
public ModifierKeys Modifiers { get; set; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 硬件加速的墨迹处理器,利用WPF的GPU渲染能力
|
||||
/// </summary>
|
||||
public class HardwareAcceleratedInkProcessor
|
||||
{
|
||||
private readonly RenderTargetBitmap _renderTarget;
|
||||
private readonly DrawingVisual _drawingVisual;
|
||||
private bool _isInitialized;
|
||||
|
||||
public HardwareAcceleratedInkProcessor(int width = 1920, int height = 1080)
|
||||
{
|
||||
// 创建硬件加速的渲染目标
|
||||
_renderTarget = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
|
||||
_drawingVisual = new DrawingVisual();
|
||||
|
||||
// 启用硬件加速
|
||||
RenderOptions.SetBitmapScalingMode(_drawingVisual, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(_drawingVisual, EdgeMode.Aliased);
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用GPU加速的贝塞尔曲线平滑
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeWithGPU(Stroke originalStroke)
|
||||
{
|
||||
if (!_isInitialized || originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用PathGeometry进行硬件加速的曲线拟合
|
||||
var pathGeometry = CreateSmoothPathGeometry(originalStroke.StylusPoints);
|
||||
|
||||
// 将PathGeometry转换回StylusPoint集合
|
||||
var smoothedPoints = ConvertPathGeometryToStylusPoints(pathGeometry, originalStroke.StylusPoints);
|
||||
|
||||
return new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return originalStroke;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建平滑的路径几何体
|
||||
/// </summary>
|
||||
private PathGeometry CreateSmoothPathGeometry(StylusPointCollection points)
|
||||
{
|
||||
var pathGeometry = new PathGeometry();
|
||||
var pathFigure = new PathFigure();
|
||||
|
||||
if (points.Count < 2) return pathGeometry;
|
||||
|
||||
pathFigure.StartPoint = new Point(points[0].X, points[0].Y);
|
||||
|
||||
// 使用贝塞尔曲线段创建平滑路径,增加插点密度
|
||||
for (int i = 0; i < points.Count - 1; i += 2) // 从i+=3改为i+=2,增加插点密度
|
||||
{
|
||||
var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint;
|
||||
var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1;
|
||||
var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2;
|
||||
|
||||
var bezierSegment = new BezierSegment(p1, p2, p3, true);
|
||||
pathFigure.Segments.Add(bezierSegment);
|
||||
}
|
||||
|
||||
pathGeometry.Figures.Add(pathFigure);
|
||||
return pathGeometry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将PathGeometry转换为StylusPoint集合
|
||||
/// </summary>
|
||||
private List<StylusPoint> ConvertPathGeometryToStylusPoints(PathGeometry pathGeometry, StylusPointCollection originalPoints)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
var flattened = pathGeometry.GetFlattenedPathGeometry();
|
||||
|
||||
foreach (var figure in flattened.Figures)
|
||||
{
|
||||
result.Add(new StylusPoint(figure.StartPoint.X, figure.StartPoint.Y, 0.5f));
|
||||
|
||||
foreach (var segment in figure.Segments)
|
||||
{
|
||||
if (segment is LineSegment lineSegment)
|
||||
{
|
||||
result.Add(new StylusPoint(lineSegment.Point.X, lineSegment.Point.Y, 0.5f));
|
||||
}
|
||||
else if (segment is PolyLineSegment polyLineSegment)
|
||||
{
|
||||
foreach (var point in polyLineSegment.Points)
|
||||
{
|
||||
result.Add(new StylusPoint(point.X, point.Y, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保持原始压感信息
|
||||
InterpolatePressure(result, originalPoints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插值压感信息
|
||||
/// </summary>
|
||||
private void InterpolatePressure(List<StylusPoint> smoothedPoints, StylusPointCollection originalPoints)
|
||||
{
|
||||
if (originalPoints.Count == 0 || smoothedPoints.Count == 0) return;
|
||||
|
||||
for (int i = 0; i < smoothedPoints.Count; i++)
|
||||
{
|
||||
double ratio = (double)i / (smoothedPoints.Count - 1);
|
||||
int originalIndex = (int)(ratio * (originalPoints.Count - 1));
|
||||
originalIndex = Math.Max(0, Math.Min(originalIndex, originalPoints.Count - 1));
|
||||
|
||||
var point = smoothedPoints[i];
|
||||
float pressure = originalPoints[originalIndex].PressureFactor;
|
||||
smoothedPoints[i] = new StylusPoint(point.X, point.Y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用GPU加速的并行贝塞尔计算
|
||||
/// </summary>
|
||||
public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 32)
|
||||
{
|
||||
if (controlPoints.Length < 4) return controlPoints;
|
||||
|
||||
var result = new StylusPoint[segments * (controlPoints.Length / 4)];
|
||||
|
||||
Parallel.For(0, controlPoints.Length / 4, segmentIndex =>
|
||||
{
|
||||
var p0 = controlPoints[segmentIndex * 4];
|
||||
var p1 = controlPoints[segmentIndex * 4 + 1];
|
||||
var p2 = controlPoints[segmentIndex * 4 + 2];
|
||||
var p3 = controlPoints[segmentIndex * 4 + 3];
|
||||
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
double t = (double)i / (segments - 1);
|
||||
result[segmentIndex * segments + i] = CubicBezierFast(p0, p1, p2, p3, t);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的三次贝塞尔曲线计算
|
||||
/// </summary>
|
||||
private static StylusPoint CubicBezierFast(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X;
|
||||
double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y;
|
||||
float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t);
|
||||
|
||||
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放GPU资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_renderTarget?.Clear();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// IACore DLL自动释放器
|
||||
/// 在应用启动时自动释放IACore相关的DLL文件到应用程序目录
|
||||
/// </summary>
|
||||
public static class IACoreDllExtractor
|
||||
{
|
||||
private static readonly string[] RequiredDlls = {
|
||||
"IACore.dll",
|
||||
"IALoader.dll",
|
||||
"IAWinFX.dll"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 在应用启动时释放IACore相关DLL
|
||||
/// </summary>
|
||||
public static void ExtractIACoreDlls()
|
||||
{
|
||||
try
|
||||
{
|
||||
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
LogHelper.WriteLogToFile("开始检查并释放IACore相关DLL文件");
|
||||
|
||||
foreach (string dllName in RequiredDlls)
|
||||
{
|
||||
string targetPath = Path.Combine(appDirectory, dllName);
|
||||
|
||||
// 检查文件是否已存在且有效
|
||||
if (File.Exists(targetPath) && IsValidDll(targetPath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"{dllName} 已存在且有效,跳过释放");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 从嵌入资源中释放DLL
|
||||
if (ExtractDllFromResource(dllName, targetPath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"成功释放 {dllName} 到 {targetPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"警告:无法释放 {dllName},可能影响形状识别功能", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IACore DLL释放检查完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从嵌入资源中提取DLL文件
|
||||
/// </summary>
|
||||
private static bool ExtractDllFromResource(string dllName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string resourceName = $"Ink_Canvas.Resources.IACore.{dllName}";
|
||||
|
||||
using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (resourceStream == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"未找到嵌入资源: {resourceName}", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保目标目录存在
|
||||
string targetDirectory = Path.GetDirectoryName(targetPath);
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
using (FileStream fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
resourceStream.CopyTo(fileStream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从资源提取 {dllName} 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查DLL文件是否有效
|
||||
/// </summary>
|
||||
private static bool IsValidDll(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
return false;
|
||||
|
||||
FileInfo fileInfo = new FileInfo(filePath);
|
||||
|
||||
// 检查文件大小(空文件或过小的文件可能无效)
|
||||
if (fileInfo.Length < 1024) // 小于1KB可能无效
|
||||
return false;
|
||||
|
||||
// 简单检查PE头(DLL文件应该以MZ开头)
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
byte[] buffer = new byte[2];
|
||||
if (fs.Read(buffer, 0, 2) == 2)
|
||||
{
|
||||
return buffer[0] == 0x4D && buffer[1] == 0x5A; // "MZ"
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理释放的DLL文件(可选,在应用退出时调用)
|
||||
/// </summary>
|
||||
public static void CleanupExtractedDlls()
|
||||
{
|
||||
try
|
||||
{
|
||||
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
foreach (string dllName in RequiredDlls)
|
||||
{
|
||||
string filePath = Path.Combine(appDirectory, dllName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
LogHelper.WriteLogToFile($"已清理 {dllName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理 {dllName} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 改进的三次贝塞尔曲线平滑算法
|
||||
/// </summary>
|
||||
public class ImprovedBezierSmoothing
|
||||
{
|
||||
private readonly InkSmoothingConfig _config;
|
||||
|
||||
public ImprovedBezierSmoothing(InkSmoothingConfig config = null)
|
||||
{
|
||||
_config = config ?? new InkSmoothingConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用改进的贝塞尔曲线算法平滑笔画
|
||||
/// </summary>
|
||||
public Stroke SmoothStroke(Stroke originalStroke)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 3)
|
||||
return originalStroke;
|
||||
|
||||
var originalPoints = originalStroke.StylusPoints.ToArray();
|
||||
|
||||
// 预处理:去除噪声点
|
||||
var cleanedPoints = RemoveNoisePoints(originalPoints);
|
||||
|
||||
// 使用改进的贝塞尔曲线拟合
|
||||
var smoothedPoints = ApplyCubicBezierSmoothing(cleanedPoints);
|
||||
|
||||
// 后处理:重采样和优化
|
||||
var finalPoints = PostProcessPoints(smoothedPoints);
|
||||
|
||||
return new Stroke(new StylusPointCollection(finalPoints))
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除噪声点
|
||||
/// </summary>
|
||||
private StylusPoint[] RemoveNoisePoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 3) return points;
|
||||
|
||||
var result = new List<StylusPoint> { points[0] };
|
||||
double minDistance = _config.ResampleInterval * 0.5;
|
||||
|
||||
for (int i = 1; i < points.Length - 1; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
var next = points[i + 1];
|
||||
|
||||
// 计算到前一个点的距离
|
||||
double distToPrev = Math.Sqrt((curr.X - prev.X) * (curr.X - prev.X) +
|
||||
(curr.Y - prev.Y) * (curr.Y - prev.Y));
|
||||
|
||||
// 如果距离太近,跳过这个点
|
||||
if (distToPrev < minDistance)
|
||||
continue;
|
||||
|
||||
// 检查是否为异常点(与前后点形成锐角)
|
||||
if (IsOutlierPoint(prev, curr, next))
|
||||
continue;
|
||||
|
||||
result.Add(curr);
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否为异常点
|
||||
/// </summary>
|
||||
private bool IsOutlierPoint(StylusPoint prev, StylusPoint curr, StylusPoint next)
|
||||
{
|
||||
var v1 = new Vector(curr.X - prev.X, curr.Y - prev.Y);
|
||||
var v2 = new Vector(next.X - curr.X, next.Y - curr.Y);
|
||||
|
||||
if (v1.Length == 0 || v2.Length == 0) return false;
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
|
||||
double dotProduct = Vector.Multiply(v1, v2);
|
||||
double angle = Math.Acos(Math.Max(-1, Math.Min(1, dotProduct)));
|
||||
|
||||
// 如果角度小于30度,认为是异常点
|
||||
return angle < Math.PI / 6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用三次贝塞尔曲线平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 4) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
// 使用滑动窗口进行贝塞尔曲线拟合
|
||||
for (int i = 0; i <= points.Length - 4; i++)
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
|
||||
// 计算控制点
|
||||
var controlPoints = CalculateOptimalControlPoints(p0, p1, p2, p3);
|
||||
|
||||
// 计算插值步数
|
||||
int steps = CalculateInterpolationSteps(p0, p1, p2, p3);
|
||||
|
||||
// 生成贝塞尔曲线点
|
||||
for (int j = 1; j <= steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
var bezierPoint = CalculateBezierPoint(p0, controlPoints.cp1, controlPoints.cp2, p3, t);
|
||||
result.Add(bezierPoint);
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算最优控制点
|
||||
/// </summary>
|
||||
private (Point cp1, Point cp2) CalculateOptimalControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 计算切线方向
|
||||
var tangent1 = CalculateTangent(p0, p1, p2);
|
||||
var tangent2 = CalculateTangent(p1, p2, p3);
|
||||
|
||||
// 计算控制点距离
|
||||
double dist1 = CalculateDistance(p0, p1);
|
||||
double dist2 = CalculateDistance(p2, p3);
|
||||
|
||||
double controlDist1 = dist1 * _config.CurveTension;
|
||||
double controlDist2 = dist2 * _config.CurveTension;
|
||||
|
||||
// 计算控制点
|
||||
var cp1 = new Point(
|
||||
p1.X + tangent1.X * controlDist1,
|
||||
p1.Y + tangent1.Y * controlDist1
|
||||
);
|
||||
|
||||
var cp2 = new Point(
|
||||
p2.X - tangent2.X * controlDist2,
|
||||
p2.Y - tangent2.Y * controlDist2
|
||||
);
|
||||
|
||||
return (cp1, cp2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算切线方向
|
||||
/// </summary>
|
||||
private Vector CalculateTangent(StylusPoint p0, StylusPoint p1, StylusPoint p2)
|
||||
{
|
||||
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||
|
||||
// 如果向量长度为零,返回零向量
|
||||
if (v1.Length == 0 || v2.Length == 0)
|
||||
return new Vector(0, 0);
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
|
||||
// 返回平均方向
|
||||
var tangent = (v1 + v2) / 2;
|
||||
if (tangent.Length > 0)
|
||||
tangent.Normalize();
|
||||
|
||||
return tangent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两点间距离
|
||||
/// </summary>
|
||||
private double CalculateDistance(StylusPoint p1, StylusPoint p2)
|
||||
{
|
||||
double dx = p2.X - p1.X;
|
||||
double dy = p2.Y - p1.Y;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算插值步数
|
||||
/// </summary>
|
||||
private int CalculateInterpolationSteps(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
if (!_config.UseAdaptiveInterpolation)
|
||||
return _config.InterpolationSteps;
|
||||
|
||||
// 计算曲线长度
|
||||
double totalLength = CalculateDistance(p0, p1) + CalculateDistance(p1, p2) + CalculateDistance(p2, p3);
|
||||
|
||||
// 计算曲率
|
||||
double curvature = CalculateCurvature(p0, p1, p2, p3);
|
||||
|
||||
// 基于长度和曲率计算步数
|
||||
int baseSteps = Math.Max(8, Math.Min(20, (int)(totalLength / 10)));
|
||||
int curvatureSteps = (int)(curvature * 15);
|
||||
|
||||
return Math.Max(_config.InterpolationSteps, Math.Min(30, baseSteps + curvatureSteps));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算曲率
|
||||
/// </summary>
|
||||
private double CalculateCurvature(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||
var v3 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||
|
||||
if (v1.Length == 0 || v2.Length == 0 || v3.Length == 0) return 0;
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
v3.Normalize();
|
||||
|
||||
// 计算角度变化
|
||||
double angle1 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v1, v2))));
|
||||
double angle2 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v2, v3))));
|
||||
|
||||
return (angle1 + angle2) / Math.PI; // 归一化到0-1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算贝塞尔曲线上的点
|
||||
/// </summary>
|
||||
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
// 预计算系数
|
||||
double c0 = uuu;
|
||||
double c1 = 3 * uu * t;
|
||||
double c2 = 3 * u * tt;
|
||||
double c3 = ttt;
|
||||
|
||||
double x = c0 * p0.X + c1 * cp1.X + c2 * cp2.X + c3 * p3.X;
|
||||
double y = c0 * p0.Y + c1 * cp1.Y + c2 * cp2.Y + c3 * p3.Y;
|
||||
|
||||
// 插值压力值
|
||||
float pressure = (float)(p0.PressureFactor * u + p3.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
return new StylusPoint(x, y, pressure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后处理点集
|
||||
/// </summary>
|
||||
private StylusPoint[] PostProcessPoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
// 如果点数过多,进行重采样
|
||||
if (points.Length > _config.MaxPointsPerStroke)
|
||||
{
|
||||
return ResamplePoints(points, _config.ResampleInterval);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重采样点集
|
||||
/// </summary>
|
||||
private StylusPoint[] ResamplePoints(StylusPoint[] points, double interval)
|
||||
{
|
||||
var result = new List<StylusPoint> { points[0] };
|
||||
double accumulated = 0;
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
double dx = curr.X - prev.X;
|
||||
double dy = curr.Y - prev.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist + accumulated >= interval)
|
||||
{
|
||||
double t = (interval - accumulated) / dist;
|
||||
double x = prev.X + t * dx;
|
||||
double y = prev.Y + t * dy;
|
||||
float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
result.Add(new StylusPoint(x, y, pressure));
|
||||
accumulated = 0;
|
||||
i--; // 重新处理当前点
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulated += dist;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,832 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 墨迹渐隐管理器 - 管理墨迹的渐隐动画和状态
|
||||
/// </summary>
|
||||
public class InkFadeManager
|
||||
{
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// 是否启用墨迹渐隐功能
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹渐隐时间(毫秒)
|
||||
/// </summary>
|
||||
public int FadeTime { get; set; } = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// 渐隐动画持续时间(毫秒)
|
||||
/// </summary>
|
||||
public int AnimationDuration { get; set; } = 1000;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private readonly Dictionary<Stroke, DispatcherTimer> _fadeTimers;
|
||||
private readonly Dictionary<Stroke, UIElement> _strokeVisuals;
|
||||
private readonly Dictionary<Stroke, Point> _strokeStartPoints;
|
||||
private readonly Dictionary<Stroke, Point> _strokeEndPoints;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public InkFadeManager(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
_dispatcher = _mainWindow.Dispatcher;
|
||||
_fadeTimers = new Dictionary<Stroke, DispatcherTimer>();
|
||||
_strokeVisuals = new Dictionary<Stroke, UIElement>();
|
||||
_strokeStartPoints = new Dictionary<Stroke, Point>();
|
||||
_strokeEndPoints = new Dictionary<Stroke, Point>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 添加需要渐隐的墨迹
|
||||
/// </summary>
|
||||
/// <param name="stroke">墨迹对象</param>
|
||||
/// <param name="startPoint">落笔点</param>
|
||||
/// <param name="endPoint">抬笔点</param>
|
||||
public void AddFadingStroke(Stroke stroke, Point startPoint, Point endPoint)
|
||||
{
|
||||
if (!IsEnabled || stroke == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// 记录墨迹的起点和终点
|
||||
_strokeStartPoints[stroke] = startPoint;
|
||||
_strokeEndPoints[stroke] = endPoint;
|
||||
|
||||
// 创建墨迹的视觉元素(湿墨迹状态)
|
||||
var strokeVisual = CreateStrokeVisual(stroke);
|
||||
if (strokeVisual == null) return;
|
||||
|
||||
_strokeVisuals[stroke] = strokeVisual;
|
||||
|
||||
// 创建定时器,在指定时间后开始渐隐动画
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(FadeTime)
|
||||
};
|
||||
|
||||
timer.Tick += (sender, e) =>
|
||||
{
|
||||
StartFadeAnimation(stroke);
|
||||
timer.Stop();
|
||||
_fadeTimers.Remove(stroke);
|
||||
};
|
||||
|
||||
_fadeTimers[stroke] = timer;
|
||||
timer.Start();
|
||||
|
||||
// 将视觉元素添加到画布上
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
// 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children
|
||||
// 这样可以避免坐标系统问题
|
||||
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Children.Add(strokeVisual);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果无法获取父容器,则添加到 inkCanvas.Children
|
||||
_mainWindow.inkCanvas.Children.Add(strokeVisual);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加墨迹视觉元素到画布失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除墨迹
|
||||
/// </summary>
|
||||
/// <param name="stroke">要移除的墨迹</param>
|
||||
public void RemoveStroke(Stroke stroke)
|
||||
{
|
||||
if (stroke == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (_fadeTimers.TryGetValue(stroke, out var timer))
|
||||
{
|
||||
timer.Stop();
|
||||
_fadeTimers.Remove(stroke);
|
||||
}
|
||||
|
||||
if (_strokeVisuals.TryGetValue(stroke, out var visual))
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从父容器中移除墨迹
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(visual);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从画布移除墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
_strokeVisuals.Remove(stroke);
|
||||
}
|
||||
|
||||
_strokeStartPoints.Remove(stroke);
|
||||
_strokeEndPoints.Remove(stroke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"移除渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有渐隐墨迹
|
||||
/// </summary>
|
||||
public void ClearAllFadingStrokes()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var timer in _fadeTimers.Values)
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
_fadeTimers.Clear();
|
||||
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
||||
foreach (var visual in _strokeVisuals.Values)
|
||||
{
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas.Children.Contains(visual))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(visual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除所有墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
_strokeVisuals.Clear();
|
||||
_strokeStartPoints.Clear();
|
||||
_strokeEndPoints.Clear();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除所有渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新渐隐时间设置
|
||||
/// </summary>
|
||||
/// <param name="fadeTime">新的渐隐时间(毫秒)</param>
|
||||
public void UpdateFadeTime(int fadeTime)
|
||||
{
|
||||
FadeTime = fadeTime;
|
||||
|
||||
foreach (var kvp in _fadeTimers)
|
||||
{
|
||||
var stroke = kvp.Key;
|
||||
var timer = kvp.Value;
|
||||
|
||||
timer.Stop();
|
||||
timer.Interval = TimeSpan.FromMilliseconds(FadeTime);
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 启用墨迹渐隐功能
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile("墨迹渐隐功能已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用墨迹渐隐功能
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile("墨迹渐隐功能已禁用");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// 创建墨迹的视觉元素
|
||||
/// </summary>
|
||||
/// <param name="stroke">墨迹对象</param>
|
||||
/// <returns>视觉元素</returns>
|
||||
private UIElement CreateStrokeVisual(Stroke stroke)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建路径几何,使用墨迹的实际位置
|
||||
var geometry = stroke.GetGeometry();
|
||||
if (geometry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取绘画属性
|
||||
var drawingAttribs = stroke.DrawingAttributes;
|
||||
|
||||
// 创建路径元素,确保使用正确的绘画属性
|
||||
var path = new Path
|
||||
{
|
||||
Data = geometry,
|
||||
Stroke = new SolidColorBrush(drawingAttribs.Color),
|
||||
StrokeThickness = drawingAttribs.Width, // 使用原始墨迹的粗细
|
||||
StrokeStartLineCap = PenLineCap.Round,
|
||||
StrokeEndLineCap = PenLineCap.Round,
|
||||
StrokeLineJoin = PenLineJoin.Round,
|
||||
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null, // 高亮笔需要填充
|
||||
Opacity = 0.95, // 初始透明度更高,显得更自然
|
||||
|
||||
// 优化渲染质量
|
||||
UseLayoutRounding = false,
|
||||
SnapsToDevicePixels = false
|
||||
};
|
||||
|
||||
// 如果是高亮笔,调整透明度和混合模式
|
||||
if (drawingAttribs.IsHighlighter)
|
||||
{
|
||||
path.Opacity = 0.4; // 高亮笔初始透明度更低,更符合荧光笔特性
|
||||
|
||||
// 为高亮笔添加特殊的混合效果
|
||||
// 使用更柔和的笔触样式
|
||||
path.StrokeStartLineCap = PenLineCap.Flat;
|
||||
path.StrokeEndLineCap = PenLineCap.Flat;
|
||||
path.StrokeLineJoin = PenLineJoin.Miter;
|
||||
|
||||
// 高亮笔通常需要更宽的笔触来覆盖下面的内容
|
||||
if (drawingAttribs.Width < 20)
|
||||
{
|
||||
path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20);
|
||||
}
|
||||
}
|
||||
|
||||
// 不设置任何变换,保持墨迹原有粗细
|
||||
var bounds = geometry.Bounds;
|
||||
|
||||
// 设置墨迹的初始位置
|
||||
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
|
||||
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始渐隐动画
|
||||
/// </summary>
|
||||
/// <param name="stroke">要渐隐的墨迹</param>
|
||||
private void StartFadeAnimation(Stroke stroke)
|
||||
{
|
||||
if (!_strokeVisuals.TryGetValue(stroke, out var visual)) return;
|
||||
|
||||
try
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 获取当前透明度和判断是否为高亮笔
|
||||
var currentOpacity = visual.Opacity;
|
||||
var isHighlighter = stroke.DrawingAttributes.IsHighlighter;
|
||||
|
||||
// 根据墨迹类型选择不同的动画效果
|
||||
if (isHighlighter)
|
||||
{
|
||||
StartHighlighterFadeAnimation(visual, stroke, currentOpacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartNormalStrokeFadeAnimation(visual, stroke, currentOpacity);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始普通墨迹的渐隐动画
|
||||
/// </summary>
|
||||
private void StartNormalStrokeFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
|
||||
{
|
||||
try
|
||||
{
|
||||
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始普通墨迹渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始高亮笔的渐隐动画
|
||||
/// </summary>
|
||||
private void StartHighlighterFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
|
||||
{
|
||||
try
|
||||
{
|
||||
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始高亮笔渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渐进式渐隐动画 - 从起点到终点逐渐消失
|
||||
/// </summary>
|
||||
private void StartProgressiveFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保所有墨迹都能显示动画,包括短墨迹
|
||||
if (stroke.StylusPoints.Count < 2)
|
||||
{
|
||||
// 只有1个点的墨迹也使用分段动画,确保视觉效果
|
||||
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
// 将墨迹分段并创建多个 Path
|
||||
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"渐进式渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
// 失败时回退到简单动画
|
||||
StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建分段墨迹并开始渐进消失
|
||||
/// </summary>
|
||||
private void CreateSegmentedStroke(UIElement originalVisual, Stroke stroke, double opacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stylusPoints = stroke.StylusPoints;
|
||||
var totalPoints = stylusPoints.Count;
|
||||
|
||||
// 分段算法 - 确保所有墨迹都有足够的动画效果
|
||||
var strokeLength = CalculateStrokeLength(stylusPoints);
|
||||
var segmentCount = CalculateOptimalSegmentCount(totalPoints, strokeLength);
|
||||
|
||||
// 强制最小分段数量,确保短墨迹也有动画效果
|
||||
segmentCount = Math.Max(segmentCount, 4);
|
||||
|
||||
var pointsPerSegment = Math.Max(1, totalPoints / segmentCount);
|
||||
|
||||
// 隐藏原始视觉元素
|
||||
originalVisual.Visibility = Visibility.Hidden;
|
||||
|
||||
var segments = new List<UIElement>();
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent == null)
|
||||
{
|
||||
// 如果父容器不是Panel,直接使用InkCanvas
|
||||
parent = null; // 稍后会检查并使用InkCanvas.Children
|
||||
}
|
||||
|
||||
// 创建各个分段 - 确保短墨迹也能正确分段
|
||||
for (int i = 0; i < segmentCount; i++)
|
||||
{
|
||||
var startIndex = i * pointsPerSegment;
|
||||
var endIndex = (i == segmentCount - 1) ? totalPoints - 1 : (i + 1) * pointsPerSegment;
|
||||
|
||||
// 确保有足够的点来创建分段,对于短墨迹特殊处理
|
||||
if (endIndex <= startIndex && totalPoints > 1)
|
||||
{
|
||||
// 短墨迹:每个点作为一个分段
|
||||
startIndex = i;
|
||||
endIndex = Math.Min(i + 1, totalPoints - 1);
|
||||
}
|
||||
|
||||
// 为每个分段添加重叠,确保连接处平滑
|
||||
var overlap = Math.Max(1, pointsPerSegment / 6); // 15%的重叠,平衡平滑与速度
|
||||
var actualStartIndex = Math.Max(0, startIndex - overlap);
|
||||
var actualEndIndex = Math.Min(totalPoints - 1, endIndex + overlap);
|
||||
|
||||
var segment = CreateStrokeSegment(stroke, actualStartIndex, actualEndIndex, opacity);
|
||||
if (segment != null)
|
||||
{
|
||||
segments.Add(segment);
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Children.Add(segment);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Add(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始分段渐隐动画
|
||||
StartSegmentedFadeAnimation(segments, stroke, originalVisual, duration);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
StartSimpleFadeAnimation(originalVisual, stroke, opacity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建墨迹分段
|
||||
/// </summary>
|
||||
private UIElement CreateStrokeSegment(Stroke originalStroke, int startIndex, int endIndex, double opacity)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建分段的 StylusPoint 集合
|
||||
var segmentPoints = new StylusPointCollection();
|
||||
for (int i = startIndex; i <= endIndex && i < originalStroke.StylusPoints.Count; i++)
|
||||
{
|
||||
segmentPoints.Add(originalStroke.StylusPoints[i]);
|
||||
}
|
||||
|
||||
if (segmentPoints.Count < 2) return null;
|
||||
|
||||
// 创建分段墨迹
|
||||
var segmentStroke = new Stroke(segmentPoints)
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
|
||||
// 创建分段的视觉元素
|
||||
var geometry = segmentStroke.GetGeometry();
|
||||
if (geometry == null) return null;
|
||||
|
||||
var drawingAttribs = segmentStroke.DrawingAttributes;
|
||||
var path = new Path
|
||||
{
|
||||
Data = geometry,
|
||||
Stroke = new SolidColorBrush(drawingAttribs.Color),
|
||||
StrokeThickness = drawingAttribs.Width,
|
||||
StrokeStartLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
|
||||
StrokeEndLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
|
||||
StrokeLineJoin = drawingAttribs.IsHighlighter ? PenLineJoin.Miter : PenLineJoin.Round,
|
||||
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null,
|
||||
Opacity = opacity,
|
||||
UseLayoutRounding = false,
|
||||
SnapsToDevicePixels = false
|
||||
};
|
||||
|
||||
// 设置位置
|
||||
var bounds = geometry.Bounds;
|
||||
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
|
||||
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始分段渐隐动画
|
||||
/// </summary>
|
||||
private void StartSegmentedFadeAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual, int totalDuration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 动画时序算法
|
||||
var segmentDuration = CalculateOptimalSegmentDuration(totalDuration, segments.Count);
|
||||
var animationCurve = CreateAppleStyleAnimationCurve(segments.Count, totalDuration);
|
||||
|
||||
// 跟踪动画完成状态
|
||||
var completedSegments = new HashSet<UIElement>();
|
||||
var totalSegments = segments.Count;
|
||||
|
||||
// 渐隐效果 - 使用自然的动画曲线
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
|
||||
// 使用预计算的动画曲线获取延迟时间
|
||||
var delay = animationCurve[i];
|
||||
|
||||
// 使用定时器延迟启动每个分段的动画
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(delay)
|
||||
};
|
||||
|
||||
int segmentIndex = i; // 捕获当前索引
|
||||
timer.Tick += (sender, e) =>
|
||||
{
|
||||
StartSingleSegmentFadeAnimation(segment, segmentDuration, () =>
|
||||
{
|
||||
// 动画完成回调
|
||||
lock (completedSegments)
|
||||
{
|
||||
completedSegments.Add(segment);
|
||||
|
||||
// 检查是否所有分段都完成了
|
||||
if (completedSegments.Count >= totalSegments)
|
||||
{
|
||||
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
||||
}
|
||||
}
|
||||
});
|
||||
timer.Stop();
|
||||
};
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
// 设置一个安全超时定时器,防止无限等待
|
||||
var safetyTimeout = totalDuration + (segments.Count * segmentDuration) + 1200; // 额外1.2秒缓冲,确保动画完整
|
||||
var safetyTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(safetyTimeout)
|
||||
};
|
||||
|
||||
safetyTimer.Tick += (sender, e) =>
|
||||
{
|
||||
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
||||
safetyTimer.Stop();
|
||||
};
|
||||
|
||||
safetyTimer.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个分段的渐隐动画
|
||||
/// </summary>
|
||||
private void StartSingleSegmentFadeAnimation(UIElement segment, int duration, Action onCompleted = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只使用透明度动画,保持墨迹原有粗细
|
||||
var fadeAnimation = new DoubleAnimation
|
||||
{
|
||||
From = segment.Opacity,
|
||||
To = 0.0,
|
||||
Duration = TimeSpan.FromMilliseconds(duration),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } // 更平滑的缓动
|
||||
};
|
||||
|
||||
// 添加动画完成事件
|
||||
if (onCompleted != null)
|
||||
{
|
||||
fadeAnimation.Completed += (sender, e) =>
|
||||
{
|
||||
onCompleted?.Invoke();
|
||||
};
|
||||
}
|
||||
|
||||
// 只应用透明度动画,不改变墨迹大小
|
||||
segment.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"单个分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
// 即使失败也要调用完成回调
|
||||
onCompleted?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理分段动画
|
||||
/// </summary>
|
||||
private void CleanupSegmentedAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 移除所有分段
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (parent != null && parent.Children.Contains(segment))
|
||||
{
|
||||
parent.Children.Remove(segment);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(segment))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理原始墨迹
|
||||
OnAnimationCompleted(originalVisual, originalStroke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理分段动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简单渐隐动画(备用方案)
|
||||
/// </summary>
|
||||
private void StartSimpleFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fadeAnimation = new DoubleAnimation
|
||||
{
|
||||
From = currentOpacity,
|
||||
To = 0.0,
|
||||
Duration = TimeSpan.FromMilliseconds(duration),
|
||||
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
|
||||
};
|
||||
|
||||
fadeAnimation.Completed += (sender, e) => OnAnimationCompleted(visual, stroke);
|
||||
visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"简单渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
OnAnimationCompleted(visual, stroke);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算墨迹的实际长度
|
||||
/// </summary>
|
||||
private double CalculateStrokeLength(StylusPointCollection points)
|
||||
{
|
||||
if (points.Count < 2) return 0;
|
||||
|
||||
double totalLength = 0;
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
var p1 = points[i - 1].ToPoint();
|
||||
var p2 = points[i].ToPoint();
|
||||
totalLength += Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
|
||||
}
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据墨迹特性计算最优分段数量 - 平衡速度与完整性
|
||||
/// </summary>
|
||||
private int CalculateOptimalSegmentCount(int pointCount, double strokeLength)
|
||||
{
|
||||
// 平衡速度与完整性,确保动画效果的同时提高速度
|
||||
const double PIXELS_PER_SEGMENT = 12.0; // 每段适中长度,平衡效果与速度
|
||||
const int MIN_SEGMENTS = 5; // 适当的最小分段数,确保动画效果
|
||||
const int MAX_SEGMENTS = 100; // 适中的最大分段数,平衡性能与效果
|
||||
|
||||
// 根据长度计算基础分段数
|
||||
var lengthBasedSegments = Math.Max(MIN_SEGMENTS, (int)(strokeLength / PIXELS_PER_SEGMENT));
|
||||
|
||||
// 根据点密度调整,平衡效果与速度
|
||||
var density = pointCount > 0 ? strokeLength / pointCount : 1;
|
||||
var densityFactor = Math.Max(0.4, Math.Min(2.5, density / 1.8));
|
||||
|
||||
var finalSegments = (int)(lengthBasedSegments * densityFactor);
|
||||
|
||||
// 对于短墨迹,确保至少有4个分段
|
||||
if (pointCount <= 5)
|
||||
{
|
||||
finalSegments = Math.Max(finalSegments, 4);
|
||||
}
|
||||
|
||||
// 限制在合理范围内
|
||||
return Math.Min(MAX_SEGMENTS, Math.Max(MIN_SEGMENTS, finalSegments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算最优的单段动画持续时间 - 平衡速度与完整性
|
||||
/// </summary>
|
||||
private int CalculateOptimalSegmentDuration(int totalDuration, int segmentCount)
|
||||
{
|
||||
// 平衡速度与动画完整性
|
||||
var baseDuration = totalDuration / Math.Max(segmentCount, 1);
|
||||
var minDuration = 150; // 每段最少150ms,确保动画完整显示
|
||||
var maxDuration = 500; // 每段最多500ms,平衡速度与完整性
|
||||
|
||||
return Math.Max(minDuration, Math.Min(maxDuration, baseDuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建优化的动画时间曲线 - 平衡速度与完整性
|
||||
/// </summary>
|
||||
private int[] CreateAppleStyleAnimationCurve(int segmentCount, int totalDuration)
|
||||
{
|
||||
var curve = new int[segmentCount];
|
||||
|
||||
// 平衡速度与完整性,确保动画有足够时间播放
|
||||
var availableTime = totalDuration * 0.6; // 使用60%的总时间,给动画留足够缓冲
|
||||
var delayBetweenSegments = Math.Max(60, availableTime / Math.Max(segmentCount, 1));
|
||||
|
||||
for (int i = 0; i < segmentCount; i++)
|
||||
{
|
||||
// 线性延迟,确保每个分段都有足够时间
|
||||
curve[i] = (int)(i * delayBetweenSegments);
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画完成后的统一处理
|
||||
/// </summary>
|
||||
private void OnAnimationCompleted(UIElement visual, Stroke stroke)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从父容器中移除墨迹
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(visual);
|
||||
}
|
||||
|
||||
RemoveStroke(stroke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"渐隐动画完成后清理墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
var analyzer = new InkAnalyzer();
|
||||
analyzer.AddStrokes(strokes);
|
||||
analyzer.SetStrokesType(strokes, System.Windows.Ink.StrokeType.Drawing);
|
||||
analyzer.SetStrokesType(strokes, StrokeType.Drawing);
|
||||
|
||||
AnalysisAlternate analysisAlternate = null;
|
||||
int strokesCount = strokes.Count;
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 墨迹平滑配置类
|
||||
/// </summary>
|
||||
public class InkSmoothingConfig
|
||||
{
|
||||
// 基本平滑参数
|
||||
public double SmoothingStrength { get; set; } = 0.4;
|
||||
public double ResampleInterval { get; set; } = 2.5;
|
||||
public int InterpolationSteps { get; set; } = 12;
|
||||
|
||||
// 贝塞尔曲线参数
|
||||
public bool UseAdaptiveInterpolation { get; set; } = true;
|
||||
public double CurveTension { get; set; } = 0.3;
|
||||
public double MinCurvatureThreshold { get; set; } = 0.1;
|
||||
public double MaxCurvatureThreshold { get; set; } = 0.8;
|
||||
|
||||
// 性能参数
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
public bool UseAsyncProcessing { get; set; } = true;
|
||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||
public int MaxPointsPerStroke { get; set; } = 10000;
|
||||
|
||||
// 质量设置
|
||||
public SmoothingQuality Quality { get; set; } = SmoothingQuality.Balanced;
|
||||
|
||||
public enum SmoothingQuality
|
||||
{
|
||||
Performance, // 性能优先
|
||||
Balanced, // 平衡
|
||||
Quality // 质量优先
|
||||
}
|
||||
|
||||
// 兼容性枚举
|
||||
public enum InkSmoothingQuality
|
||||
{
|
||||
HighPerformance = 0, // 高性能低质量
|
||||
Balanced = 1, // 平衡
|
||||
HighQuality = 2 // 高质量低性能
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从设置中加载配置
|
||||
/// </summary>
|
||||
public static InkSmoothingConfig FromSettings()
|
||||
{
|
||||
var config = new InkSmoothingConfig();
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试从MainWindow.Settings加载配置(兼容性)
|
||||
if (MainWindow.Settings?.Canvas != null)
|
||||
{
|
||||
config.Quality = (SmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality;
|
||||
config.UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration;
|
||||
config.UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing;
|
||||
config.MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"加载平滑配置失败: {ex.Message}");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用质量设置
|
||||
/// </summary>
|
||||
public void ApplyQualitySettings()
|
||||
{
|
||||
switch (Quality)
|
||||
{
|
||||
case SmoothingQuality.Performance:
|
||||
SmoothingStrength = 0.2;
|
||||
ResampleInterval = 4.0;
|
||||
InterpolationSteps = 6;
|
||||
UseAdaptiveInterpolation = false;
|
||||
CurveTension = 0.2;
|
||||
MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2);
|
||||
break;
|
||||
|
||||
case SmoothingQuality.Balanced:
|
||||
SmoothingStrength = 0.4;
|
||||
ResampleInterval = 2.5;
|
||||
InterpolationSteps = 12;
|
||||
UseAdaptiveInterpolation = true;
|
||||
CurveTension = 0.3;
|
||||
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||
break;
|
||||
|
||||
case SmoothingQuality.Quality:
|
||||
SmoothingStrength = 0.6;
|
||||
ResampleInterval = 1.5;
|
||||
InterpolationSteps = 20;
|
||||
UseAdaptiveInterpolation = true;
|
||||
CurveTension = 0.4;
|
||||
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置到设置
|
||||
/// </summary>
|
||||
public void SaveToSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 尝试保存到MainWindow.Settings(兼容性)
|
||||
if (MainWindow.Settings?.Canvas != null)
|
||||
{
|
||||
MainWindow.Settings.Canvas.InkSmoothingQuality = (int)Quality;
|
||||
MainWindow.Settings.Canvas.UseHardwareAcceleration = UseHardwareAcceleration;
|
||||
MainWindow.Settings.Canvas.UseAsyncInkSmoothing = UseAsyncProcessing;
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = MaxConcurrentTasks;
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"保存平滑配置失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证配置参数
|
||||
/// </summary>
|
||||
public bool Validate()
|
||||
{
|
||||
return SmoothingStrength >= 0.0 && SmoothingStrength <= 1.0 &&
|
||||
ResampleInterval > 0.0 &&
|
||||
InterpolationSteps > 0 && InterpolationSteps <= 50 &&
|
||||
CurveTension >= 0.0 && CurveTension <= 1.0 &&
|
||||
MaxConcurrentTasks > 0 &&
|
||||
MaxPointsPerStroke > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置摘要
|
||||
/// </summary>
|
||||
public string GetSummary()
|
||||
{
|
||||
return $"质量: {Quality}, 强度: {SmoothingStrength:F2}, 间隔: {ResampleInterval:F1}, " +
|
||||
$"步数: {InterpolationSteps}, 自适应: {UseAdaptiveInterpolation}, " +
|
||||
$"张力: {CurveTension:F2}, 硬件加速: {UseHardwareAcceleration}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一的墨迹平滑管理器,整合异步处理和硬件加速
|
||||
/// </summary>
|
||||
public class InkSmoothingManager : IDisposable
|
||||
{
|
||||
private readonly AsyncAdvancedBezierSmoothing _asyncSmoothing;
|
||||
private readonly HardwareAcceleratedInkProcessor _hardwareProcessor;
|
||||
private readonly InkSmoothingPerformanceMonitor _performanceMonitor;
|
||||
private readonly InkSmoothingConfig _config;
|
||||
private readonly Dispatcher _uiDispatcher;
|
||||
private bool _disposed;
|
||||
|
||||
public InkSmoothingManager(Dispatcher uiDispatcher)
|
||||
{
|
||||
_uiDispatcher = uiDispatcher;
|
||||
_config = InkSmoothingConfig.FromSettings();
|
||||
_config.ApplyQualitySettings();
|
||||
|
||||
_asyncSmoothing = new AsyncAdvancedBezierSmoothing(uiDispatcher)
|
||||
{
|
||||
SmoothingStrength = _config.SmoothingStrength,
|
||||
ResampleInterval = _config.ResampleInterval,
|
||||
InterpolationSteps = _config.InterpolationSteps,
|
||||
UseHardwareAcceleration = _config.UseHardwareAcceleration,
|
||||
MaxConcurrentTasks = _config.MaxConcurrentTasks
|
||||
};
|
||||
|
||||
_hardwareProcessor = new HardwareAcceleratedInkProcessor();
|
||||
_performanceMonitor = new InkSmoothingPerformanceMonitor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平滑笔画(自动选择最佳方法)
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeAsync(Stroke originalStroke,
|
||||
Action<Stroke, Stroke> onCompleted = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Stroke result = originalStroke;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.UseAsyncProcessing)
|
||||
{
|
||||
// 使用异步处理
|
||||
result = await _asyncSmoothing.SmoothStrokeAsync(originalStroke, onCompleted, cancellationToken);
|
||||
}
|
||||
else if (_config.UseHardwareAcceleration)
|
||||
{
|
||||
// 使用硬件加速但同步处理
|
||||
result = await _hardwareProcessor.SmoothStrokeWithGPU(originalStroke);
|
||||
onCompleted?.Invoke(originalStroke, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 回退到传统同步处理
|
||||
result = await Task.Run(() =>
|
||||
{
|
||||
var traditionalSmoothing = new AdvancedBezierSmoothing();
|
||||
return traditionalSmoothing.SmoothStroke(originalStroke);
|
||||
}, cancellationToken);
|
||||
onCompleted?.Invoke(originalStroke, result);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
result = originalStroke;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"墨迹平滑失败: {ex.Message}");
|
||||
result = originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_performanceMonitor.RecordProcessingTime(stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步平滑笔画(用于向后兼容)
|
||||
/// </summary>
|
||||
public Stroke SmoothStroke(Stroke originalStroke)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Stroke result;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.UseHardwareAcceleration)
|
||||
{
|
||||
// 使用硬件加速的同步版本
|
||||
var task = _hardwareProcessor.SmoothStrokeWithGPU(originalStroke);
|
||||
task.Wait(5000); // 5秒超时
|
||||
result = task.Status == TaskStatus.RanToCompletion ? task.Result : originalStroke;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 传统同步处理
|
||||
var traditionalSmoothing = new AdvancedBezierSmoothing();
|
||||
result = traditionalSmoothing.SmoothStroke(originalStroke);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"同步墨迹平滑失败: {ex.Message}");
|
||||
result = originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_performanceMonitor.RecordProcessingTime(stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置
|
||||
/// </summary>
|
||||
public void UpdateConfig()
|
||||
{
|
||||
var newConfig = InkSmoothingConfig.FromSettings();
|
||||
newConfig.ApplyQualitySettings();
|
||||
|
||||
_asyncSmoothing.SmoothingStrength = newConfig.SmoothingStrength;
|
||||
_asyncSmoothing.ResampleInterval = newConfig.ResampleInterval;
|
||||
_asyncSmoothing.InterpolationSteps = newConfig.InterpolationSteps;
|
||||
_asyncSmoothing.UseHardwareAcceleration = newConfig.UseHardwareAcceleration;
|
||||
_asyncSmoothing.MaxConcurrentTasks = newConfig.MaxConcurrentTasks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取性能统计信息
|
||||
/// </summary>
|
||||
public string GetPerformanceStats()
|
||||
{
|
||||
return $"平均处理时间: {_performanceMonitor.GetAverageProcessingTimeMs():F2}ms, " +
|
||||
$"最大处理时间: {_performanceMonitor.GetMaxProcessingTimeMs():F2}ms, " +
|
||||
$"样本数: {_performanceMonitor.GetSampleCount()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有正在进行的任务
|
||||
/// </summary>
|
||||
public void CancelAllTasks()
|
||||
{
|
||||
_asyncSmoothing?.CancelAllTasks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查系统是否支持硬件加速
|
||||
/// </summary>
|
||||
public static bool IsHardwareAccelerationSupported()
|
||||
{
|
||||
try
|
||||
{
|
||||
return RenderCapability.Tier >= 0x00020000;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取推荐的配置
|
||||
/// </summary>
|
||||
public static InkSmoothingConfig GetRecommendedConfig()
|
||||
{
|
||||
var config = new InkSmoothingConfig();
|
||||
|
||||
// 根据系统性能调整配置
|
||||
var processorCount = Environment.ProcessorCount;
|
||||
var isHardwareAccelerated = IsHardwareAccelerationSupported();
|
||||
|
||||
if (processorCount >= 4 && isHardwareAccelerated)
|
||||
{
|
||||
// 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighQuality;
|
||||
config.UseHardwareAcceleration = true;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
|
||||
}
|
||||
else if (processorCount >= 2)
|
||||
{
|
||||
// 2核以上使用平衡模式
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.Balanced;
|
||||
config.UseHardwareAcceleration = isHardwareAccelerated;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 单核或性能较低的设备使用高性能模式
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighPerformance;
|
||||
config.UseHardwareAcceleration = false;
|
||||
config.UseAsyncProcessing = false;
|
||||
config.MaxConcurrentTasks = 1;
|
||||
}
|
||||
|
||||
config.ApplyQualitySettings();
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用推荐配置到设置
|
||||
/// </summary>
|
||||
public static void ApplyRecommendedSettings()
|
||||
{
|
||||
var config = GetRecommendedConfig();
|
||||
|
||||
MainWindow.Settings.Canvas.InkSmoothingQuality = (int)config.Quality;
|
||||
MainWindow.Settings.Canvas.UseHardwareAcceleration = config.UseHardwareAcceleration;
|
||||
MainWindow.Settings.Canvas.UseAsyncInkSmoothing = config.UseAsyncProcessing;
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = config.MaxConcurrentTasks;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
CancelAllTasks();
|
||||
_asyncSmoothing?.Dispose();
|
||||
_hardwareProcessor?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹平滑事件参数
|
||||
/// </summary>
|
||||
public class InkSmoothingEventArgs : EventArgs
|
||||
{
|
||||
public Stroke OriginalStroke { get; set; }
|
||||
public Stroke SmoothedStroke { get; set; }
|
||||
public TimeSpan ProcessingTime { get; set; }
|
||||
public bool WasAsync { get; set; }
|
||||
public bool UsedHardwareAcceleration { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class IsOutsideOfScreenHelper {
|
||||
public static bool IsOutsideOfScreen(FrameworkElement target) {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class IsOutsideOfScreenHelper
|
||||
{
|
||||
public static bool IsOutsideOfScreen(FrameworkElement target)
|
||||
{
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(target);
|
||||
if (hwndSource is null) {
|
||||
if (hwndSource is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var hWnd = hwndSource.Handle;
|
||||
var targetBounds = GetPixelBoundsToScreen(target);
|
||||
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
var screens = Screen.AllScreens;
|
||||
return !screens.Any(x => x.Bounds.IntersectsWith(targetBounds));
|
||||
|
||||
System.Drawing.Rectangle GetPixelBoundsToScreen(FrameworkElement visual) {
|
||||
Rectangle GetPixelBoundsToScreen(FrameworkElement visual)
|
||||
{
|
||||
var pixelBoundsToScreen = Rect.Empty;
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, 0)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, 0)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, visual.ActualHeight)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, visual.ActualHeight)));
|
||||
return new System.Drawing.Rectangle(
|
||||
return new Rectangle(
|
||||
(int)pixelBoundsToScreen.X, (int)pixelBoundsToScreen.Y,
|
||||
(int)pixelBoundsToScreen.Width, (int)pixelBoundsToScreen.Height);
|
||||
}
|
||||
|
||||
@@ -1,54 +1,133 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
class LogHelper
|
||||
{
|
||||
public static string LogFile = "Log.txt";
|
||||
private static string LogsFolder = "Logs";
|
||||
private static string AppStartTime = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
private static readonly long MaxLogsFolderSizeBytes = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
public static void NewLog(string str)
|
||||
{
|
||||
WriteLogToFile(str, LogType.Info);
|
||||
WriteLogToFile(str);
|
||||
}
|
||||
|
||||
public static void NewLog(Exception ex)
|
||||
{
|
||||
|
||||
if (ex == null) return;
|
||||
var stackTrace = ex.StackTrace ?? "<no stack trace>";
|
||||
var msg = $"[Exception] Type: {ex.GetType().FullName}\nMessage: {ex.Message}\nStackTrace: {stackTrace}";
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
msg += $"\nInnerException: {ex.InnerException.GetType().FullName} - {ex.InnerException.Message}\n{ex.InnerException.StackTrace}";
|
||||
}
|
||||
WriteLogToFile(msg, LogType.Error);
|
||||
}
|
||||
|
||||
public static void WriteLogToFile(string str, LogType logType = LogType.Info)
|
||||
{
|
||||
string strLogType = "Info";
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Event:
|
||||
strLogType = "Event";
|
||||
break;
|
||||
case LogType.Trace:
|
||||
strLogType = "Trace";
|
||||
break;
|
||||
case LogType.Error:
|
||||
strLogType = "Error";
|
||||
break;
|
||||
}
|
||||
// 检查日志是否启用
|
||||
if (MainWindow.Settings != null && MainWindow.Settings.Advanced != null && !MainWindow.Settings.Advanced.IsLogEnabled) return;
|
||||
|
||||
string strLogType = logType.ToString();
|
||||
try
|
||||
{
|
||||
var file = App.RootPath + LogFile;
|
||||
string file;
|
||||
|
||||
// 检查是否启用了日期保存功能
|
||||
if (MainWindow.Settings != null && MainWindow.Settings.Advanced != null && MainWindow.Settings.Advanced.IsSaveLogByDate)
|
||||
{
|
||||
// 确保Logs文件夹存在
|
||||
string logsPath = Path.Combine(App.RootPath, LogsFolder);
|
||||
if (!Directory.Exists(logsPath))
|
||||
{
|
||||
Directory.CreateDirectory(logsPath);
|
||||
}
|
||||
|
||||
// 检查Logs文件夹大小,如果超过5MB则清空
|
||||
CheckAndCleanLogsFolder(logsPath);
|
||||
|
||||
// 使用软件启动时间作为日志文件名
|
||||
file = Path.Combine(logsPath, $"Log_{AppStartTime}.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
file = App.RootPath + LogFile;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(App.RootPath))
|
||||
{
|
||||
Directory.CreateDirectory(App.RootPath);
|
||||
}
|
||||
StreamWriter sw = new StreamWriter(file, true);
|
||||
sw.WriteLine(string.Format("{0} [{1}] {2}", DateTime.Now.ToString("O"), strLogType, str));
|
||||
sw.Close();
|
||||
|
||||
var threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
var callingMethod = new StackTrace(2, true).GetFrame(0);
|
||||
string callerInfo = "<unknown>";
|
||||
if (callingMethod != null)
|
||||
{
|
||||
var method = callingMethod.GetMethod();
|
||||
if (method != null)
|
||||
{
|
||||
var className = method.DeclaringType != null ? method.DeclaringType.FullName : "<no class>";
|
||||
callerInfo = $"{className}.{method.Name}";
|
||||
}
|
||||
}
|
||||
string logLine = string.Format("{0} [T{1}] [{2}] [{3}] {4}", DateTime.Now.ToString("O"), threadId, strLogType, callerInfo, str);
|
||||
using (StreamWriter sw = new StreamWriter(file, true))
|
||||
{
|
||||
sw.WriteLine(logLine);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void CheckAndCleanLogsFolder(string logsPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
long totalSize = 0;
|
||||
DirectoryInfo dirInfo = new DirectoryInfo(logsPath);
|
||||
|
||||
// 如果目录不存在,直接返回
|
||||
if (!dirInfo.Exists) return;
|
||||
|
||||
// 计算文件夹大小
|
||||
foreach (FileInfo file in dirInfo.GetFiles())
|
||||
{
|
||||
totalSize += file.Length;
|
||||
}
|
||||
|
||||
// 如果超过5MB,清空文件夹
|
||||
if (totalSize > MaxLogsFolderSizeBytes)
|
||||
{
|
||||
foreach (FileInfo file in dirInfo.GetFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// 记录清理操作
|
||||
string cleanupMessage = $"Logs folder exceeded size limit ({totalSize / 1024.0 / 1024.0:F2} MB > {MaxLogsFolderSizeBytes / 1024.0 / 1024.0:F2} MB). Folder cleaned.";
|
||||
using (StreamWriter sw = new StreamWriter(Path.Combine(logsPath, $"Log_{AppStartTime}.txt"), true))
|
||||
{
|
||||
sw.WriteLine($"{DateTime.Now:O} [Cleanup] {cleanupMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
internal static void WriteLogToFile(string v, object warning)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
WriteLogToFile($"[Warning] {v}", LogType.Warning);
|
||||
}
|
||||
|
||||
public enum LogType
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 创建显示笔迹的类
|
||||
/// </summary>
|
||||
public StrokeVisual() : this(new DrawingAttributes()
|
||||
public StrokeVisual() : this(new DrawingAttributes
|
||||
{
|
||||
Color = Colors.Red,
|
||||
//FitToCurve = true,
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows.Ink;
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT墨迹管理器 - 负责PPT中墨迹的保存、加载和同步
|
||||
/// </summary>
|
||||
public class PPTInkManager : IDisposable
|
||||
{
|
||||
#region Properties
|
||||
public bool IsAutoSaveEnabled { get; set; } = true;
|
||||
public string AutoSaveLocation { get; set; } = "";
|
||||
public StrokeCollection CurrentStrokes { get; private set; } = new StrokeCollection();
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private MemoryStream[] _memoryStreams;
|
||||
private int _maxSlides = 100;
|
||||
private string _currentPresentationId = "";
|
||||
private readonly object _lockObject = new object();
|
||||
private bool _disposed;
|
||||
|
||||
// 墨迹锁定机制,防止翻页时的墨迹冲突
|
||||
private DateTime _inkLockUntil = DateTime.MinValue;
|
||||
private int _lockedSlideIndex = -1;
|
||||
private const int InkLockMilliseconds = 500;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public PPTInkManager()
|
||||
{
|
||||
InitializeMemoryStreams();
|
||||
}
|
||||
|
||||
private void InitializeMemoryStreams()
|
||||
{
|
||||
_memoryStreams = new MemoryStream[_maxSlides + 2];
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 初始化新的演示文稿
|
||||
/// </summary>
|
||||
public void InitializePresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 完全清理之前的墨迹状态
|
||||
ClearAllStrokes();
|
||||
|
||||
// 重置墨迹锁定状态
|
||||
_inkLockUntil = DateTime.MinValue;
|
||||
_lockedSlideIndex = -1;
|
||||
|
||||
// 生成演示文稿唯一标识符
|
||||
_currentPresentationId = GeneratePresentationId(presentation);
|
||||
|
||||
// 重新初始化内存流数组
|
||||
var slideCount = presentation.Slides.Count;
|
||||
_memoryStreams = new MemoryStream[slideCount + 2];
|
||||
|
||||
// 如果启用自动保存,尝试加载已保存的墨迹
|
||||
if (IsAutoSaveEnabled && !string.IsNullOrEmpty(AutoSaveLocation))
|
||||
{
|
||||
LoadSavedStrokes();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化演示文稿墨迹管理失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前页面的墨迹
|
||||
/// </summary>
|
||||
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查墨迹锁定
|
||||
if (!CanWriteInk(slideIndex))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (slideIndex < _memoryStreams.Length)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
strokes.Save(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
// 释放旧的内存流
|
||||
_memoryStreams[slideIndex]?.Dispose();
|
||||
_memoryStreams[slideIndex] = ms;
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定页面的墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection LoadSlideStrokes(int slideIndex)
|
||||
{
|
||||
if (slideIndex <= 0) return new StrokeCollection();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (slideIndex < _memoryStreams.Length && _memoryStreams[slideIndex] != null && _memoryStreams[slideIndex].Length > 0)
|
||||
{
|
||||
_memoryStreams[slideIndex].Position = 0;
|
||||
var strokes = new StrokeCollection(_memoryStreams[slideIndex]);
|
||||
LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace);
|
||||
return strokes;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面并加载墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果有当前墨迹,先保存到正确的页面
|
||||
if (currentStrokes != null && currentStrokes.Count > 0)
|
||||
{
|
||||
// 确定要保存的页面索引
|
||||
int saveToSlideIndex = _lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex;
|
||||
|
||||
// 确保页面索引有效
|
||||
if (saveToSlideIndex > 0 && saveToSlideIndex < _memoryStreams.Length)
|
||||
{
|
||||
SaveCurrentSlideStrokes(saveToSlideIndex, currentStrokes);
|
||||
LogHelper.WriteLogToFile($"已保存第{saveToSlideIndex}页墨迹,墨迹数量: {currentStrokes.Count}", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置墨迹锁定
|
||||
LockInkForSlide(slideIndex);
|
||||
|
||||
// 加载新页面的墨迹
|
||||
var newStrokes = LoadSlideStrokes(slideIndex);
|
||||
LogHelper.WriteLogToFile($"已切换到第{slideIndex}页,加载墨迹数量: {newStrokes.Count}", LogHelper.LogType.Trace);
|
||||
|
||||
return newStrokes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换到第{slideIndex}页失败: {ex}", LogHelper.LogType.Error);
|
||||
return new StrokeCollection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有墨迹到文件
|
||||
/// </summary>
|
||||
public void SaveAllStrokesToFile(Presentation presentation)
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var folderPath = GetPresentationFolderPath();
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
// 保存位置信息
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(folderPath, "Position"), _lockedSlideIndex.ToString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存位置信息失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 保存所有页面的墨迹
|
||||
int savedCount = 0;
|
||||
for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++)
|
||||
{
|
||||
if (_memoryStreams[i] != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_memoryStreams[i].Length > 8)
|
||||
{
|
||||
var srcBuf = new byte[_memoryStreams[i].Length];
|
||||
_memoryStreams[i].Position = 0;
|
||||
var byteLength = _memoryStreams[i].Read(srcBuf, 0, srcBuf.Length);
|
||||
|
||||
var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk");
|
||||
File.WriteAllBytes(filePath, srcBuf);
|
||||
savedCount++;
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 删除空的墨迹文件
|
||||
var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存第{i}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存墨迹到文件失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载已保存的墨迹
|
||||
/// </summary>
|
||||
public void LoadSavedStrokes()
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation)) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var folderPath = GetPresentationFolderPath();
|
||||
if (!Directory.Exists(folderPath)) return;
|
||||
|
||||
var files = new DirectoryInfo(folderPath).GetFiles("*.icstk");
|
||||
int loadedCount = 0;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (int.TryParse(Path.GetFileNameWithoutExtension(file.Name), out int slideIndex))
|
||||
{
|
||||
if (slideIndex > 0 && slideIndex < _memoryStreams.Length)
|
||||
{
|
||||
var fileBytes = File.ReadAllBytes(file.FullName);
|
||||
_memoryStreams[slideIndex] = new MemoryStream(fileBytes);
|
||||
_memoryStreams[slideIndex].Position = 0;
|
||||
loadedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载墨迹文件{file.Name}失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有墨迹
|
||||
/// </summary>
|
||||
public void ClearAllStrokes()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _memoryStreams.Length; i++)
|
||||
{
|
||||
_memoryStreams[i]?.Dispose();
|
||||
_memoryStreams[i] = null;
|
||||
}
|
||||
|
||||
CurrentStrokes.Clear();
|
||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻页后锁定墨迹写入
|
||||
/// </summary>
|
||||
public void LockInkForSlide(int slideIndex)
|
||||
{
|
||||
_inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds);
|
||||
_lockedSlideIndex = slideIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以写入墨迹
|
||||
/// </summary>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
if (DateTime.Now < _inkLockUntil) return false;
|
||||
if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false;
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private string GeneratePresentationId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationPath = presentation.FullName;
|
||||
var fileHash = GetFileHash(presentationPath);
|
||||
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"生成演示文稿ID失败: {ex}", LogHelper.LogType.Error);
|
||||
return $"unknown_{DateTime.Now.Ticks}";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFileHash(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return "unknown";
|
||||
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath));
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error);
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPresentationFolderPath()
|
||||
{
|
||||
return Path.Combine(AutoSaveLocation, "Auto Saved - Presentations", _currentPresentationId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
ClearAllStrokes();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,468 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT UI管理器 - 统一管理PPT相关的UI更新和样式设置
|
||||
/// </summary>
|
||||
public class PPTUIManager
|
||||
{
|
||||
#region Properties
|
||||
public bool ShowPPTButton { get; set; } = true;
|
||||
public int PPTButtonsDisplayOption { get; set; } = 2222;
|
||||
public int PPTSButtonsOption { get; set; } = 221;
|
||||
public int PPTBButtonsOption { get; set; } = 121;
|
||||
public int PPTLSButtonPosition { get; set; } = 0;
|
||||
public int PPTRSButtonPosition { get; set; } = 0;
|
||||
public int PPTLBButtonPosition { get; set; } = 0;
|
||||
public int PPTRBButtonPosition { get; set; } = 0;
|
||||
public bool EnablePPTButtonPageClickable { get; set; } = true;
|
||||
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public PPTUIManager(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
_dispatcher = _mainWindow.Dispatcher;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 更新PPT连接状态UI
|
||||
/// </summary>
|
||||
public void UpdateConnectionStatus(bool isConnected)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isConnected)
|
||||
{
|
||||
_mainWindow.StackPanelPPTControls.Visibility = Visibility.Visible;
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainWindow.StackPanelPPTControls.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
|
||||
HideAllNavigationPanels();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新PPT连接状态UI失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新幻灯片放映状态UI
|
||||
/// </summary>
|
||||
public void UpdateSlideShowStatus(bool isInSlideShow, int currentSlide = 0, int totalSlides = 0)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isInSlideShow)
|
||||
{
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
|
||||
|
||||
if (currentSlide > 0 && totalSlides > 0)
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
|
||||
UpdateNavigationPanelsVisibility();
|
||||
UpdateNavigationButtonStyles();
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
|
||||
HideAllNavigationPanels();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新幻灯片放映状态UI失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新当前页码显示
|
||||
/// </summary>
|
||||
public void UpdateCurrentSlideNumber(int currentSlide, int totalSlides)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新页码显示失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT放映状态变化
|
||||
/// </summary>
|
||||
public void OnSlideShowStateChanged(bool isInSlideShow)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isInSlideShow)
|
||||
{
|
||||
// 如果不在放映模式,隐藏所有导航面板
|
||||
HideAllNavigationPanels();
|
||||
LogHelper.WriteLogToFile("PPT放映状态变化:隐藏导航面板", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理PPT放映状态变化失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新导航面板显示状态
|
||||
/// </summary>
|
||||
public void UpdateNavigationPanelsVisibility()
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否应该显示PPT按钮
|
||||
// 不仅要检查按钮设置,还要确保确实在PPT放映模式下
|
||||
bool shouldShowButtons = ShowPPTButton &&
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
_mainWindow.PPTManager?.IsInSlideShow == true;
|
||||
|
||||
if (!shouldShowButtons)
|
||||
{
|
||||
HideAllNavigationPanels();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置侧边按钮位置
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2);
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2);
|
||||
|
||||
// 设置底部按钮水平位置
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.Margin = new Thickness(6 + PPTLBButtonPosition, 0, 0, 6);
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.Margin = new Thickness(0, 0, 6 + PPTRBButtonPosition, 6);
|
||||
|
||||
// 根据显示选项设置面板可见性
|
||||
var displayOption = PPTButtonsDisplayOption.ToString();
|
||||
if (displayOption.Length >= 4)
|
||||
{
|
||||
var options = displayOption.ToCharArray();
|
||||
|
||||
// 左下角面板
|
||||
if (options[0] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftBottomPanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 右下角面板
|
||||
if (options[1] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.RightBottomPanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 左侧面板
|
||||
if (options[2] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftSidePanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 右侧面板
|
||||
if (options[3] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.RightSidePanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新导航面板显示状态失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新导航按钮样式
|
||||
/// </summary>
|
||||
public void UpdateNavigationButtonStyles()
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateSideButtonStyles();
|
||||
UpdateBottomButtonStyles();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新导航按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏所有导航面板
|
||||
/// </summary>
|
||||
public void HideAllNavigationPanels()
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"隐藏导航面板失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示/隐藏侧边栏退出按钮
|
||||
/// </summary>
|
||||
public void UpdateSidebarExitButtons(bool show)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var visibility = show ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
if (_mainWindow.BtnExitPptFromSidebarLeft != null)
|
||||
_mainWindow.BtnExitPptFromSidebarLeft.Visibility = visibility;
|
||||
|
||||
if (_mainWindow.BtnExitPptFromSidebarRight != null)
|
||||
_mainWindow.BtnExitPptFromSidebarRight.Visibility = visibility;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新侧边栏退出按钮失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置浮动栏透明度
|
||||
/// </summary>
|
||||
public void SetFloatingBarOpacity(double opacity)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.ViewboxFloatingBar.Opacity = opacity;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置浮动栏透明度失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置主面板边距
|
||||
/// </summary>
|
||||
public void SetMainPanelMargin(Thickness margin)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.ViewBoxStackPanelMain.Margin = margin;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置主面板边距失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private void UpdateSideButtonStyles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var sideOption = PPTSButtonsOption.ToString();
|
||||
if (sideOption.Length < 3) return;
|
||||
|
||||
var options = sideOption.ToCharArray();
|
||||
|
||||
// 页码按钮显示
|
||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||
_mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility;
|
||||
|
||||
// 透明度设置
|
||||
var opacity = options[1] == '2' ? 0.5 : 1.0;
|
||||
_mainWindow.PPTBtnLSBorder.Opacity = opacity;
|
||||
_mainWindow.PPTBtnRSBorder.Opacity = opacity;
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新侧边按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBottomButtonStyles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bottomOption = PPTBButtonsOption.ToString();
|
||||
if (bottomOption.Length < 3) return;
|
||||
|
||||
var options = bottomOption.ToCharArray();
|
||||
|
||||
// 页码按钮显示
|
||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||
_mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility;
|
||||
|
||||
// 透明度设置
|
||||
var opacity = options[1] == '2' ? 0.5 : 1.0;
|
||||
_mainWindow.PPTBtnLBBorder.Opacity = opacity;
|
||||
_mainWindow.PPTBtnRBBorder.Opacity = opacity;
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton)
|
||||
{
|
||||
try
|
||||
{
|
||||
Color backgroundColor, borderColor, foregroundColor, feedbackColor;
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
backgroundColor = Color.FromRgb(39, 39, 42);
|
||||
borderColor = Color.FromRgb(82, 82, 91);
|
||||
foregroundColor = Colors.White;
|
||||
feedbackColor = Colors.White;
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundColor = Color.FromRgb(244, 244, 245);
|
||||
borderColor = Color.FromRgb(161, 161, 170);
|
||||
foregroundColor = Color.FromRgb(39, 39, 42);
|
||||
feedbackColor = Color.FromRgb(24, 24, 27);
|
||||
}
|
||||
|
||||
// 应用背景和边框颜色
|
||||
var backgroundBrush = new SolidColorBrush(backgroundColor);
|
||||
var borderBrush = new SolidColorBrush(borderColor);
|
||||
|
||||
leftBorder.Background = backgroundBrush;
|
||||
leftBorder.BorderBrush = borderBrush;
|
||||
rightBorder.Background = backgroundBrush;
|
||||
rightBorder.BorderBrush = borderBrush;
|
||||
|
||||
// 应用图标和文字颜色
|
||||
var foregroundBrush = new SolidColorBrush(foregroundColor);
|
||||
var feedbackBrush = new SolidColorBrush(feedbackColor);
|
||||
|
||||
if (isSideButton)
|
||||
{
|
||||
ApplySideButtonColors(foregroundBrush, feedbackBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyBottomButtonColors(foregroundBrush, feedbackBrush);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
|
||||
{
|
||||
// 图标颜色
|
||||
_mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush;
|
||||
|
||||
// 反馈背景颜色
|
||||
_mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
|
||||
// 文字颜色
|
||||
TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush);
|
||||
TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush);
|
||||
}
|
||||
|
||||
private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
|
||||
{
|
||||
// 图标颜色
|
||||
_mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush;
|
||||
|
||||
// 反馈背景颜色
|
||||
_mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
|
||||
// 文字颜色
|
||||
TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush);
|
||||
TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮控件
|
||||
/// </summary>
|
||||
public class LauncherButton
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 实际按钮控件
|
||||
/// </summary>
|
||||
private readonly SimpleStackPanel _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 获取按钮UI元素
|
||||
/// </summary>
|
||||
public UIElement Element => _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherButton(SuperLauncherPlugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
_plugin = plugin;
|
||||
LogHelper.WriteLogToFile("开始创建启动台按钮");
|
||||
|
||||
// 创建SimpleStackPanel
|
||||
_panel = new SimpleStackPanel
|
||||
{
|
||||
Name = "Launcher_Icon",
|
||||
Orientation = Orientation.Vertical,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = 28,
|
||||
Margin = new Thickness(0, -2, 0, 0),
|
||||
Background = Brushes.Transparent
|
||||
};
|
||||
|
||||
LogHelper.WriteLogToFile("创建SimpleStackPanel完成");
|
||||
|
||||
// 添加图标
|
||||
var image = CreateIconImage();
|
||||
_panel.Children.Add(image);
|
||||
|
||||
// 添加文本
|
||||
TextBlock textBlock = new TextBlock
|
||||
{
|
||||
Text = "启动台",
|
||||
Foreground = Brushes.Black,
|
||||
FontSize = 8,
|
||||
Margin = new Thickness(0, 1, 0, 0),
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
_panel.Children.Add(textBlock);
|
||||
|
||||
// 设置鼠标事件
|
||||
_panel.MouseDown += Panel_MouseDown;
|
||||
_panel.MouseUp += Panel_MouseUp;
|
||||
_panel.MouseLeave += Panel_MouseLeave;
|
||||
|
||||
// 右键菜单支持
|
||||
_panel.ContextMenu = CreateContextMenu();
|
||||
|
||||
// 设置工具提示
|
||||
_panel.ToolTip = "启动台";
|
||||
|
||||
LogHelper.WriteLogToFile("启动台按钮创建完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建右键菜单
|
||||
/// </summary>
|
||||
private ContextMenu CreateContextMenu()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建菜单
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
// 创建位置切换菜单项
|
||||
MenuItem positionMenuItem = new MenuItem();
|
||||
positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
"移至右侧" : "移至左侧";
|
||||
positionMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 切换位置
|
||||
_plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
LauncherButtonPosition.Right : LauncherButtonPosition.Left;
|
||||
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}");
|
||||
};
|
||||
menu.Items.Add(positionMenuItem);
|
||||
|
||||
// 添加设置菜单项
|
||||
MenuItem settingsMenuItem = new MenuItem();
|
||||
settingsMenuItem.Header = "打开设置";
|
||||
settingsMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 打开插件设置窗口
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用反射调用主窗口的ShowPluginSettings方法
|
||||
var method = mainWindow.GetType().GetMethod("ShowPluginSettings");
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(mainWindow, null);
|
||||
LogHelper.WriteLogToFile("已打开插件设置窗口");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
menu.Items.Add(settingsMenuItem);
|
||||
|
||||
return menu;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实际的UI元素
|
||||
/// </summary>
|
||||
[Obsolete("使用Element属性代替")]
|
||||
public UIElement GetUIElement()
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建图标图像
|
||||
/// </summary>
|
||||
private Image CreateIconImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建图像
|
||||
Image image = new Image
|
||||
{
|
||||
Height = 17,
|
||||
Margin = new Thickness(0, 3, 0, 0)
|
||||
};
|
||||
|
||||
// 设置位图缩放模式
|
||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
||||
|
||||
// 创建绘图图像
|
||||
DrawingImage drawingImage = new DrawingImage();
|
||||
DrawingGroup drawingGroup = new DrawingGroup();
|
||||
drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z");
|
||||
|
||||
// 使用提供的应用网格图标
|
||||
GeometryDrawing geometryDrawing = new GeometryDrawing
|
||||
{
|
||||
Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)),
|
||||
Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z")
|
||||
};
|
||||
|
||||
drawingGroup.Children.Add(geometryDrawing);
|
||||
|
||||
// 设置图像源
|
||||
drawingImage.Drawing = drawingGroup;
|
||||
image.Source = drawingImage;
|
||||
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
|
||||
// 返回一个空图像
|
||||
return new Image();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下事件
|
||||
/// </summary>
|
||||
private void Panel_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 提供反馈
|
||||
_panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标按下");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标抬起事件
|
||||
/// </summary>
|
||||
private void Panel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有左键点击才显示启动台窗口
|
||||
if (e.ChangedButton != MouseButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口");
|
||||
|
||||
// 获取按钮在屏幕上的位置
|
||||
Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0));
|
||||
|
||||
// 显示启动台窗口
|
||||
_plugin.ShowLauncherWindow(buttonPosition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标离开事件
|
||||
/// </summary>
|
||||
private void Panel_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public enum LauncherButtonPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// 左侧
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// 右侧
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public class LauncherConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public List<LauncherItem> Items { get; set; } = new List<LauncherItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用项
|
||||
/// </summary>
|
||||
public class LauncherItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可见
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 在启动台中的位置(0-39)
|
||||
/// </summary>
|
||||
public int Position { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已固定位置
|
||||
/// </summary>
|
||||
public bool IsPositionFixed { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 图标缓存
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
private ImageSource _iconCache;
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用程序图标
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ImageSource Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconCache != null)
|
||||
{
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(Path))
|
||||
{
|
||||
// 从文件中获取图标
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从注册表中获取文件类型关联图标
|
||||
string extension = System.IO.Path.GetExtension(Path);
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(fileType))
|
||||
{
|
||||
string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
string[] parts = iconPath.Split(',');
|
||||
string iconFile = parts[0].Trim('"');
|
||||
int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0;
|
||||
|
||||
if (File.Exists(iconFile))
|
||||
{
|
||||
Icon icon = IconExtractor.Extract(iconFile, iconIndex, true);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 返回默认图标
|
||||
return GetDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认图标
|
||||
/// </summary>
|
||||
private ImageSource GetDefaultIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 对于资源管理器,使用特定图标
|
||||
if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 直接从C:\Windows\explorer.exe获取图标
|
||||
string explorerPath = @"C:\Windows\explorer.exe";
|
||||
if (File.Exists(explorerPath))
|
||||
{
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(explorerPath);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取资源管理器图标时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
// 如果获取Windows图标失败,回退到默认图标
|
||||
}
|
||||
|
||||
// 回退到备用图标
|
||||
string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png");
|
||||
if (File.Exists(explorerIconPath))
|
||||
{
|
||||
Uri uri = new Uri(explorerIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回一个简单的默认图标
|
||||
string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png");
|
||||
if (File.Exists(iconPath))
|
||||
{
|
||||
Uri uri = new Uri(iconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
// 如果还是没有找到,尝试使用应用程序图标
|
||||
string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png");
|
||||
if (File.Exists(appIconPath))
|
||||
{
|
||||
Uri uri = new Uri(appIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动应用程序
|
||||
/// </summary>
|
||||
public void Launch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(Path) && !Path.Contains(":\\"))
|
||||
{
|
||||
// 可能是系统命令,如explorer.exe
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用Process.Start启动应用程序
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已启动应用程序: {Path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图标提取工具类
|
||||
/// </summary>
|
||||
public static class IconExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// 从文件中提取图标
|
||||
/// </summary>
|
||||
/// <param name="file">文件路径</param>
|
||||
/// <param name="index">图标索引</param>
|
||||
/// <param name="largeIcon">是否提取大图标</param>
|
||||
/// <returns>提取的图标</returns>
|
||||
public static Icon Extract(string file, int index, bool largeIcon)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr large;
|
||||
IntPtr small;
|
||||
ExtractIconEx(file, index, out large, out small, 1);
|
||||
|
||||
try
|
||||
{
|
||||
return Icon.FromHandle(largeIcon ? large : small);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (large != IntPtr.Zero)
|
||||
DestroyIcon(large);
|
||||
|
||||
if (small != IntPtr.Zero)
|
||||
DestroyIcon(small);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")]
|
||||
private static extern int ExtractIconEx(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string lpszFile,
|
||||
int nIconIndex,
|
||||
out IntPtr phiconLarge,
|
||||
out IntPtr phiconSmall,
|
||||
int nIcons);
|
||||
|
||||
[DllImport("User32.dll")]
|
||||
private static extern int DestroyIcon(IntPtr hIcon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<UserControl x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherSettingsControl"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500" d:DesignWidth="600">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 自定义按钮样式 -->
|
||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.6"/>
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
||||
<Setter Property="Cursor" Value="Arrow"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15" Foreground="Black"/>
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,15">
|
||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 按钮位置 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center" Foreground="Black"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="0,5">
|
||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 应用管理 -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="#CCCCCC" CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 应用列表 -->
|
||||
<DataGrid Grid.Row="0" x:Name="DgApps" AutoGenerateColumns="False" Margin="5"
|
||||
CanUserAddRows="False" CanUserDeleteRows="False"
|
||||
HeadersVisibility="Column" SelectionMode="Single"
|
||||
SelectionChanged="DgApps_SelectionChanged">
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Header="显示" Binding="{Binding IsVisible}" Width="50"/>
|
||||
<DataGridTextColumn Header="名称" Binding="{Binding Name}" Width="150"/>
|
||||
<DataGridTextColumn Header="路径" Binding="{Binding Path}" Width="*"/>
|
||||
<DataGridTextColumn Header="位置" Binding="{Binding Position}" Width="50"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"
|
||||
Background="#FF007ACC" Foreground="White" BorderBrush="#FF005A9B" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"
|
||||
Background="#FF6C757D" Foreground="White" BorderBrush="#FF5A6268" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"
|
||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"
|
||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,396 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Ink_Canvas.Windows;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherSettingsControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherSettingsControl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherSettingsControl(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 设置按钮位置
|
||||
RbtnLeft.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left;
|
||||
RbtnRight.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Right;
|
||||
|
||||
// 绑定应用列表
|
||||
DgApps.ItemsSource = _plugin.LauncherItems;
|
||||
|
||||
// 初始化按钮状态
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新按钮状态
|
||||
/// </summary>
|
||||
private void UpdateButtonStates()
|
||||
{
|
||||
bool hasSelection = DgApps.SelectedItem != null;
|
||||
BtnEdit.IsEnabled = hasSelection;
|
||||
BtnDelete.IsEnabled = hasSelection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置单选按钮选择事件
|
||||
/// </summary>
|
||||
private void RbtnPosition_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
|
||||
LauncherButtonPosition oldPosition = _plugin.Config.ButtonPosition;
|
||||
|
||||
if (sender == RbtnLeft)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Left;
|
||||
}
|
||||
else if (sender == RbtnRight)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Right;
|
||||
}
|
||||
|
||||
// 如果位置发生变化,更新按钮位置
|
||||
if (oldPosition != _plugin.Config.ButtonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更改为: {_plugin.Config.ButtonPosition}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"更新启动台按钮位置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建新的启动项
|
||||
LauncherItem item = new LauncherItem
|
||||
{
|
||||
Name = "",
|
||||
Path = "",
|
||||
IsVisible = true,
|
||||
Position = -1 // 让插件管理器分配位置
|
||||
};
|
||||
|
||||
// 直接显示编辑对话框
|
||||
EditLauncherItem(item, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"添加启动项时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
EditLauncherItem(item, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
// 确认删除
|
||||
MessageBoxResult result = MessageBox.Show(
|
||||
$"确定要删除 {item.Name} 吗?",
|
||||
"删除确认",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
// 从集合中移除
|
||||
_plugin.LauncherItems.Remove(item);
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 如果插件已启用,重新加载启动台按钮
|
||||
if (_plugin.IsEnabled)
|
||||
{
|
||||
_plugin.Disable();
|
||||
_plugin.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果插件未启用,则启用它
|
||||
_plugin.Enable();
|
||||
|
||||
// 通知PluginSettingsWindow刷新插件列表
|
||||
var window = Window.GetWindow(this);
|
||||
if (window is PluginSettingsWindow pluginSettingsWindow)
|
||||
{
|
||||
// 触发刷新
|
||||
pluginSettingsWindow.RefreshPluginList();
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox.Show("设置已保存并应用!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用项选择变更事件
|
||||
/// </summary>
|
||||
private void DgApps_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑启动项
|
||||
/// </summary>
|
||||
/// <param name="item">启动项</param>
|
||||
/// <param name="isNew">是否为新建</param>
|
||||
private void EditLauncherItem(LauncherItem item, bool isNew)
|
||||
{
|
||||
// 创建简单的编辑窗口
|
||||
Window editWindow = new Window
|
||||
{
|
||||
Title = isNew ? "添加" : "编辑应用",
|
||||
Width = 400,
|
||||
Height = 200,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
ResizeMode = ResizeMode.NoResize
|
||||
};
|
||||
|
||||
// 创建编辑表单
|
||||
Grid grid = new Grid
|
||||
{
|
||||
Margin = new Thickness(20)
|
||||
};
|
||||
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(80) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
// 名称输入框
|
||||
TextBlock nameLabel = new TextBlock
|
||||
{
|
||||
Text = "名称:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
TextBox nameTextBox = new TextBox
|
||||
{
|
||||
Text = item.Name,
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
Grid.SetRow(nameLabel, 0);
|
||||
Grid.SetColumn(nameLabel, 0);
|
||||
Grid.SetRow(nameTextBox, 0);
|
||||
Grid.SetColumn(nameTextBox, 1);
|
||||
|
||||
grid.Children.Add(nameLabel);
|
||||
grid.Children.Add(nameTextBox);
|
||||
|
||||
// 路径输入框
|
||||
TextBlock pathLabel = new TextBlock
|
||||
{
|
||||
Text = "路径:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid pathGrid = new Grid();
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength() });
|
||||
|
||||
TextBox pathTextBox = new TextBox
|
||||
{
|
||||
Text = item.Path,
|
||||
Margin = new Thickness(0, 5, 5, 5)
|
||||
};
|
||||
Button browseButton = new Button
|
||||
{
|
||||
Content = "浏览",
|
||||
Padding = new Thickness(5, 0, 5, 0),
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
browseButton.Click += (s, e) =>
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择应用程序",
|
||||
Filter = "应用程序 (*.exe)|*.exe|所有文件 (*.*)|*.*",
|
||||
Multiselect = false,
|
||||
FileName = pathTextBox.Text
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
pathTextBox.Text = dialog.FileName;
|
||||
|
||||
// 如果选择的是.exe文件,自动获取文件名填入名称字段
|
||||
if (Path.GetExtension(dialog.FileName).ToLower() == ".exe")
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(dialog.FileName);
|
||||
// 只有在名称字段为空或者是新建项目时才自动填入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text) || isNew)
|
||||
{
|
||||
nameTextBox.Text = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Grid.SetColumn(pathTextBox, 0);
|
||||
Grid.SetColumn(browseButton, 1);
|
||||
pathGrid.Children.Add(pathTextBox);
|
||||
pathGrid.Children.Add(browseButton);
|
||||
|
||||
Grid.SetRow(pathLabel, 1);
|
||||
Grid.SetColumn(pathLabel, 0);
|
||||
Grid.SetRow(pathGrid, 1);
|
||||
Grid.SetColumn(pathGrid, 1);
|
||||
|
||||
grid.Children.Add(pathLabel);
|
||||
grid.Children.Add(pathGrid);
|
||||
|
||||
// 确认和取消按钮
|
||||
StackPanel buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
Button okButton = new Button
|
||||
{
|
||||
Content = "确定",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
IsDefault = true
|
||||
};
|
||||
|
||||
Button cancelButton = new Button
|
||||
{
|
||||
Content = "取消",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
IsCancel = true
|
||||
};
|
||||
|
||||
okButton.Click += (s, e) =>
|
||||
{
|
||||
// 验证输入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用名称!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pathTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用路径!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新项目
|
||||
item.Name = nameTextBox.Text;
|
||||
item.Path = pathTextBox.Text;
|
||||
|
||||
// 如果是新建,添加到集合
|
||||
if (isNew)
|
||||
{
|
||||
_plugin.AddLauncherItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 触发属性变更通知,刷新DataGrid
|
||||
if (DgApps.ItemsSource is ICollectionView view)
|
||||
{
|
||||
view.Refresh();
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
editWindow.DialogResult = true;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
cancelButton.Click += (s, e) =>
|
||||
{
|
||||
editWindow.DialogResult = false;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
Grid.SetRow(buttonPanel, 2);
|
||||
Grid.SetColumnSpan(buttonPanel, 2);
|
||||
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
// 设置窗口内容
|
||||
editWindow.Content = grid;
|
||||
|
||||
// 显示窗口
|
||||
editWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<Window x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherWindow"
|
||||
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"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
Title="启动台"
|
||||
Width="400"
|
||||
Height="300"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="#80000000"
|
||||
ResizeMode="NoResize"
|
||||
Topmost="True"
|
||||
Deactivated="Window_Deactivated"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 应用项样式 -->
|
||||
<Style x:Key="LauncherItemStyle" TargetType="Button">
|
||||
<Setter Property="Width" Value="80"/>
|
||||
<Setter Property="Height" Value="80"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="Background" Value="#40FFFFFF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0" Source="{Binding Icon}" Width="32" Height="32" Margin="0,10,0,5"/>
|
||||
<TextBlock Grid.Row="1" Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center"
|
||||
Margin="2,0,2,8" FontSize="11" Foreground="White"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#80FFFFFF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#C0FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border CornerRadius="15" Background="#80000000" BorderThickness="1" BorderBrush="#40FFFFFF">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Grid Grid.Row="0" Height="40">
|
||||
<TextBlock Text="启动台" Foreground="White" FontSize="18" FontWeight="Bold"
|
||||
VerticalAlignment="Center" Margin="15,0,0,0"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
|
||||
<Button x:Name="BtnFixMode" Click="BtnFixMode_Click" Width="30" Height="30"
|
||||
Margin="5,0" Background="Transparent" BorderThickness="0"
|
||||
ToolTip="切换固定模式">
|
||||
<Path x:Name="FixModeIcon" Data="M7,2V13H10V22L17,10H13L17,2H7Z" Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
<Button x:Name="BtnClose" Click="BtnClose_Click" Width="30" Height="30"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
ToolTip="关闭">
|
||||
<Path Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
|
||||
Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 应用网格 -->
|
||||
<ScrollViewer Grid.Row="1" Margin="10" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<WrapPanel x:Name="AppPanel" Orientation="Horizontal" HorizontalAlignment="Center"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,466 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于固定模式
|
||||
/// </summary>
|
||||
private bool _isFixMode;
|
||||
|
||||
/// <summary>
|
||||
/// 应用项按钮列表
|
||||
/// </summary>
|
||||
private readonly Dictionary<Button, LauncherItem> _appButtons = new Dictionary<Button, LauncherItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽中的按钮
|
||||
/// </summary>
|
||||
private Button _draggingButton;
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽开始位置
|
||||
/// </summary>
|
||||
private Point _dragStartPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public LauncherWindow(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 添加鼠标按下事件(用于拖动窗口)
|
||||
MouseDown += (s, e) =>
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
};
|
||||
|
||||
// 根据应用数量调整窗口大小
|
||||
AdjustWindowSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载启动台应用项
|
||||
/// </summary>
|
||||
private void LoadLauncherItems()
|
||||
{
|
||||
// 清空现有应用项
|
||||
AppPanel.Children.Clear();
|
||||
_appButtons.Clear();
|
||||
|
||||
// 获取显示的应用项
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(item => item.IsVisible)
|
||||
.OrderBy(item => item.Position)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in visibleItems)
|
||||
{
|
||||
// 创建应用按钮
|
||||
Button appButton = new Button
|
||||
{
|
||||
Style = (Style)FindResource("LauncherItemStyle"),
|
||||
DataContext = item,
|
||||
Tag = item.Position
|
||||
};
|
||||
|
||||
// 添加点击事件
|
||||
appButton.Click += AppButton_Click;
|
||||
|
||||
// 在固定模式下,添加拖拽事件
|
||||
appButton.PreviewMouseDown += AppButton_PreviewMouseDown;
|
||||
appButton.PreviewMouseMove += AppButton_PreviewMouseMove;
|
||||
appButton.PreviewMouseUp += AppButton_PreviewMouseUp;
|
||||
|
||||
// 记录按钮和项目的对应关系
|
||||
_appButtons.Add(appButton, item);
|
||||
|
||||
// 添加到面板
|
||||
AppPanel.Children.Add(appButton);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据应用数量调整窗口大小
|
||||
/// </summary>
|
||||
private void AdjustWindowSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 每行最多显示4个应用
|
||||
const int appsPerRow = 4;
|
||||
|
||||
// 计算行数
|
||||
int visibleCount = _appButtons.Count;
|
||||
int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow);
|
||||
|
||||
// 设置窗口宽度(每个应用90像素宽 = 80 + 5*2)
|
||||
Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400
|
||||
|
||||
// 设置窗口高度(每个应用90像素高 = 80 + 5*2)
|
||||
Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮点击事件
|
||||
/// </summary>
|
||||
private void AppButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isFixMode) return; // 在固定模式下,不响应点击事件
|
||||
|
||||
if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item))
|
||||
{
|
||||
// 获取应用路径和名称,用于后续启动
|
||||
string appPath = item.Path;
|
||||
string appName = item.Name;
|
||||
|
||||
LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}");
|
||||
|
||||
// 首先标记窗口正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 创建一个应用启动任务
|
||||
var launchTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待一段时间,确保窗口关闭流程已经开始
|
||||
Thread.Sleep(200);
|
||||
|
||||
// 使用UI线程启动应用
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查应用路径是否存在
|
||||
if (File.Exists(appPath) || !appPath.Contains(":\\"))
|
||||
{
|
||||
// 创建进程启动信息
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = appPath,
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
// 启动应用程序
|
||||
var process = Process.Start(psi);
|
||||
LogHelper.WriteLogToFile($"应用程序 {appName} 已启动");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭窗口
|
||||
try
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try { Close(); } catch { }
|
||||
|
||||
// 启动应用程序任务
|
||||
launchTask.Start();
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 如果无法通过UI关闭窗口,直接启动任务
|
||||
launchTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
try { IsClosing = true; Close(); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
#region 固定模式拖拽事件
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标按下事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode) return;
|
||||
|
||||
if (e.ChangedButton == MouseButton.Left && sender is Button button)
|
||||
{
|
||||
_draggingButton = button;
|
||||
_dragStartPoint = e.GetPosition(AppPanel);
|
||||
button.CaptureMouse();
|
||||
button.Opacity = 0.7;
|
||||
|
||||
// 阻止事件冒泡,以避免触发按钮点击
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标移动事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point currentPosition = e.GetPosition(AppPanel);
|
||||
|
||||
// 移动按钮
|
||||
System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2);
|
||||
System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2);
|
||||
|
||||
// 将按钮移到最上层
|
||||
Panel.SetZIndex(_draggingButton, 100);
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标释放事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
// 释放鼠标捕获
|
||||
_draggingButton.ReleaseMouseCapture();
|
||||
|
||||
// 计算新位置
|
||||
Point releasePoint = e.GetPosition(AppPanel);
|
||||
int newPosition = CalculateGridPosition(releasePoint);
|
||||
|
||||
// 获取当前项目
|
||||
LauncherItem currentItem = _appButtons[_draggingButton];
|
||||
|
||||
// 重新排序
|
||||
ReorderItems(currentItem, newPosition);
|
||||
|
||||
// 重新加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 清除拖拽状态
|
||||
_draggingButton.Opacity = 1;
|
||||
Panel.SetZIndex(_draggingButton, 0);
|
||||
_draggingButton = null;
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算网格位置
|
||||
/// </summary>
|
||||
private int CalculateGridPosition(Point point)
|
||||
{
|
||||
// 计算行和列
|
||||
int columnCount = 4; // 每行最多4个应用
|
||||
int columnWidth = 90; // 应用宽度(包括边距)
|
||||
int rowHeight = 90; // 应用高度(包括边距)
|
||||
|
||||
int column = (int)(point.X / columnWidth);
|
||||
int row = (int)(point.Y / rowHeight);
|
||||
|
||||
// 确保在有效范围内
|
||||
column = Math.Max(0, Math.Min(column, columnCount - 1));
|
||||
row = Math.Max(0, row);
|
||||
|
||||
// 计算位置索引
|
||||
return row * columnCount + column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新排序应用项
|
||||
/// </summary>
|
||||
private void ReorderItems(LauncherItem item, int newPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 设置项目为固定位置
|
||||
item.IsPositionFixed = true;
|
||||
|
||||
// 如果位置相同,无需调整
|
||||
if (item.Position == newPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有可见项目
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(i => i.IsVisible)
|
||||
.OrderBy(i => i.Position)
|
||||
.ToList();
|
||||
|
||||
// 移除当前项目
|
||||
visibleItems.Remove(item);
|
||||
|
||||
// 查找插入位置
|
||||
int insertIndex = 0;
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
if (visibleItems[i].Position >= newPosition)
|
||||
{
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
insertIndex = i + 1;
|
||||
}
|
||||
|
||||
// 插入项目
|
||||
visibleItems.Insert(insertIndex, item);
|
||||
|
||||
// 重新分配位置
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
visibleItems[i].Position = i;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 窗口失去焦点事件
|
||||
/// </summary>
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口
|
||||
if (!_isFixMode && IsLoaded && !IsClosing)
|
||||
{
|
||||
// 标记为正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次检查窗口状态
|
||||
if (IsLoaded && !IsClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口是否正在关闭
|
||||
/// </summary>
|
||||
private bool IsClosing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重写OnClosing方法,标记窗口正在关闭
|
||||
/// </summary>
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
IsClosing = true;
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 固定模式按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnFixMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 切换固定模式
|
||||
_isFixMode = !_isFixMode;
|
||||
|
||||
// 更新固定模式按钮图标颜色
|
||||
FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White;
|
||||
|
||||
// 显示提示
|
||||
if (_isFixMode)
|
||||
{
|
||||
MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn
|
||||
{
|
||||
/// <summary>
|
||||
/// 超级启动台插件
|
||||
/// </summary>
|
||||
public class SuperLauncherPlugin : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
public override string Name => "超级启动台";
|
||||
|
||||
public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。";
|
||||
|
||||
public override Version Version => new Version(1, 0, 1);
|
||||
|
||||
public override string Author => "ICC CE 团队";
|
||||
|
||||
public override bool IsBuiltIn => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件属性和字段
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public LauncherConfig Config { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public ObservableCollection<LauncherItem> LauncherItems { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台按钮
|
||||
/// </summary>
|
||||
private LauncherButton _launcherButton;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台窗口
|
||||
/// </summary>
|
||||
private LauncherWindow _launcherWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件路径
|
||||
/// </summary>
|
||||
private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json");
|
||||
|
||||
/// <summary>
|
||||
/// 标记是否已添加到浮动栏
|
||||
/// </summary>
|
||||
private bool _isAddedToFloatingBar;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// 创建配置目录
|
||||
string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
if (!Directory.Exists(configDir))
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
LoadConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已初始化");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsEnabled) return; // 防止重复启用
|
||||
|
||||
// 创建启动台按钮
|
||||
if (_launcherButton == null)
|
||||
{
|
||||
_launcherButton = new LauncherButton(this);
|
||||
LogHelper.WriteLogToFile("超级启动台按钮已创建");
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
AddLauncherButtonToFloatingBar();
|
||||
|
||||
// 设置启用状态
|
||||
base.Enable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsEnabled) return; // 防止重复禁用
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
// 设置禁用状态
|
||||
base.Disable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
return new LauncherSettingsControl(this);
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件设置
|
||||
/// </summary>
|
||||
public override void SavePluginSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保配置已加载
|
||||
if (Config == null)
|
||||
{
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
// 更新其他设置,但不更改插件启用状态
|
||||
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件设置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 配置管理
|
||||
|
||||
/// <summary>
|
||||
/// 加载配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
string json = File.ReadAllText(_configPath);
|
||||
Config = JsonConvert.DeserializeObject<LauncherConfig>(json) ?? CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items ?? new List<LauncherItem>());
|
||||
|
||||
// 注意:不再根据配置更改插件启用状态
|
||||
// 插件状态由PluginManager统一管理
|
||||
}
|
||||
else
|
||||
{
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置
|
||||
/// </summary>
|
||||
public void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 同步LauncherItems到Config
|
||||
Config.Items = new List<LauncherItem>(LauncherItems);
|
||||
|
||||
// 序列化并保存配置
|
||||
string json = JsonConvert.SerializeObject(Config, Formatting.Indented);
|
||||
File.WriteAllText(_configPath, json);
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台配置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认配置
|
||||
/// </summary>
|
||||
private LauncherConfig CreateDefaultConfig()
|
||||
{
|
||||
var config = new LauncherConfig
|
||||
{
|
||||
ButtonPosition = LauncherButtonPosition.Right,
|
||||
// 不再使用IsEnabled,插件状态由PluginManager管理
|
||||
Items = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem
|
||||
{
|
||||
Name = "资源管理器",
|
||||
Path = @"C:\Windows\explorer.exe",
|
||||
IsVisible = true,
|
||||
Position = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台按钮管理
|
||||
|
||||
/// <summary>
|
||||
/// 将启动台按钮添加到浮动栏
|
||||
/// </summary>
|
||||
private void AddLauncherButtonToFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果已经添加,先移除
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建启动台按钮
|
||||
_launcherButton = new LauncherButton(this);
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
if (Config.ButtonPosition == LauncherButtonPosition.Left)
|
||||
{
|
||||
floatingBar.Children.Insert(0, buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧");
|
||||
}
|
||||
else
|
||||
{
|
||||
floatingBar.Children.Add(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归查找StackPanelFloatingBar
|
||||
/// </summary>
|
||||
private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result)
|
||||
{
|
||||
if (parent == null || result != null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 检查当前对象是否为我们要找的面板
|
||||
if (parent is Panel panel && panel.Name == "StackPanelFloatingBar")
|
||||
{
|
||||
result = panel;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取子元素数量
|
||||
int childCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
|
||||
// 遍历所有子元素
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
||||
FindStackPanelFloatingBar(child, ref result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从浮动栏移除启动台按钮
|
||||
/// </summary>
|
||||
private void RemoveLauncherButtonFromFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isAddedToFloatingBar || _launcherButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取按钮元素
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
if (floatingBar.Children.Contains(buttonElement))
|
||||
{
|
||||
floatingBar.Children.Remove(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新启动台按钮位置
|
||||
/// </summary>
|
||||
public void UpdateButtonPosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果按钮已添加到浮动栏,重新添加以更新位置
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
AddLauncherButtonToFloatingBar();
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台功能
|
||||
|
||||
/// <summary>
|
||||
/// 显示启动台窗口
|
||||
/// </summary>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
public void ShowLauncherWindow(Point buttonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果窗口已存在,关闭它
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新的启动台窗口
|
||||
_launcherWindow = new LauncherWindow(this);
|
||||
|
||||
// 计算窗口位置,使其位于按钮上方
|
||||
PositionLauncherWindow(_launcherWindow, buttonPosition);
|
||||
|
||||
// 显示窗口
|
||||
_launcherWindow.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置启动台窗口位置
|
||||
/// </summary>
|
||||
/// <param name="window">启动台窗口</param>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition)
|
||||
{
|
||||
// 确保窗口已加载
|
||||
if (window.ActualWidth == 0 || window.ActualHeight == 0)
|
||||
{
|
||||
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
|
||||
// 设置窗口加载完成后的位置
|
||||
window.Loaded += (s, e) =>
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用到启动台
|
||||
/// </summary>
|
||||
/// <param name="item">启动台项</param>
|
||||
public void AddLauncherItem(LauncherItem item)
|
||||
{
|
||||
// 如果项目数量已达上限,则不添加
|
||||
if (LauncherItems.Count >= 40)
|
||||
{
|
||||
MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 寻找合适的位置
|
||||
if (item.Position < 0)
|
||||
{
|
||||
item.Position = FindNextAvailablePosition();
|
||||
}
|
||||
|
||||
// 添加项目并保存配置
|
||||
LauncherItems.Add(item);
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找下一个可用位置
|
||||
/// </summary>
|
||||
private int FindNextAvailablePosition()
|
||||
{
|
||||
// 获取已使用的位置列表
|
||||
var usedPositions = new HashSet<int>();
|
||||
foreach (var item in LauncherItems)
|
||||
{
|
||||
usedPositions.Add(item.Position);
|
||||
}
|
||||
|
||||
// 查找第一个可用位置
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
if (!usedPositions.Contains(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有位置都已使用,则返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类,提供对插件服务的访问和基本实现
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBase : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务实例
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBase()
|
||||
{
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写初始化方法,调用OnStartup
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
OnStartup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写清理方法,调用OnShutdown
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
OnShutdown();
|
||||
base.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// ICCPP 插件适配器,用于加载和管理 .iccpp 格式的插件
|
||||
/// </summary>
|
||||
public class ICCPPPluginAdapter : PluginBase
|
||||
{
|
||||
private readonly byte[] _pluginData;
|
||||
private readonly string _pluginPath;
|
||||
private readonly string _pluginName;
|
||||
private readonly Version _pluginVersion;
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 创建 ICCPP 插件适配器
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">插件文件路径</param>
|
||||
/// <param name="pluginData">插件文件数据</param>
|
||||
public ICCPPPluginAdapter(string pluginPath, byte[] pluginData)
|
||||
{
|
||||
_pluginPath = pluginPath;
|
||||
_pluginData = pluginData;
|
||||
PluginPath = pluginPath;
|
||||
|
||||
// 从文件名获取插件名称
|
||||
_pluginName = Path.GetFileNameWithoutExtension(pluginPath);
|
||||
_pluginVersion = new Version(1, 0, 0); // 默认版本
|
||||
|
||||
// 尝试从插件数据中读取更多信息
|
||||
TryReadPluginMetadata();
|
||||
}
|
||||
|
||||
public ICCPPPluginAdapter()
|
||||
{
|
||||
_pluginPath = string.Empty;
|
||||
_pluginData = new byte[0];
|
||||
PluginPath = string.Empty;
|
||||
_pluginName = "ICCPPPlugin";
|
||||
_pluginVersion = new Version(1, 0, 0);
|
||||
// 可选:初始化其他字段
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从插件数据中读取元数据
|
||||
/// </summary>
|
||||
private void TryReadPluginMetadata()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以根据 .iccpp 文件的实际格式解析元数据
|
||||
// 例如,如果文件有特定的头部结构,可以在这里解析
|
||||
|
||||
// 示例:如果前100字节包含元数据
|
||||
if (_pluginData.Length > 100)
|
||||
{
|
||||
// 解析元数据的代码...
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"解析插件 {_pluginName} 元数据时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlugin 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => _pluginName;
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => $"{_pluginName} (ICCPP 格式插件)";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => _pluginVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "未知";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的初始化逻辑
|
||||
// 例如,根据文件格式加载特定资源
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已初始化");
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
if (IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的启用逻辑
|
||||
// 例如,加载动态库、注册事件等
|
||||
|
||||
base.Enable(); // 设置启用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的禁用逻辑
|
||||
// 例如,卸载动态库、注销事件等
|
||||
|
||||
base.Disable(); // 设置禁用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理插件资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的清理逻辑
|
||||
// 例如,释放资源等
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已清理资源");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理 ICCPP 插件 {Name} 资源时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件接口,提供对插件服务的访问
|
||||
/// </summary>
|
||||
public interface IEnhancedPlugin : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取插件服务实例
|
||||
/// </summary>
|
||||
IPluginService PluginService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
void OnStartup();
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
void OnShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
MenuItem[] GetMenuItems();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
Button[] GetToolbarButtons();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
string GetStatusBarInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
void OnConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义插件的基本接口
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
bool IsBuiltIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// 此方法在插件加载时被调用,用于执行一些初始化工作
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 此方法在插件被用户或系统启用时调用,激活插件功能
|
||||
/// </summary>
|
||||
void Enable();
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 此方法在插件被用户或系统禁用时调用,停用插件功能
|
||||
/// </summary>
|
||||
void Disable();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// 此方法返回插件的设置界面控件,用于展示在设置窗口
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
UserControl GetSettingsView();
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// 此方法在插件被卸载前调用,用于释放资源和执行清理
|
||||
/// </summary>
|
||||
void Cleanup();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务接口,提供对软件内部功能的访问
|
||||
/// </summary>
|
||||
public interface IPluginService
|
||||
{
|
||||
#region 窗口和UI访问
|
||||
|
||||
/// <summary>
|
||||
/// 获取主窗口引用
|
||||
/// </summary>
|
||||
Window MainWindow { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前画布
|
||||
/// </summary>
|
||||
InkCanvas CurrentCanvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有画布页面
|
||||
/// </summary>
|
||||
List<Canvas> AllCanvasPages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面索引
|
||||
/// </summary>
|
||||
int CurrentPageIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面数量
|
||||
/// </summary>
|
||||
int TotalPageCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取浮动工具栏
|
||||
/// </summary>
|
||||
FrameworkElement FloatingToolBar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取左侧面板
|
||||
/// </summary>
|
||||
FrameworkElement LeftPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取右侧面板
|
||||
/// </summary>
|
||||
FrameworkElement RightPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶部面板
|
||||
/// </summary>
|
||||
FrameworkElement TopPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取底部面板
|
||||
/// </summary>
|
||||
FrameworkElement BottomPanel { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
int CurrentDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触宽度
|
||||
/// </summary>
|
||||
double CurrentInkWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触颜色
|
||||
/// </summary>
|
||||
Color CurrentInkColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前高亮笔宽度
|
||||
/// </summary>
|
||||
double CurrentHighlighterWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦大小
|
||||
/// </summary>
|
||||
int CurrentEraserSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦类型
|
||||
/// </summary>
|
||||
int CurrentEraserType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦形状
|
||||
/// </summary>
|
||||
int CurrentEraserShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触透明度
|
||||
/// </summary>
|
||||
double CurrentInkAlpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触样式
|
||||
/// </summary>
|
||||
int CurrentInkStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前背景颜色
|
||||
/// </summary>
|
||||
string CurrentBackgroundColor { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前主题模式
|
||||
/// </summary>
|
||||
bool IsDarkTheme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为白板模式
|
||||
/// </summary>
|
||||
bool IsWhiteboardMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为PPT模式
|
||||
/// </summary>
|
||||
bool IsPPTMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为全屏模式
|
||||
/// </summary>
|
||||
bool IsFullScreenMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为画板模式
|
||||
/// </summary>
|
||||
bool IsCanvasMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为选择模式
|
||||
/// </summary>
|
||||
bool IsSelectionMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为擦除模式
|
||||
/// </summary>
|
||||
bool IsEraserMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为形状绘制模式
|
||||
/// </summary>
|
||||
bool IsShapeDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为高亮模式
|
||||
/// </summary>
|
||||
bool IsHighlighterMode { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 画布操作
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
void ClearCanvas();
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有画布
|
||||
/// </summary>
|
||||
void ClearAllCanvases();
|
||||
|
||||
/// <summary>
|
||||
/// 添加新页面
|
||||
/// </summary>
|
||||
void AddNewPage();
|
||||
|
||||
/// <summary>
|
||||
/// 删除当前页面
|
||||
/// </summary>
|
||||
void DeleteCurrentPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页面索引</param>
|
||||
void SwitchToPage(int pageIndex);
|
||||
|
||||
/// <summary>
|
||||
/// 切换到下一页
|
||||
/// </summary>
|
||||
void NextPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到上一页
|
||||
/// </summary>
|
||||
void PreviousPage();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
void SetDrawingMode(int mode);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetInkWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetInkColor(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// 设置高亮笔宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetHighlighterWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦大小
|
||||
/// </summary>
|
||||
/// <param name="size">大小</param>
|
||||
void SetEraserSize(int size);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
void SetEraserType(int type);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦形状
|
||||
/// </summary>
|
||||
/// <param name="shape">形状</param>
|
||||
void SetEraserShape(int shape);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触透明度
|
||||
/// </summary>
|
||||
/// <param name="alpha">透明度</param>
|
||||
void SetInkAlpha(double alpha);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触样式
|
||||
/// </summary>
|
||||
/// <param name="style">样式</param>
|
||||
void SetInkStyle(int style);
|
||||
|
||||
/// <summary>
|
||||
/// 设置背景颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetBackgroundColor(string color);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void SaveCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 加载画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void LoadCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为图片
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="format">图片格式</param>
|
||||
void ExportAsImage(string filePath, string format);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为PDF
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void ExportAsPDF(string filePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
void Undo();
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
void Redo();
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以撤销
|
||||
/// </summary>
|
||||
bool CanUndo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以重做
|
||||
/// </summary>
|
||||
bool CanRedo { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择操作
|
||||
|
||||
/// <summary>
|
||||
/// 全选
|
||||
/// </summary>
|
||||
void SelectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 取消选择
|
||||
/// </summary>
|
||||
void DeselectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 删除选中内容
|
||||
/// </summary>
|
||||
void DeleteSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 复制选中内容
|
||||
/// </summary>
|
||||
void CopySelected();
|
||||
|
||||
/// <summary>
|
||||
/// 剪切选中内容
|
||||
/// </summary>
|
||||
void CutSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 粘贴内容
|
||||
/// </summary>
|
||||
void Paste();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口管理
|
||||
|
||||
/// <summary>
|
||||
/// 显示设置窗口
|
||||
/// </summary>
|
||||
void ShowSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏设置窗口
|
||||
/// </summary>
|
||||
void HideSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示插件设置窗口
|
||||
/// </summary>
|
||||
void ShowPluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏插件设置窗口
|
||||
/// </summary>
|
||||
void HidePluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示帮助窗口
|
||||
/// </summary>
|
||||
void ShowHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏帮助窗口
|
||||
/// </summary>
|
||||
void HideHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示关于窗口
|
||||
/// </summary>
|
||||
void ShowAboutWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏关于窗口
|
||||
/// </summary>
|
||||
void HideAboutWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通知和消息
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
void ShowNotification(string message, NotificationType type = NotificationType.Info);
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
bool ShowConfirmDialog(string message, string title = "确认");
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
string ShowInputDialog(string message, string title = "输入", string defaultValue = "");
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统功能
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
T GetSetting<T>(string key, T defaultValue = default(T));
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
void SetSetting<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置到文件
|
||||
/// </summary>
|
||||
void SaveSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载设置
|
||||
/// </summary>
|
||||
void LoadSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 重置设置为默认值
|
||||
/// </summary>
|
||||
void ResetSettings();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件管理
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载的插件
|
||||
/// </summary>
|
||||
/// <returns>插件列表</returns>
|
||||
List<IPlugin> GetAllPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>插件实例</returns>
|
||||
IPlugin GetPlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void EnablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void DisablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void UnloadPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件系统
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void RegisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void UnregisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
void TriggerEvent(string eventName, object sender, EventArgs args);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知类型枚举
|
||||
/// </summary>
|
||||
public enum NotificationType
|
||||
{
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件基类,提供基本实现
|
||||
/// </summary>
|
||||
public abstract class PluginBase : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件状态(私有字段)
|
||||
/// </summary>
|
||||
private bool _isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 插件状态(公共属性)
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
protected set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnEnabledStateChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件ID
|
||||
/// </summary>
|
||||
public string Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件路径
|
||||
/// </summary>
|
||||
public string PluginPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public abstract Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public abstract string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public virtual bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件
|
||||
/// </summary>
|
||||
public event EventHandler<bool> EnabledStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
Id = GetType().FullName;
|
||||
|
||||
// 添加日志,记录插件名称
|
||||
try
|
||||
{
|
||||
string name = Name;
|
||||
LogHelper.WriteLogToFile($"初始化插件: ID={Id}, 名称={name ?? "未命名"}");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"警告: 插件 {Id} 的名称为空", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件名称时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已初始化");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public virtual void Enable()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public virtual void Disable()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public virtual UserControl GetSettingsView()
|
||||
{
|
||||
// 默认返回空设置页面
|
||||
return new UserControl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// </summary>
|
||||
public virtual void Cleanup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已卸载");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件自身的设置
|
||||
/// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态
|
||||
/// 插件启用状态由PluginManager统一管理
|
||||
/// </summary>
|
||||
public virtual void SavePluginSettings()
|
||||
{
|
||||
// 默认实现不做任何事情
|
||||
// 子类可以重写此方法,将自身设置保存到配置文件中
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发状态变更事件
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">是否启用</param>
|
||||
protected virtual void OnEnabledStateChanged(bool isEnabled)
|
||||
{
|
||||
EnabledStateChanged?.Invoke(this, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件配置管理器,允许插件管理自己的配置
|
||||
/// </summary>
|
||||
public class PluginConfigurationManager
|
||||
{
|
||||
private static readonly string PluginConfigDirectory = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
private static readonly Dictionary<string, Dictionary<string, object>> _pluginConfigs = new Dictionary<string, Dictionary<string, object>>();
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
static PluginConfigurationManager()
|
||||
{
|
||||
// 确保配置目录存在
|
||||
if (!Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(PluginConfigDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>配置值</returns>
|
||||
public static T GetConfiguration<T>(string pluginName, string key, T defaultValue = default(T))
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
// 尝试类型转换
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="value">配置值</param>
|
||||
public static void SetConfiguration<T>(string pluginName, string key, T value)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_pluginConfigs.ContainsKey(pluginName))
|
||||
{
|
||||
_pluginConfigs[pluginName] = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
_pluginConfigs[pluginName][key] = value;
|
||||
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
public static void RemoveConfiguration(string pluginName, string key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.Remove(key))
|
||||
{
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>配置字典</returns>
|
||||
public static Dictionary<string, object> GetAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
return new Dictionary<string, object>(pluginConfig);
|
||||
}
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void ClearAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.Remove(pluginName))
|
||||
{
|
||||
// 删除配置文件
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
File.Delete(configFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除插件 {pluginName} 所有配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void LoadPluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
string json = File.ReadAllText(configFile);
|
||||
var config = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_pluginConfigs[pluginName] = config ?? new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已加载插件 {pluginName} 的配置");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
private static void SavePluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> pluginConfig;
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_pluginConfigs.TryGetValue(pluginName, out pluginConfig))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
string json = JsonConvert.SerializeObject(pluginConfig, Formatting.Indented);
|
||||
File.WriteAllText(configFile, json);
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存插件 {pluginName} 的配置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有插件的配置
|
||||
/// </summary>
|
||||
public static void LoadAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
string[] configFiles = Directory.GetFiles(PluginConfigDirectory, "*.json");
|
||||
foreach (string configFile in configFiles)
|
||||
{
|
||||
string pluginName = Path.GetFileNameWithoutExtension(configFile);
|
||||
LoadPluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有插件的配置
|
||||
/// </summary>
|
||||
public static void SaveAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
foreach (string pluginName in _pluginConfigs.Keys)
|
||||
{
|
||||
SavePluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,455 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务管理器,实现IPluginService接口,提供对软件内部功能的访问
|
||||
/// </summary>
|
||||
public class PluginServiceManager : IPluginService
|
||||
{
|
||||
private static PluginServiceManager _instance;
|
||||
private MainWindow _mainWindow;
|
||||
private Dictionary<string, EventHandler> _eventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// 单例实例
|
||||
/// </summary>
|
||||
public static PluginServiceManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new PluginServiceManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private PluginServiceManager()
|
||||
{
|
||||
_eventHandlers = new Dictionary<string, EventHandler>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置主窗口引用
|
||||
/// </summary>
|
||||
/// <param name="mainWindow">主窗口实例</param>
|
||||
public void SetMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
#region 窗口和UI访问
|
||||
|
||||
public Window MainWindow => _mainWindow;
|
||||
|
||||
public InkCanvas CurrentCanvas => null; // 暂时返回null,避免访问权限问题
|
||||
|
||||
public List<Canvas> AllCanvasPages => new List<Canvas>(); // 暂时返回空列表
|
||||
|
||||
public int CurrentPageIndex => 0; // 暂时返回0
|
||||
|
||||
public int TotalPageCount => 0; // 暂时返回0
|
||||
|
||||
public FrameworkElement FloatingToolBar => _mainWindow?.ViewboxFloatingBar;
|
||||
|
||||
public FrameworkElement LeftPanel => _mainWindow?.BlackboardLeftSide;
|
||||
|
||||
public FrameworkElement RightPanel => _mainWindow?.BlackboardRightSide;
|
||||
|
||||
public FrameworkElement TopPanel => _mainWindow?.BorderTools;
|
||||
|
||||
public FrameworkElement BottomPanel => _mainWindow?.BorderSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态
|
||||
|
||||
public int CurrentDrawingMode => 0; // 暂时返回0
|
||||
|
||||
public double CurrentInkWidth => 2.5; // 暂时返回默认值
|
||||
|
||||
public Color CurrentInkColor => Colors.Black; // 暂时返回默认值
|
||||
|
||||
public double CurrentHighlighterWidth => 20.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserSize => 2; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserType => 0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserShape => 0; // 暂时返回默认值
|
||||
|
||||
public double CurrentInkAlpha => 255.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentInkStyle => 0; // 暂时返回默认值
|
||||
|
||||
public string CurrentBackgroundColor => "#162924"; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态
|
||||
|
||||
public bool IsDarkTheme => false; // 暂时返回默认值
|
||||
|
||||
public bool IsWhiteboardMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsPPTMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsFullScreenMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsCanvasMode => true; // 暂时返回默认值
|
||||
|
||||
public bool IsSelectionMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsEraserMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsShapeDrawingMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsHighlighterMode => false; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region 画布操作
|
||||
|
||||
public void ClearCanvas()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ClearAllCanvases()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void AddNewPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteCurrentPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SwitchToPage(int pageIndex)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void NextPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void PreviousPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制操作
|
||||
|
||||
public void SetDrawingMode(int mode)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkColor(Color color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetHighlighterWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserSize(int size)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserType(int type)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserShape(int shape)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkAlpha(double alpha)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkStyle(int style)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetBackgroundColor(string color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
public void SaveCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsImage(string filePath, string format)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsPDF(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public bool CanUndo => false; // 暂时返回默认值
|
||||
|
||||
public bool CanRedo => false; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择操作
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeselectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CopySelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CutSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Paste()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口管理
|
||||
|
||||
public void ShowSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowPluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HidePluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通知和消息
|
||||
|
||||
public void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统功能
|
||||
|
||||
public T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void SetSetting<T>(string key, T value)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件管理
|
||||
|
||||
public List<IPlugin> GetAllPlugins()
|
||||
{
|
||||
return new List<IPlugin>(PluginManager.Instance.Plugins);
|
||||
}
|
||||
|
||||
public IPlugin GetPlugin(string pluginName)
|
||||
{
|
||||
return PluginManager.Instance.Plugins.FirstOrDefault(p => p.Name == pluginName);
|
||||
}
|
||||
|
||||
public void EnablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadPlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.UnloadPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件系统
|
||||
|
||||
public void RegisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (!_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventHandlers[eventName] += handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerEvent(string eventName, object sender, EventArgs args)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName]?.Invoke(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件模板,用于开发者参考
|
||||
/// 注意:实际开发时,请将此类移到单独的程序集中
|
||||
/// </summary>
|
||||
public class PluginTemplate : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => "插件模板";
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => "这是一个插件开发模板,用于开发者参考。";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => new Version(1, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "Your Name";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件(外部插件请返回false)
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
/// <summary>
|
||||
/// 插件初始化
|
||||
/// 在这里进行插件的初始化工作,如加载配置、注册事件等
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件ID和记录日志
|
||||
base.Initialize();
|
||||
|
||||
// TODO: 在这里进行插件初始化工作
|
||||
|
||||
// 示例:记录初始化信息
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 开始初始化");
|
||||
|
||||
// 示例:加载配置
|
||||
LoadConfig();
|
||||
|
||||
// 示例:注册自定义事件
|
||||
// MainWindow.Instance.SomeEvent += OnSomeEvent;
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 初始化完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 在这里激活插件功能
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Enable();
|
||||
|
||||
// TODO: 在这里启用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 在这里停用插件功能
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Disable();
|
||||
|
||||
// TODO: 在这里禁用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理资源
|
||||
/// 在插件卸载时调用,清理资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
// TODO: 在这里清理插件资源
|
||||
|
||||
// 示例:取消注册事件
|
||||
// MainWindow.Instance.SomeEvent -= OnSomeEvent;
|
||||
|
||||
// 示例:保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 最后调用基类方法
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件配置
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 从文件或其他位置加载配置
|
||||
// 示例:
|
||||
// string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json");
|
||||
// if (File.Exists(configPath))
|
||||
// {
|
||||
// string json = File.ReadAllText(configPath);
|
||||
// YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<YourConfigClass>(json);
|
||||
// }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
private void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 保存配置到文件或其他位置
|
||||
// 示例:
|
||||
// string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
// if (!Directory.Exists(configDir))
|
||||
// {
|
||||
// Directory.CreateDirectory(configDir);
|
||||
// }
|
||||
// string configPath = Path.Combine(configDir, "YourPluginName.json");
|
||||
// string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented);
|
||||
// File.WriteAllText(configPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件设置界面
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
// 创建插件设置界面
|
||||
return new PluginTemplateSettingsControl();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件功能方法
|
||||
|
||||
// TODO: 在这里添加插件的具体功能方法
|
||||
|
||||
/// <summary>
|
||||
/// 示例方法:执行一些功能
|
||||
/// </summary>
|
||||
public void DoSomething()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: 实现你的功能
|
||||
MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件设置控件
|
||||
/// </summary>
|
||||
public class PluginTemplateSettingsControl : UserControl
|
||||
{
|
||||
public PluginTemplateSettingsControl()
|
||||
{
|
||||
// 创建设置界面布局
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
// 添加标题
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "插件模板设置",
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
// 添加说明文字
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。",
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 0, 0, 15)
|
||||
});
|
||||
|
||||
// 添加示例设置选项
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = "启用某项功能",
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
panel.Children.Add(checkBox);
|
||||
|
||||
// 添加文本输入框
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "设置项:",
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
});
|
||||
|
||||
panel.Children.Add(new TextBox
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
Width = 200,
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
});
|
||||
|
||||
// 添加按钮
|
||||
var button = new Button
|
||||
{
|
||||
Content = "保存设置",
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
button.Click += (sender, e) =>
|
||||
{
|
||||
MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
};
|
||||
|
||||
panel.Children.Add(button);
|
||||
|
||||
// 设置控件内容
|
||||
Content = panel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -27,10 +28,7 @@ namespace Ink_Canvas.Helpers
|
||||
//MessageBox.Show("启动失败: " + ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine(softwareName + " 未找到可执行文件路径。");
|
||||
}
|
||||
//Console.WriteLine(softwareName + " 未找到可执行文件路径。");
|
||||
}
|
||||
|
||||
private static string FindEasiCameraExecutablePath(string softwareName)
|
||||
@@ -51,7 +49,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
{
|
||||
executablePath = System.IO.Path.Combine(installLocation, "sweclauncher.exe");
|
||||
executablePath = Path.Combine(installLocation, "sweclauncher.exe");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(uninstallString))
|
||||
{
|
||||
@@ -59,7 +57,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
string folderPath = uninstallString.Substring(0, lastSlashIndex);
|
||||
executablePath = System.IO.Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
executablePath = Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class StartupCount
|
||||
{
|
||||
private static readonly string CountFilePath = Path.Combine(App.RootPath, "startup-count");
|
||||
private static readonly object fileLock = new object();
|
||||
|
||||
public static int GetCount()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(CountFilePath))
|
||||
{
|
||||
var text = File.ReadAllText(CountFilePath).Trim();
|
||||
if (int.TryParse(text, out int count))
|
||||
return count;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void Increment()
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
int count = GetCount() + 1;
|
||||
try
|
||||
{
|
||||
File.WriteAllText(CountFilePath, count.ToString());
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(CountFilePath))
|
||||
File.Delete(CountFilePath);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
// Added for UIElement
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class TimeMachine
|
||||
public partial class TimeMachine
|
||||
{
|
||||
private readonly List<TimeMachineHistory> _currentStrokeHistory = new List<TimeMachineHistory>();
|
||||
|
||||
@@ -133,12 +135,13 @@ namespace Ink_Canvas.Helpers
|
||||
public class TimeMachineHistory
|
||||
{
|
||||
public TimeMachineHistoryType CommitType;
|
||||
public bool StrokeHasBeenCleared = false;
|
||||
public bool StrokeHasBeenCleared;
|
||||
public StrokeCollection CurrentStroke;
|
||||
public StrokeCollection ReplacedStroke;
|
||||
//这里说一下 Tuple的 Value1 是初始值 ; Value 2 是改变值
|
||||
public Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> StylusPointDictionary;
|
||||
public Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> DrawingAttributes;
|
||||
public UIElement InsertedElement; // 新增
|
||||
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared)
|
||||
{
|
||||
CommitType = commitType;
|
||||
@@ -163,6 +166,11 @@ namespace Ink_Canvas.Helpers
|
||||
StrokeHasBeenCleared = strokeHasBeenCleared;
|
||||
ReplacedStroke = replacedStroke;
|
||||
}
|
||||
public TimeMachineHistory(UIElement element, TimeMachineHistoryType commitType) // 新增
|
||||
{
|
||||
CommitType = commitType;
|
||||
InsertedElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TimeMachineHistoryType
|
||||
@@ -171,6 +179,34 @@ namespace Ink_Canvas.Helpers
|
||||
ShapeRecognition,
|
||||
Clear,
|
||||
Manipulation,
|
||||
DrawingAttributes
|
||||
DrawingAttributes,
|
||||
ElementInsert // 新增
|
||||
}
|
||||
|
||||
public partial class TimeMachine // 新增partial,便于扩展
|
||||
{
|
||||
public void CommitElementInsertHistory(UIElement element)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(new TimeMachineHistory(element, TimeMachineHistoryType.ElementInsert));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public void CommitElementRemoveHistory(UIElement element)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
var history = new TimeMachineHistory(element, TimeMachineHistoryType.ElementInsert);
|
||||
history.StrokeHasBeenCleared = true; // 标记为已清除
|
||||
_currentStrokeHistory.Add(history);
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Windows.Automation;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class WinTabWindowsChecker {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class WinTabWindowsChecker
|
||||
{
|
||||
/*
|
||||
public static bool IsWindowMinimized(string windowName, bool matchFullName = true) {
|
||||
// 获取Win+Tab预览中的窗口
|
||||
@@ -40,28 +42,37 @@ namespace Ink_Canvas.Helpers {
|
||||
}
|
||||
*/
|
||||
|
||||
public static bool IsWindowExisted(string windowName, bool matchFullName = true) {
|
||||
public static bool IsWindowExisted(string windowName, bool matchFullName = true)
|
||||
{
|
||||
// 获取Win+Tab预览中的窗口
|
||||
AutomationElementCollection windows = AutomationElement.RootElement.FindAll(
|
||||
TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
|
||||
|
||||
foreach (AutomationElement window in windows) {
|
||||
foreach (AutomationElement window in windows)
|
||||
{
|
||||
//LogHelper.WriteLogToFile("" + window.Current.Name);
|
||||
|
||||
string windowTitle = window.Current.Name;
|
||||
|
||||
// 如果窗口标题包含 windowName,则进行检查
|
||||
if (!string.IsNullOrEmpty(windowTitle) && windowTitle.Contains(windowName)) {
|
||||
if (matchFullName) {
|
||||
if (windowTitle.Length == windowName.Length) {
|
||||
if (!string.IsNullOrEmpty(windowTitle) && windowTitle.Contains(windowName))
|
||||
{
|
||||
if (matchFullName)
|
||||
{
|
||||
if (windowTitle.Length == windowName.Length)
|
||||
{
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
if (windowPattern != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
if (windowPattern != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"Version": "1.0",
|
||||
"LastModified": "2025-01-28T15:30:00",
|
||||
"Hotkeys": [
|
||||
{
|
||||
"Name": "Undo",
|
||||
"Key": "Z",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "Redo",
|
||||
"Key": "Y",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "Clear",
|
||||
"Key": "E",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "Paste",
|
||||
"Key": "V",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "SelectTool",
|
||||
"Key": "S",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "DrawTool",
|
||||
"Key": "D",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "EraserTool",
|
||||
"Key": "E",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "BlackboardTool",
|
||||
"Key": "B",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "QuitDrawTool",
|
||||
"Key": "Q",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen1",
|
||||
"Key": "D1",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen2",
|
||||
"Key": "D2",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen3",
|
||||
"Key": "D3",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen4",
|
||||
"Key": "D4",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen5",
|
||||
"Key": "D5",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "DrawLine",
|
||||
"Key": "L",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Screenshot",
|
||||
"Key": "C",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Hide",
|
||||
"Key": "V",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Exit",
|
||||
"Key": "Escape",
|
||||
"Modifiers": "None"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -113,12 +113,15 @@
|
||||
<ItemGroup>
|
||||
<Reference Include="IACore">
|
||||
<HintPath>.\IACore.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="IALoader">
|
||||
<HintPath>.\IALoader.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="IAWinFX">
|
||||
<HintPath>.\IAWinFX.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="netstandard" />
|
||||
@@ -132,12 +135,18 @@
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="WindowsFormsIntegration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
||||
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.9.27" />
|
||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||
@@ -181,6 +190,11 @@
|
||||
<ItemGroup>
|
||||
<None Include="Resources\TimerDownNotice.wav" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\IACore\IACore.dll" />
|
||||
<EmbeddedResource Include="Resources\IACore\IALoader.dll" />
|
||||
<EmbeddedResource Include="Resources\IACore\IAWinFX.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Cursors\Cursor.cur" />
|
||||
<Resource Include="Resources\Cursors\Pen.cur" />
|
||||
|
||||
+2168
-276
File diff suppressed because it is too large
Load Diff
+1933
-90
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -9,14 +6,18 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : Window {
|
||||
public bool isFloatingBarFolded = false;
|
||||
private bool isFloatingBarChangingHideMode = false;
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public bool isFloatingBarFolded;
|
||||
private bool isFloatingBarChangingHideMode;
|
||||
|
||||
private void CloseWhiteboardImmediately() {
|
||||
private void CloseWhiteboardImmediately()
|
||||
{
|
||||
if (isDisplayingOrHidingBlackboard) return;
|
||||
isDisplayingOrHidingBlackboard = true;
|
||||
HideSubPanelsImmediately();
|
||||
@@ -30,21 +31,24 @@ namespace Ink_Canvas {
|
||||
BtnSwitch_Click(BtnSwitch, null);
|
||||
BtnExit.Foreground = Brushes.White;
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
|
||||
new Thread(new ThreadStart(() => {
|
||||
new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(200);
|
||||
Application.Current.Dispatcher.Invoke(() => { isDisplayingOrHidingBlackboard = false; });
|
||||
})).Start();
|
||||
}).Start();
|
||||
}
|
||||
|
||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await FoldFloatingBar(sender);
|
||||
}
|
||||
|
||||
public async Task FoldFloatingBar(object sender)
|
||||
public async Task FoldFloatingBar(object sender, bool isAutoFoldCommand = false)
|
||||
{
|
||||
var isShouldRejectAction = false;
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == Fold_Icon && lastBorderMouseDownObject != Fold_Icon) isShouldRejectAction = true;
|
||||
@@ -54,14 +58,15 @@ namespace Ink_Canvas {
|
||||
|
||||
// FloatingBarIcons_MouseUp_New(sender);
|
||||
if (sender == null)
|
||||
foldFloatingBarByUser = true;
|
||||
foldFloatingBarByUser = false;
|
||||
else
|
||||
foldFloatingBarByUser = true;
|
||||
unfoldFloatingBarByUser = false;
|
||||
|
||||
if (isFloatingBarChangingHideMode) return;
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
InkCanvasForInkReplay.Visibility = Visibility.Collapsed;
|
||||
InkCanvasGridForInkReplay.Visibility = Visibility.Visible;
|
||||
InkCanvasGridForInkReplay.IsHitTestVisible = true;
|
||||
@@ -73,7 +78,8 @@ namespace Ink_Canvas {
|
||||
isStopInkReplay = true;
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
isFloatingBarChangingHideMode = true;
|
||||
isFloatingBarFolded = true;
|
||||
if (currentMode != 0) CloseWhiteboardImmediately();
|
||||
@@ -84,13 +90,15 @@ namespace Ink_Canvas {
|
||||
CursorWithDelIcon_Click(sender, null);
|
||||
});
|
||||
|
||||
await Task.Delay(10);
|
||||
await Task.Delay(300);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
GridForFloatingBarDraging.Visibility = Visibility.Collapsed;
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
HideSubPanels("cursor");
|
||||
SidePannelMarginAnimation(-10);
|
||||
@@ -98,12 +106,16 @@ namespace Ink_Canvas {
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
|
||||
private async void LeftUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (Settings.Appearance.IsShowQuickPanel == true) {
|
||||
private async void LeftUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings.Appearance.IsShowQuickPanel)
|
||||
{
|
||||
HideRightQuickPanel();
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Visible;
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(-50, 0, 0, -150),
|
||||
To = new Thickness(-1, 0, 0, -150)
|
||||
@@ -113,21 +125,27 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Margin = new Thickness(-1, 0, 0, -150);
|
||||
});
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
UnFoldFloatingBar_MouseUp(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void RightUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (Settings.Appearance.IsShowQuickPanel == true) {
|
||||
private async void RightUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings.Appearance.IsShowQuickPanel)
|
||||
{
|
||||
HideLeftQuickPanel();
|
||||
RightUnFoldButtonQuickPanel.Visibility = Visibility.Visible;
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(0, 0, -50, -150),
|
||||
To = new Thickness(0, 0, -1, -150)
|
||||
@@ -137,19 +155,25 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RightUnFoldButtonQuickPanel.Margin = new Thickness(0, 0, -1, -150);
|
||||
});
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
UnFoldFloatingBar_MouseUp(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void HideLeftQuickPanel() {
|
||||
if (LeftUnFoldButtonQuickPanel.Visibility == Visibility.Visible) {
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
private async void HideLeftQuickPanel()
|
||||
{
|
||||
if (LeftUnFoldButtonQuickPanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(-1, 0, 0, -150),
|
||||
To = new Thickness(-50, 0, 0, -150)
|
||||
@@ -159,17 +183,22 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Margin = new Thickness(0, 0, -50, -150);
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void HideRightQuickPanel() {
|
||||
if (RightUnFoldButtonQuickPanel.Visibility == Visibility.Visible) {
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
private async void HideRightQuickPanel()
|
||||
{
|
||||
if (RightUnFoldButtonQuickPanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(0, 0, -1, -150),
|
||||
To = new Thickness(0, 0, -50, -150)
|
||||
@@ -179,25 +208,29 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RightUnFoldButtonQuickPanel.Margin = new Thickness(0, 0, -50, -150);
|
||||
RightUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HideQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
private void HideQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideLeftQuickPanel();
|
||||
HideRightQuickPanel();
|
||||
}
|
||||
|
||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await UnFoldFloatingBar(sender);
|
||||
}
|
||||
|
||||
public async Task UnFoldFloatingBar(object sender)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
RightUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
@@ -209,28 +242,41 @@ namespace Ink_Canvas {
|
||||
|
||||
if (isFloatingBarChangingHideMode) return;
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
isFloatingBarChangingHideMode = true;
|
||||
isFloatingBarFolded = false;
|
||||
});
|
||||
|
||||
await Task.Delay(0);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 根据设置决定是否自动切换至批注模式
|
||||
if (Settings.Automation.IsAutoEnterAnnotationModeWhenExitFoldMode && currentMode == 0)
|
||||
{
|
||||
// 切换至批注模式
|
||||
PenIcon_Click(null, null);
|
||||
}
|
||||
|
||||
if (StackPanelPPTControls.Visibility == Visibility.Visible)
|
||||
{
|
||||
var dops = Settings.PowerPointSettings.PPTButtonsDisplayOption.ToString();
|
||||
var dopsc = dops.ToCharArray();
|
||||
if (dopsc[0] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(LeftBottomPanelForPPTNavigation);
|
||||
if (dopsc[1] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
|
||||
if (dopsc[2] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
|
||||
if (dopsc[3] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
|
||||
if (dopsc[0] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftBottomPanelForPPTNavigation);
|
||||
if (dopsc[1] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
|
||||
if (dopsc[2] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
|
||||
if (dopsc[3] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
|
||||
}
|
||||
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
ViewboxFloatingBarMarginAnimation(60);
|
||||
else
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
// 新增:只在屏幕模式下显示浮动栏
|
||||
if (currentMode == 0)
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
ViewboxFloatingBarMarginAnimation(60);
|
||||
else
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
SidePannelMarginAnimation(-50, !unfoldFloatingBarByUser);
|
||||
});
|
||||
|
||||
@@ -239,17 +285,20 @@ namespace Ink_Canvas {
|
||||
|
||||
private async void SidePannelMarginAnimation(int MarginFromEdge, bool isNoAnimation = false) // Possible value: -50, -10
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (MarginFromEdge == -10) LeftSidePanel.Visibility = Visibility.Visible;
|
||||
|
||||
var LeftSidePanelmarginAnimation = new ThicknessAnimation {
|
||||
Duration = isNoAnimation == true ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
var LeftSidePanelmarginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = isNoAnimation ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
From = LeftSidePanel.Margin,
|
||||
To = new Thickness(MarginFromEdge, 0, 0, -150)
|
||||
};
|
||||
LeftSidePanelmarginAnimation.EasingFunction = new CubicEase();
|
||||
var RightSidePanelmarginAnimation = new ThicknessAnimation {
|
||||
Duration = isNoAnimation == true ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
var RightSidePanelmarginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = isNoAnimation ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
From = RightSidePanel.Margin,
|
||||
To = new Thickness(0, 0, MarginFromEdge, -150)
|
||||
};
|
||||
@@ -260,7 +309,8 @@ namespace Ink_Canvas {
|
||||
|
||||
await Task.Delay(600);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftSidePanel.Margin = new Thickness(MarginFromEdge, 0, 0, -150);
|
||||
RightSidePanel.Margin = new Thickness(0, 0, MarginFromEdge, -150);
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
using IWshRuntimeLibrary;
|
||||
using System;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using IWshRuntimeLibrary;
|
||||
using Application = System.Windows.Forms.Application;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : Window {
|
||||
public static bool StartAutomaticallyCreate(string exeName) {
|
||||
try {
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public static bool StartAutomaticallyCreate(string exeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shell = new WshShell();
|
||||
var shortcut = (IWshShortcut)shell.CreateShortcut(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + ".lnk");
|
||||
//设置快捷方式的目标所在的位置(源程序完整路径)
|
||||
shortcut.TargetPath = System.Windows.Forms.Application.ExecutablePath;
|
||||
shortcut.TargetPath = Application.ExecutablePath;
|
||||
//应用程序的工作目录
|
||||
//当用户没有指定一个具体的目录时,快捷方式的目标应用程序将使用该属性所指定的目录来装载或保存文件。
|
||||
shortcut.WorkingDirectory = Environment.CurrentDirectory;
|
||||
@@ -28,10 +34,12 @@ namespace Ink_Canvas {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool StartAutomaticallyDel(string exeName) {
|
||||
try {
|
||||
System.IO.File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName +
|
||||
".lnk");
|
||||
public static bool StartAutomaticallyDel(string exeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName +
|
||||
".lnk");
|
||||
return true;
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
@@ -1,50 +1,55 @@
|
||||
using Microsoft.Win32;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using System;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using Microsoft.Win32;
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : Window {
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Color FloatBarForegroundColor = Color.FromRgb(102, 102, 102);
|
||||
|
||||
private void SetTheme(string theme) {
|
||||
if (theme == "Light") {
|
||||
var rd1 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/Styles/Light.xaml", UriKind.Relative) };
|
||||
private void SetTheme(string theme)
|
||||
{
|
||||
if (theme == "Light")
|
||||
{
|
||||
var rd1 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/Styles/Light.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
var rd2 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
|
||||
var rd2 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd2);
|
||||
|
||||
var rd3 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative) };
|
||||
var rd3 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
||||
|
||||
var rd4 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative) };
|
||||
var rd4 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Light);
|
||||
|
||||
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
|
||||
}
|
||||
else if (theme == "Dark") {
|
||||
var rd1 = new ResourceDictionary() { Source = new Uri("Resources/Styles/Dark.xaml", UriKind.Relative) };
|
||||
else if (theme == "Dark")
|
||||
{
|
||||
var rd1 = new ResourceDictionary { Source = new Uri("Resources/Styles/Dark.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
var rd2 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
|
||||
var rd2 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd2);
|
||||
|
||||
var rd3 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative) };
|
||||
var rd3 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
||||
|
||||
var rd4 = new ResourceDictionary()
|
||||
{ Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative) };
|
||||
var rd4 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Dark);
|
||||
@@ -53,8 +58,10 @@ namespace Ink_Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) {
|
||||
switch (Settings.Appearance.Theme) {
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
switch (Settings.Appearance.Theme)
|
||||
{
|
||||
case 0:
|
||||
SetTheme("Light");
|
||||
break;
|
||||
@@ -68,9 +75,11 @@ namespace Ink_Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSystemThemeLight() {
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
try {
|
||||
try
|
||||
{
|
||||
var registryKey = Registry.CurrentUser;
|
||||
var themeKey =
|
||||
registryKey.OpenSubKey("software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Xml.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
using Ink_Canvas.Helpers;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : Window {
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private StrokeCollection[] strokeCollections = new StrokeCollection[101];
|
||||
private bool[] whiteboadLastModeIsRedo = new bool[101];
|
||||
private StrokeCollection lastTouchDownStrokeCollection = new StrokeCollection();
|
||||
@@ -23,46 +20,173 @@ namespace Ink_Canvas {
|
||||
private int WhiteboardTotalCount = 1;
|
||||
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][]; //最多99页,0用来存储非白板时的墨迹以便还原
|
||||
|
||||
private void SaveStrokes(bool isBackupMain = false) {
|
||||
if (isBackupMain) {
|
||||
// 保存每页白板图片信息
|
||||
private void SaveStrokes(bool isBackupMain = false)
|
||||
{
|
||||
// 确保画布上的所有UI元素都被保存到时间机器历史记录中
|
||||
var currentHistory = timeMachine.ExportTimeMachineHistory();
|
||||
var elementsInHistory = new HashSet<UIElement>();
|
||||
|
||||
// 收集已经在历史记录中的元素
|
||||
if (currentHistory != null)
|
||||
{
|
||||
foreach (var h in currentHistory)
|
||||
{
|
||||
if (h.CommitType == TimeMachineHistoryType.ElementInsert &&
|
||||
h.InsertedElement != null &&
|
||||
!h.StrokeHasBeenCleared)
|
||||
{
|
||||
elementsInHistory.Add(h.InsertedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查画布上的所有UI元素,确保它们都在历史记录中
|
||||
var missingElements = 0;
|
||||
foreach (UIElement child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image || child is MediaElement)
|
||||
{
|
||||
if (!elementsInHistory.Contains(child))
|
||||
{
|
||||
timeMachine.CommitElementInsertHistory(child);
|
||||
missingElements++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 确保画布上的所有墨迹都被保存
|
||||
if (inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
// 检查是否有墨迹没有在时间机器历史记录中
|
||||
var strokesInHistory = new HashSet<Stroke>();
|
||||
if (currentHistory != null)
|
||||
{
|
||||
foreach (var h in currentHistory)
|
||||
{
|
||||
if (h.CommitType == TimeMachineHistoryType.UserInput &&
|
||||
h.CurrentStroke != null &&
|
||||
!h.StrokeHasBeenCleared)
|
||||
{
|
||||
foreach (Stroke stroke in h.CurrentStroke)
|
||||
{
|
||||
strokesInHistory.Add(stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收集没有在历史记录中的墨迹
|
||||
var missingStrokes = new StrokeCollection();
|
||||
foreach (Stroke stroke in inkCanvas.Strokes)
|
||||
{
|
||||
if (!strokesInHistory.Contains(stroke))
|
||||
{
|
||||
missingStrokes.Add(stroke);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingStrokes.Count > 0)
|
||||
{
|
||||
timeMachine.CommitStrokeUserInputHistory(missingStrokes);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBackupMain)
|
||||
{
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[0] = timeMachineHistory;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
} else {
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearStrokes(bool isErasedByCode) {
|
||||
private void ClearStrokes(bool isErasedByCode)
|
||||
{
|
||||
_currentCommitType = CommitReason.ClearingCanvas;
|
||||
if (isErasedByCode) _currentCommitType = CommitReason.CodeInput;
|
||||
|
||||
|
||||
|
||||
// 只清除笔画,不清除图片元素
|
||||
// 图片元素的清除由调用方决定
|
||||
inkCanvas.Strokes.Clear();
|
||||
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
}
|
||||
|
||||
private void RestoreStrokes(bool isBackupMain = false) {
|
||||
try {
|
||||
if (TimeMachineHistories[CurrentWhiteboardIndex] == null) return; //防止白板打开后不居中
|
||||
if (isBackupMain) {
|
||||
// 恢复每页白板图片信息
|
||||
private void RestoreStrokes(bool isBackupMain = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex;
|
||||
|
||||
// 先清空当前画布的墨迹
|
||||
inkCanvas.Strokes.Clear();
|
||||
|
||||
// 清空当前画布的所有内容(墨迹和图片)
|
||||
// 这里必须清除图片,因为页面切换时需要完全重置画布状态
|
||||
inkCanvas.Children.Clear();
|
||||
|
||||
// 如果历史记录为空,直接返回(新页面或空页面)
|
||||
if (TimeMachineHistories[targetIndex] == null)
|
||||
{
|
||||
timeMachine.ClearStrokeHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBackupMain)
|
||||
{
|
||||
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]);
|
||||
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
|
||||
// 通过时间机器历史恢复所有内容(墨迹和图片)
|
||||
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch {
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e) {
|
||||
if (sender == BtnLeftPageListWB) {
|
||||
if (BoardBorderLeftPageListView.Visibility == Visibility.Visible) {
|
||||
private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (sender == BtnLeftPageListWB)
|
||||
{
|
||||
if (BoardBorderLeftPageListView.Visibility == Visibility.Visible)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView);
|
||||
RefreshBlackBoardSidePageListView();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderLeftPageListView);
|
||||
@@ -71,11 +195,15 @@ namespace Ink_Canvas {
|
||||
(ListViewItem)BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1), BlackBoardLeftSidePageListScrollViewer);
|
||||
}
|
||||
} else if (sender == BtnRightPageListWB)
|
||||
}
|
||||
else if (sender == BtnRightPageListWB)
|
||||
{
|
||||
if (BoardBorderRightPageListView.Visibility == Visibility.Visible) {
|
||||
if (BoardBorderRightPageListView.Visibility == Visibility.Visible)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
||||
RefreshBlackBoardSidePageListView();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderRightPageListView);
|
||||
@@ -88,9 +216,21 @@ namespace Ink_Canvas {
|
||||
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e) {
|
||||
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (CurrentWhiteboardIndex <= 1) return;
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
SaveStrokes();
|
||||
|
||||
ClearStrokes(true);
|
||||
@@ -101,15 +241,29 @@ namespace Ink_Canvas {
|
||||
UpdateIndexInfoDisplay();
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e) {
|
||||
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e)
|
||||
{
|
||||
Trace.WriteLine("113223234");
|
||||
|
||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true);
|
||||
if (CurrentWhiteboardIndex >= WhiteboardTotalCount) {
|
||||
if (CurrentWhiteboardIndex >= WhiteboardTotalCount)
|
||||
{
|
||||
// 在最后一页时,点击"新页面"按钮直接新增一页
|
||||
BtnWhiteBoardAdd_Click(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
SaveStrokes();
|
||||
|
||||
@@ -121,10 +275,23 @@ namespace Ink_Canvas {
|
||||
UpdateIndexInfoDisplay();
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardAdd_Click(object sender, EventArgs e) {
|
||||
private void BtnWhiteBoardAdd_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (WhiteboardTotalCount >= 99) return;
|
||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true);
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
SaveStrokes();
|
||||
ClearStrokes(true);
|
||||
|
||||
@@ -135,16 +302,35 @@ namespace Ink_Canvas {
|
||||
for (var i = WhiteboardTotalCount; i > CurrentWhiteboardIndex; i--)
|
||||
TimeMachineHistories[i] = TimeMachineHistories[i - 1];
|
||||
|
||||
// 确保新页面的历史记录为空
|
||||
TimeMachineHistories[CurrentWhiteboardIndex] = null;
|
||||
|
||||
// 恢复新页面(这会清空画布,因为历史记录为null)
|
||||
RestoreStrokes();
|
||||
|
||||
UpdateIndexInfoDisplay();
|
||||
|
||||
if (WhiteboardTotalCount >= 99) BtnWhiteBoardAdd.IsEnabled = false;
|
||||
|
||||
if (BlackBoardLeftSidePageListView.Visibility == Visibility.Visible) {
|
||||
if (BlackBoardLeftSidePageListView.Visibility == Visibility.Visible)
|
||||
{
|
||||
RefreshBlackBoardSidePageListView();
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardDelete_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnWhiteBoardDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
ClearStrokes(true);
|
||||
|
||||
if (CurrentWhiteboardIndex != WhiteboardTotalCount)
|
||||
@@ -162,68 +348,43 @@ namespace Ink_Canvas {
|
||||
if (WhiteboardTotalCount < 99) BtnWhiteBoardAdd.IsEnabled = true;
|
||||
}
|
||||
|
||||
private void UpdateIndexInfoDisplay() {
|
||||
private void UpdateIndexInfoDisplay()
|
||||
{
|
||||
TextBlockWhiteBoardIndexInfo.Text =
|
||||
$"{CurrentWhiteboardIndex}/{WhiteboardTotalCount}";
|
||||
|
||||
if (CurrentWhiteboardIndex == WhiteboardTotalCount) {
|
||||
var newImageSource = new BitmapImage();
|
||||
newImageSource.BeginInit();
|
||||
newImageSource.UriSource = new Uri("/Resources/Icons-Fluent/ic_fluent_add_circle_24_regular.png",
|
||||
UriKind.RelativeOrAbsolute);
|
||||
newImageSource.EndInit();
|
||||
//BoardLeftPannelNextPage.Source = newImageSource;
|
||||
//BoardRightPannelNextPage.Source = newImageSource;
|
||||
//BoardRightPannelNextPageTextBlock.Text = "加页";
|
||||
//BoardLeftPannelNextPageTextBlock.Text = "加页";
|
||||
} else {
|
||||
var newImageSource = new BitmapImage();
|
||||
newImageSource.BeginInit();
|
||||
newImageSource.UriSource =
|
||||
new Uri("/Resources/Icons-Fluent/ic_fluent_arrow_circle_right_24_regular.png",
|
||||
UriKind.RelativeOrAbsolute);
|
||||
newImageSource.EndInit();
|
||||
//BoardLeftPannelNextPage.Source = newImageSource;
|
||||
//BoardRightPannelNextPage.Source = newImageSource;
|
||||
//BoardRightPannelNextPageTextBlock.Text = "下一页";
|
||||
//BoardLeftPannelNextPageTextBlock.Text = "下一页";
|
||||
}
|
||||
bool isLastPage = CurrentWhiteboardIndex == WhiteboardTotalCount;
|
||||
bool isMaxPage = WhiteboardTotalCount >= 99;
|
||||
|
||||
// 设置按钮文本
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
|
||||
// 始终允许点击“下一页/新页面”按钮(除非已达最大页数)
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage;
|
||||
|
||||
// 保持按钮常亮(高亮)
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = true;
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = true;
|
||||
|
||||
if (CurrentWhiteboardIndex == 1) {
|
||||
if (CurrentWhiteboardIndex == 1)
|
||||
{
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = false;
|
||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27));
|
||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
|
||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
||||
|
||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
||||
|
||||
if (CurrentWhiteboardIndex == WhiteboardTotalCount) {
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27));
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 0.5;
|
||||
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 0.5;
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = false;
|
||||
} else {
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user