Compare commits
277 Commits
feat/NetOffice
...
net6
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a917d529d | |||
| e45e8fad43 | |||
| 8364dde2e3 | |||
| 6105f8759d | |||
| c8e3bceab2 | |||
| f825211987 | |||
| aea4c2ce3c | |||
| 35c8e980f8 | |||
| 09713f70bf | |||
| ed7157163c | |||
| 86fdc04616 | |||
| ed44a22edb | |||
| 2c4d6f124e | |||
| 2664ee812c | |||
| b8b07f90cd | |||
| f39d0c0f82 | |||
| 81b291f2e6 | |||
| a25ea95c19 | |||
| 16724fd0ae | |||
| 723c0b9cdc | |||
| 39e098221c | |||
| 005ba66bc2 | |||
| d15db2d8a9 | |||
| 191fd25c66 | |||
| 5f1f190613 | |||
| 7736d88657 | |||
| cc5ba1364c | |||
| fcbfb326c0 | |||
| d6c1a1fd71 | |||
| 8db0e8c95c | |||
| 860dc43c8d | |||
| be34eda535 | |||
| 2d9901791c | |||
| ef9b211677 | |||
| 53fda7e512 | |||
| bbb2981208 | |||
| e1aa003b77 | |||
| b7e88371ac | |||
| 2b8dcfdc86 | |||
| 9691263aa5 | |||
| 95ee002765 | |||
| 786945f6c8 | |||
| 7a7c5f266d | |||
| 15884c5901 | |||
| 16f53acf42 | |||
| 2e61777469 | |||
| 46f4a0523c | |||
| c8848f6cde | |||
| 1fe66790e6 | |||
| dada05aabb | |||
| a18d476415 | |||
| 94142ec8a5 | |||
| fff52aa282 | |||
| 07ebbfbd24 | |||
| 2a17ea1bd1 | |||
| e801394dbe | |||
| 9fb18e020e | |||
| 82ec0ad1cd | |||
| 3853fd31f4 | |||
| 41a59136c7 | |||
| 8c7eeb51d0 | |||
| 4f5f3ed8fe | |||
| 207321651d | |||
| 267d9b4450 | |||
| 31aee3bd9a | |||
| a8c9734a5a | |||
| 138484ac19 | |||
| 1217ef7ef9 | |||
| 02cf10ab13 | |||
| db181d994a | |||
| ee4e9032f2 | |||
| 54fd9bcde6 | |||
| 6fbd8b6fac | |||
| 8383002b5c | |||
| 8c37b91b5b | |||
| 5a387eef96 | |||
| c27759189d | |||
| a33a399989 | |||
| 6c7c76958f | |||
| 6980abe331 | |||
| 5fc92cdd10 | |||
| 4bcc39a7ae | |||
| a044a8bc21 | |||
| f15a293a69 | |||
| 448a695503 | |||
| 3915893ee6 | |||
| 914e6eea2e | |||
| 3434ab6227 | |||
| 90ba3f7fa6 | |||
| f05bcf6cc8 | |||
| 21d5ee25ea | |||
| 13c73fbfe4 | |||
| 97dbedfed5 | |||
| e5ef1c6472 | |||
| 17945a9298 | |||
| 17c0ecc0f5 | |||
| 8c2fc15d81 | |||
| ac399773d0 | |||
| 199674feca | |||
| cb6af3e21f | |||
| efe0bb6ae2 | |||
| 4a1403eed3 | |||
| 77a7628453 | |||
| 21186d1edb | |||
| 1cb77b4f48 | |||
| a6f92a883c | |||
| 2c0b09a9ad | |||
| 003fffca2d | |||
| 28b719e9db | |||
| 4d8fbef455 | |||
| e55f8e8ea1 | |||
| 8ad987a64b | |||
| b0aff1e378 | |||
| 6239ac0b88 | |||
| 875fa9c6f0 | |||
| a7b020b0ff | |||
| b614517728 | |||
| 329e9bd933 | |||
| 73f27a9423 | |||
| ffee3aa564 | |||
| 9268ebb580 | |||
| 40fc4e89e0 | |||
| 6fd4a4f036 | |||
| 483991b85c | |||
| a0d24ea6cc | |||
| 9613285513 | |||
| 649a939a57 | |||
| f20e360c0b | |||
| c1e599971e | |||
| 21c93d38c7 | |||
| 4902559cfa | |||
| 49b22dc184 | |||
| f1f75ff015 | |||
| 6e68fa9cfc | |||
| c68596b91e | |||
| 7d52573595 | |||
| 6ac34ba8aa | |||
| 9136f1dbe3 | |||
| 98cfdb53c3 | |||
| e17387c99e | |||
| d30ed9e726 | |||
| 988af60a30 | |||
| 8b53456b5d | |||
| d23193527e | |||
| b58376847e | |||
| d0551db70a | |||
| e416151a56 | |||
| b02b496b88 | |||
| e31dbbcedc | |||
| 09ad1bac86 | |||
| a2ac761f70 | |||
| 9e5ad7e2b4 | |||
| 4f015fb155 | |||
| b4526473fa | |||
| 3ab9fcc857 | |||
| 7ef231a4c7 | |||
| da3a1f7dea | |||
| 81001542b8 | |||
| aacf5b696c | |||
| 062c0feb65 | |||
| 92f97eab78 | |||
| 31120eab09 | |||
| 9b7d718f78 | |||
| 9132c9eb14 | |||
| adebf8ec38 | |||
| 854e14f9b0 | |||
| d7faac257c | |||
| 1b3c5294d9 | |||
| f7802ba4d0 | |||
| 8e84dce73f | |||
| 620da0e645 | |||
| c4657ebb86 | |||
| 24cbaf69ea | |||
| f3ef2f7aec | |||
| 77dff81217 | |||
| 004364c3a9 | |||
| 877d702978 | |||
| a8bc9f442b | |||
| f09f05ab3c | |||
| ef5377f85c | |||
| 5dbdd7bf12 | |||
| d73e3eb0ea | |||
| b7d03d694c | |||
| b7d4983af5 | |||
| 919cf8a736 | |||
| 8a7d2ddf30 | |||
| b1640f44c2 | |||
| 6e61538dec | |||
| f789a919e1 | |||
| b9651240df | |||
| 5266368f79 | |||
| 42854ff924 | |||
| 48b0e09278 | |||
| f05062f902 | |||
| 532aa03c56 | |||
| b891cb6fe3 | |||
| 0683779e09 | |||
| b949b1651a | |||
| 259ce3dd11 | |||
| cab703b98e | |||
| a57e0496ee | |||
| df66a49ba5 | |||
| 245a29f797 | |||
| 4e8f574fdb | |||
| a5d6135f0c | |||
| 12c7fb1713 | |||
| 4dd56a4e5d | |||
| abe5992d21 | |||
| 8d74b6ee30 | |||
| 8b5797ac66 | |||
| 231b850f74 | |||
| 7003bb8426 | |||
| c4b5be783e | |||
| c38829f7d8 | |||
| fdf3633d8c | |||
| 3916476af7 | |||
| edc94547ab | |||
| 8a8cf9a679 | |||
| 21f3b99838 | |||
| 3bfa0b1d85 | |||
| de55fd43a0 | |||
| 6e68e53b1e | |||
| 4b8d89854e | |||
| 8298f7d5bb | |||
| 816153a1db | |||
| 1e8ddf4754 | |||
| d73e87b980 | |||
| b64b0a3618 | |||
| d52ae90b56 | |||
| 0045f97569 | |||
| 41be1e901d | |||
| 0a14d96e10 | |||
| 57c3fe358b | |||
| c255db6ff3 | |||
| f6484f759e | |||
| b16636d06b | |||
| 220a316d70 | |||
| dbac80509a | |||
| bb82eb4891 | |||
| b56f99fff9 | |||
| 4151dfaf1f | |||
| 836e0b0fde | |||
| a7fe7937a5 | |||
| dba59b92ac | |||
| 0d6ffa6de6 | |||
| 9edf5ee36c | |||
| d6f20a365e | |||
| f7befe387c | |||
| b1a34b7a64 | |||
| d556cf7741 | |||
| 76a046cca6 | |||
| d6385aea1c | |||
| 31bf2b5af1 | |||
| de613c8a4b | |||
| 84b626d344 | |||
| 256b3c0887 | |||
| 77cf91e802 | |||
| ea55eb1738 | |||
| 302ef307fe | |||
| 3f3a10de7d | |||
| c73123cd23 | |||
| 73935e0f22 | |||
| 0dfe835eef | |||
| 1ec07421b5 | |||
| 441e600b5d | |||
| bd4b4bd233 | |||
| f0343c98b5 | |||
| 36ff945384 | |||
| 1b01e4a5c5 | |||
| 5eff424b2d | |||
| 998d829783 | |||
| 226a6942dc | |||
| 52a3d184c7 | |||
| 44cdab063c | |||
| 67a70fd6e4 | |||
| 2e8660de26 | |||
| 379d514bb5 |
@@ -143,6 +143,15 @@
|
|||||||
"infra",
|
"infra",
|
||||||
"blog"
|
"blog"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Hao3288",
|
||||||
|
"name": "NoobHao",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/119276078?v=4",
|
||||||
|
"profile": "https://github.com/Hao3288",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"repoType": "github"
|
"repoType": "github"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: .NET Build & Package
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, beta ]
|
branches: [ net6 ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Check if exe file is generated
|
- name: Check if exe file is generated
|
||||||
id: check-exe
|
id: check-exe
|
||||||
run: |
|
run: |
|
||||||
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||||
|
|
||||||
if (Test-Path $exePath) {
|
if (Test-Path $exePath) {
|
||||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
||||||
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net472/*"
|
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net6.0-windows10.0.19041.0/*"
|
||||||
|
|
||||||
- name: Create Summary
|
- name: Create Summary
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: PR Check
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
branches: [ main, beta ]
|
branches: [ main, net6 ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Check if exe file is generated
|
- name: Check if exe file is generated
|
||||||
id: check-exe
|
id: check-exe
|
||||||
run: |
|
run: |
|
||||||
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||||
|
|
||||||
if (Test-Path $exePath) {
|
if (Test-Path $exePath) {
|
||||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
||||||
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net472/*"
|
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net6.0-windows10.0.19041.0/*"
|
||||||
|
|
||||||
|
|
||||||
- name: Create Summary
|
- name: Create Summary
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ jobs:
|
|||||||
- name: Check if exe file is generated
|
- name: Check if exe file is generated
|
||||||
id: check-exe
|
id: check-exe
|
||||||
run: |
|
run: |
|
||||||
$exePath = "Ink Canvas\bin\Release\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
$exePath = "Ink Canvas\bin\Release\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||||
|
|
||||||
if (Test-Path $exePath) {
|
if (Test-Path $exePath) {
|
||||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||||
@@ -284,7 +284,7 @@ jobs:
|
|||||||
New-Item -ItemType Directory -Path "release" -Force
|
New-Item -ItemType Directory -Path "release" -Force
|
||||||
|
|
||||||
# 复制发布文件(使用架构特定的路径)
|
# 复制发布文件(使用架构特定的路径)
|
||||||
Copy-Item "Ink Canvas\bin\Release\$architecture\net472\*" "release/" -Recurse -Force
|
Copy-Item "Ink Canvas\bin\Release\$architecture\net6.0-windows10.0.19041.0\*" "release/" -Recurse -Force
|
||||||
|
|
||||||
# 创建压缩包
|
# 创建压缩包
|
||||||
Compress-Archive -Path "release/*" -DestinationPath $archiveName -Force
|
Compress-Archive -Path "release/*" -DestinationPath $archiveName -Force
|
||||||
@@ -305,6 +305,7 @@ jobs:
|
|||||||
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
|
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
|
||||||
|
|
||||||
# 替换源文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
# 替换源文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
||||||
|
$issContent = $issContent -replace 'Source: "release\\\*";', 'Source: "../release/*";'
|
||||||
$issContent = $issContent -replace 'Source: ".*\\{#MyAppExeName}";', 'Source: "../release/{#MyAppExeName}";'
|
$issContent = $issContent -replace 'Source: ".*\\{#MyAppExeName}";', 'Source: "../release/{#MyAppExeName}";'
|
||||||
$issContent = $issContent -replace 'Source: ".*\\InkCanvasForClass.exe.config";', 'Source: "../release/InkCanvasForClass.exe.config";'
|
$issContent = $issContent -replace 'Source: ".*\\InkCanvasForClass.exe.config";', 'Source: "../release/InkCanvasForClass.exe.config";'
|
||||||
|
|
||||||
@@ -750,5 +751,5 @@ jobs:
|
|||||||
/repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt \
|
/repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt \
|
||||||
-f message="Update AutomaticUpdateVersionControl.txt" \
|
-f message="Update AutomaticUpdateVersionControl.txt" \
|
||||||
-f content="$CONTENT" \
|
-f content="$CONTENT" \
|
||||||
-f branch="beta" \
|
-f branch="net6" \
|
||||||
${SHA:+-f sha="$SHA"}
|
${SHA:+-f sha="$SHA"}
|
||||||
|
|||||||
+2
-1
@@ -429,4 +429,5 @@ FodyWeavers.xsd
|
|||||||
|
|
||||||
# Telemetry DSN configuration file (contains sensitive information)
|
# Telemetry DSN configuration file (contains sensitive information)
|
||||||
telemetry_dsn.txt
|
telemetry_dsn.txt
|
||||||
**/telemetry_dsn.txt
|
**/telemetry_dsn.txt
|
||||||
|
.trae/skills/migrate-toggle-switch/SKILL.md
|
||||||
|
|||||||
+69
-3
@@ -1,10 +1,16 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 17.5.33530.505
|
VisualStudioVersion = 18.4.11626.88 stable
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "Ink Canvas\InkCanvasForClass.csproj", "{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "Ink Canvas\InkCanvasForClass.csproj", "{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.PluginSdk", "InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj", "{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.IACoreHelper", "InkCanvas.IACoreHelper\InkCanvas.IACoreHelper.csproj", "{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -39,6 +45,66 @@ Global
|
|||||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|Any CPU
|
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|Any CPU
|
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|Any CPU
|
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.ActiveCfg = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.Build.0 = Debug|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.Build.0 = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.ActiveCfg = Release|x86
|
||||||
|
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.Build.0 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
+1
-3
@@ -9,9 +9,7 @@
|
|||||||
>
|
>
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<Style TargetType="ui:ScrollViewerEx">
|
<FontFamily x:Key="HarmonyOSFont">./Resources/Fonts/#HarmonyOS Sans SC</FontFamily>
|
||||||
<EventSetter Event="PreviewMouseWheel" Handler="ScrollViewer_PreviewMouseWheel"/>
|
|
||||||
</Style>
|
|
||||||
<ContextMenu Opened="SysTrayMenu_Opened" Closed="SysTrayMenu_Closed" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
<ContextMenu Opened="SysTrayMenu_Opened" Closed="SysTrayMenu_Closed" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||||
<MenuItem IsCheckable="True" IsChecked="False" Checked="HideICCMainWindowTrayIconMenuItem_Checked" Unchecked="HideICCMainWindowTrayIconMenuItem_UnChecked" Name="HideICCMainWindowTrayIconMenuItem">
|
<MenuItem IsCheckable="True" IsChecked="False" Checked="HideICCMainWindowTrayIconMenuItem_Checked" Unchecked="HideICCMainWindowTrayIconMenuItem_UnChecked" Name="HideICCMainWindowTrayIconMenuItem">
|
||||||
<MenuItem.Header>
|
<MenuItem.Header>
|
||||||
|
|||||||
+325
-150
@@ -1,5 +1,6 @@
|
|||||||
using H.NotifyIcon;
|
using H.NotifyIcon;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
|
using Ink_Canvas.Plugins;
|
||||||
using Ink_Canvas.Properties;
|
using Ink_Canvas.Properties;
|
||||||
using iNKORE.UI.WPF.Modern.Controls;
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
@@ -7,6 +8,7 @@ using Newtonsoft.Json;
|
|||||||
using Sentry;
|
using Sentry;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -17,6 +19,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Interop;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using Application = System.Windows.Application;
|
using Application = System.Windows.Application;
|
||||||
using MessageBox = System.Windows.MessageBox;
|
using MessageBox = System.Windows.MessageBox;
|
||||||
@@ -32,6 +35,20 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
|
|
||||||
|
public void ReleaseMutexForRestart()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (mutex != null)
|
||||||
|
{
|
||||||
|
mutex.ReleaseMutex();
|
||||||
|
mutex.Dispose();
|
||||||
|
mutex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
public static string[] StartArgs;
|
public static string[] StartArgs;
|
||||||
public static string RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
public static string RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
||||||
|
|
||||||
@@ -61,9 +78,16 @@ namespace Ink_Canvas
|
|||||||
private static string lastErrorMessage = string.Empty;
|
private static string lastErrorMessage = string.Empty;
|
||||||
// 新增:是否已初始化崩溃监听器
|
// 新增:是否已初始化崩溃监听器
|
||||||
private static bool crashListenersInitialized;
|
private static bool crashListenersInitialized;
|
||||||
|
private IntPtr processDestroyHook = IntPtr.Zero;
|
||||||
|
private IntPtr monitoredMainWindowHandle = IntPtr.Zero;
|
||||||
|
private bool mainWindowDestroyedLogged;
|
||||||
|
private WinEventDelegate processDestroyHookCallback;
|
||||||
// 新增:启动画面相关
|
// 新增:启动画面相关
|
||||||
private static SplashScreen _splashScreen;
|
private static SplashScreen _splashScreen;
|
||||||
private static bool _isSplashScreenShown = false;
|
private static bool _isSplashScreenShown = false;
|
||||||
|
private static System.Resources.ResourceSet _pendingLocalizedResourceSet;
|
||||||
|
private static readonly Stopwatch startupStopwatch = new Stopwatch();
|
||||||
|
private static readonly Stopwatch splashStopwatch = new Stopwatch();
|
||||||
|
|
||||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
private static extern int SetCurrentProcessExplicitAppUserModelID(string appId);
|
private static extern int SetCurrentProcessExplicitAppUserModelID(string appId);
|
||||||
@@ -192,15 +216,13 @@ namespace Ink_Canvas
|
|||||||
// 尝试注册Windows关闭消息监听
|
// 尝试注册Windows关闭消息监听
|
||||||
SetConsoleCtrlHandler(ConsoleCtrlHandler, true);
|
SetConsoleCtrlHandler(ConsoleCtrlHandler, true);
|
||||||
|
|
||||||
// 如果系统支持,添加Windows Management Instrumentation监听器
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 使用反射动态加载和调用WMI
|
TrySetupTerminationMonitoring();
|
||||||
TrySetupWmiMonitoring();
|
|
||||||
}
|
}
|
||||||
catch (Exception wmiEx)
|
catch (Exception monitorEx)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"设置WMI进程监控失败: {wmiEx.Message}", LogHelper.LogType.Warning);
|
LogHelper.WriteLogToFile($"设置终止监控失败: {monitorEx.Message}", LogHelper.LogType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
crashListenersInitialized = true;
|
crashListenersInitialized = true;
|
||||||
@@ -212,80 +234,114 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动态加载WMI监控
|
private void TrySetupTerminationMonitoring()
|
||||||
private void TrySetupWmiMonitoring()
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 检查System.Management程序集是否可用
|
processDestroyHookCallback = OnWinEventMainWindowDestroyed;
|
||||||
var assemblyName = "System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
|
|
||||||
var assembly = Assembly.Load(assemblyName);
|
|
||||||
if (assembly == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到System.Management程序集,跳过WMI监控", LogHelper.LogType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用反射创建WMI查询
|
// 等主窗口句柄可用后再开始监听
|
||||||
var watcherType = assembly.GetType("System.Management.ManagementEventWatcher");
|
Dispatcher.BeginInvoke(new Action(BindMainWindowLifecycle), DispatcherPriority.ApplicationIdle);
|
||||||
if (watcherType == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到ManagementEventWatcher类型,跳过WMI监控", LogHelper.LogType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建WMI查询字符串
|
|
||||||
string queryString = $"SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = {currentProcessId}";
|
|
||||||
|
|
||||||
// 创建ManagementEventWatcher实例
|
|
||||||
object watcher = Activator.CreateInstance(watcherType, queryString);
|
|
||||||
|
|
||||||
// 获取EventArrived事件信息
|
|
||||||
var eventInfo = watcherType.GetEvent("EventArrived");
|
|
||||||
if (eventInfo == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到EventArrived事件,跳过WMI监控", LogHelper.LogType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建委托并订阅事件
|
|
||||||
Type delegateType = eventInfo.EventHandlerType;
|
|
||||||
var handler = Delegate.CreateDelegate(delegateType, this, GetType().GetMethod("WmiEventHandler", BindingFlags.NonPublic | BindingFlags.Instance));
|
|
||||||
eventInfo.AddEventHandler(watcher, handler);
|
|
||||||
|
|
||||||
// 启动监听
|
|
||||||
var startMethod = watcherType.GetMethod("Start");
|
|
||||||
startMethod.Invoke(watcher, null);
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("已成功启动WMI进程监控");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"动态加载WMI监控失败: {ex.Message}", LogHelper.LogType.Warning);
|
LogHelper.WriteLogToFile($"初始化终止监控失败: {ex.GetType().FullName}: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WMI事件处理方法
|
private void BindMainWindowLifecycle()
|
||||||
private void WmiEventHandler(object sender, EventArgs e)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 尝试从事件参数中提取信息
|
if (Current?.MainWindow == null)
|
||||||
dynamic eventArgs = e;
|
|
||||||
dynamic newEvent = eventArgs.NewEvent;
|
|
||||||
if (newEvent != null)
|
|
||||||
{
|
{
|
||||||
dynamic targetInstance = newEvent["TargetInstance"];
|
return;
|
||||||
if (targetInstance != null)
|
}
|
||||||
{
|
|
||||||
string processName = targetInstance["Name"]?.ToString() ?? "未知进程";
|
Current.MainWindow.SourceInitialized -= MainWindow_SourceInitialized;
|
||||||
WriteCrashLog($"WMI检测到进程{processName}(ID:{currentProcessId})已终止");
|
Current.MainWindow.SourceInitialized += MainWindow_SourceInitialized;
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainWindow_SourceInitialized(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!(sender is Window window))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitoredMainWindowHandle = new WindowInteropHelper(window).Handle;
|
||||||
|
if (monitoredMainWindowHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterMainWindowDestroyHook();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterMainWindowDestroyHook()
|
||||||
|
{
|
||||||
|
if (processDestroyHook != IntPtr.Zero || monitoredMainWindowHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processDestroyHook = SetWinEventHook(
|
||||||
|
EVENT_OBJECT_DESTROY,
|
||||||
|
EVENT_OBJECT_DESTROY,
|
||||||
|
IntPtr.Zero,
|
||||||
|
processDestroyHookCallback,
|
||||||
|
(uint)currentProcessId,
|
||||||
|
0,
|
||||||
|
WINEVENT_OUTOFCONTEXT);
|
||||||
|
|
||||||
|
if (processDestroyHook == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWinEventMainWindowDestroyed(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||||
|
{
|
||||||
|
if (eventType != EVENT_OBJECT_DESTROY || mainWindowDestroyedLogged)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idObject != OBJID_WINDOW || idChild != CHILDID_SELF)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hwnd != monitoredMainWindowHandle || hwnd == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindowDestroyedLogged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupTerminationMonitoring()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (processDestroyHook != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
UnhookWinEvent(processDestroyHook);
|
||||||
|
processDestroyHook = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"处理WMI事件时出错: {ex.Message}", LogHelper.LogType.Warning);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +350,19 @@ namespace Ink_Canvas
|
|||||||
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);
|
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);
|
||||||
|
|
||||||
private delegate bool ConsoleCtrlDelegate(int ctrlType);
|
private delegate bool ConsoleCtrlDelegate(int ctrlType);
|
||||||
|
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
|
||||||
|
|
||||||
|
private const uint EVENT_OBJECT_DESTROY = 0x8001;
|
||||||
|
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||||
|
private const int OBJID_WINDOW = 0;
|
||||||
|
private const int CHILDID_SELF = 0;
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||||
|
|
||||||
private static bool ConsoleCtrlHandler(int ctrlType)
|
private static bool ConsoleCtrlHandler(int ctrlType)
|
||||||
{
|
{
|
||||||
@@ -428,6 +497,7 @@ namespace Ink_Canvas
|
|||||||
// 处理进程退出事件
|
// 处理进程退出事件
|
||||||
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
|
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
CleanupTerminationMonitoring();
|
||||||
TimeSpan runDuration = DateTime.Now - appStartTime;
|
TimeSpan runDuration = DateTime.Now - appStartTime;
|
||||||
string durationText = FormatTimeSpan(runDuration);
|
string durationText = FormatTimeSpan(runDuration);
|
||||||
WriteCrashLog($"应用程序退出,运行时长: {durationText}");
|
WriteCrashLog($"应用程序退出,运行时长: {durationText}");
|
||||||
@@ -476,6 +546,7 @@ namespace Ink_Canvas
|
|||||||
_splashScreen.Show();
|
_splashScreen.Show();
|
||||||
_isSplashScreenShown = true;
|
_isSplashScreenShown = true;
|
||||||
splashScreenStartTime = DateTime.Now;
|
splashScreenStartTime = DateTime.Now;
|
||||||
|
splashStopwatch.Restart();
|
||||||
LogHelper.WriteLogToFile("启动画面已显示");
|
LogHelper.WriteLogToFile("启动画面已显示");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -695,20 +766,24 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
appStartTime = DateTime.Now;
|
appStartTime = DateTime.Now;
|
||||||
appStartupStartTime = DateTime.Now;
|
appStartupStartTime = DateTime.Now;
|
||||||
|
startupStopwatch.Restart();
|
||||||
|
|
||||||
|
TryApplyPreferredLanguageFromSettings();
|
||||||
|
|
||||||
|
_pendingLocalizedResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
|
||||||
|
|
||||||
// 根据设置决定是否显示启动画面
|
// 根据设置决定是否显示启动画面
|
||||||
if (ShouldShowSplashScreen() && !IsLaunchByFileOrUri(e.Args))
|
if (ShouldShowSplashScreen() && !IsLaunchByFileOrUri(e.Args))
|
||||||
{
|
{
|
||||||
ShowSplashScreen();
|
ShowSplashScreen();
|
||||||
SetSplashMessage(Strings.GetString("Splash_Starting"));
|
SetSplashMessage(Strings.GetString("Splash_Starting"));
|
||||||
SetSplashProgress(20);
|
SetSplashProgress(25);
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
// 强制刷新UI,确保启动画面显示
|
// 强制刷新UI,确保启动画面显示
|
||||||
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
|
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(100);
|
||||||
RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
||||||
|
|
||||||
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version));
|
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version));
|
||||||
@@ -739,64 +814,13 @@ namespace Ink_Canvas
|
|||||||
LogHelper.WriteLogToFile("App | 检测到最终应用启动(更新后的应用)");
|
LogHelper.WriteLogToFile("App | 检测到最终应用启动(更新后的应用)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 释放IACore相关DLL
|
|
||||||
if (_isSplashScreenShown)
|
|
||||||
{
|
|
||||||
SetSplashMessage("正在初始化组件...");
|
|
||||||
SetSplashProgress(40);
|
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IACoreDllExtractor.ExtractIACoreDlls();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 释放UIAccess DLL
|
|
||||||
if (_isSplashScreenShown)
|
|
||||||
{
|
|
||||||
SetSplashMessage("正在初始化组件...");
|
|
||||||
SetSplashProgress(50);
|
|
||||||
await Task.Delay(300);
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
UIAccessDllExtractor.ExtractUIAccessDlls();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"释放UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录应用启动(设备标识符)
|
|
||||||
if (_isSplashScreenShown)
|
if (_isSplashScreenShown)
|
||||||
{
|
{
|
||||||
SetSplashMessage("正在加载配置...");
|
SetSplashMessage("正在加载配置...");
|
||||||
SetSplashProgress(60);
|
SetSplashProgress(50);
|
||||||
await Task.Delay(500);
|
await Task.Delay(100);
|
||||||
}
|
}
|
||||||
DeviceIdentifier.RecordAppLaunch();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var systemVersion = DeviceIdentifier.GetSystemVersion();
|
|
||||||
if (!string.IsNullOrWhiteSpace(systemVersion))
|
|
||||||
{
|
|
||||||
SentrySdk.ConfigureScope(scope =>
|
|
||||||
{
|
|
||||||
scope.SetTag("system_version", systemVersion);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"App | 初始化系统版本遥测标签失败: {ex.Message}", LogHelper.LogType.Warning);
|
|
||||||
}
|
|
||||||
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
|
||||||
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
|
|
||||||
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
|
||||||
|
|
||||||
// 处理更新模式启动
|
// 处理更新模式启动
|
||||||
bool isUpdateMode = AutoUpdateHelper.HandleUpdateModeStartup(e.Args);
|
bool isUpdateMode = AutoUpdateHelper.HandleUpdateModeStartup(e.Args);
|
||||||
@@ -831,7 +855,7 @@ namespace Ink_Canvas
|
|||||||
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1061,7 +1085,6 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
_taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon");
|
_taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon");
|
||||||
_taskbar.ForceCreate();
|
|
||||||
|
|
||||||
StartArgs = e.Args;
|
StartArgs = e.Args;
|
||||||
|
|
||||||
@@ -1069,39 +1092,59 @@ namespace Ink_Canvas
|
|||||||
if (_isSplashScreenShown)
|
if (_isSplashScreenShown)
|
||||||
{
|
{
|
||||||
SetSplashMessage("正在初始化主界面...");
|
SetSplashMessage("正在初始化主界面...");
|
||||||
SetSplashProgress(80);
|
SetSplashProgress(75);
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
}
|
||||||
var mainWindow = new MainWindow();
|
var mainWindow = new MainWindow();
|
||||||
MainWindow = mainWindow;
|
MainWindow = mainWindow;
|
||||||
|
|
||||||
|
// 注册 InkCanvas 服务供插件使用
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var inkCanvasService = new Plugins.InkCanvasService(mainWindow);
|
||||||
|
Plugins.PluginManager.Instance.RegisterService<Plugins.IInkCanvasService>(inkCanvasService);
|
||||||
|
LogHelper.WriteLogToFile("InkCanvasService registered for plugins");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"Failed to register InkCanvasService: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var appRestartService = new Plugins.AppRestartService();
|
||||||
|
Plugins.PluginManager.Instance.RegisterService<Plugins.IAppRestartService>(appRestartService);
|
||||||
|
LogHelper.WriteLogToFile("AppRestartService registered for plugins");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"Failed to register AppRestartService: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
// 主窗口加载完成后关闭启动画面
|
// 主窗口加载完成后关闭启动画面
|
||||||
mainWindow.Loaded += (s, args) =>
|
mainWindow.Loaded += (s, args) =>
|
||||||
{
|
{
|
||||||
isStartupComplete = true;
|
isStartupComplete = true;
|
||||||
startupCompleteHeartbeat = DateTime.Now;
|
startupCompleteHeartbeat = DateTime.Now;
|
||||||
if (_isSplashScreenShown && splashScreenStartTime != DateTime.MinValue)
|
if (_isSplashScreenShown && splashStopwatch.IsRunning)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"启动完成心跳已记录,启动画面显示时长: {(startupCompleteHeartbeat - splashScreenStartTime).TotalSeconds:F2}秒");
|
LogHelper.WriteLogToFile($"启动完成心跳已记录,启动画面显示时长: {splashStopwatch.Elapsed.TotalSeconds:F2}秒");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"启动完成心跳已记录");
|
LogHelper.WriteLogToFile($"启动完成心跳已记录");
|
||||||
}
|
}
|
||||||
LogHelper.WriteLogToFile($"启动时长: {(startupCompleteHeartbeat - appStartupStartTime).TotalSeconds:F2}秒");
|
LogHelper.WriteLogToFile($"启动时长: {startupStopwatch.Elapsed.TotalSeconds:F2}秒");
|
||||||
|
|
||||||
if (_isSplashScreenShown)
|
if (_isSplashScreenShown)
|
||||||
{
|
{
|
||||||
SetSplashMessage("完成初始化...");
|
SetSplashMessage("启动完成!");
|
||||||
SetSplashProgress(80);
|
SetSplashProgress(100);
|
||||||
Task.Delay(300).ContinueWith(_ =>
|
Task.Delay(100).ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
SetSplashMessage("启动完成!");
|
|
||||||
SetSplashProgress(100);
|
|
||||||
// 延迟关闭启动画面,让用户看到完成消息
|
// 延迟关闭启动画面,让用户看到完成消息
|
||||||
Task.Delay(500).ContinueWith(__ =>
|
Task.Delay(100).ContinueWith(__ =>
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() => CloseSplashScreen());
|
Dispatcher.Invoke(() => CloseSplashScreen());
|
||||||
});
|
});
|
||||||
@@ -1111,6 +1154,19 @@ namespace Ink_Canvas
|
|||||||
};
|
};
|
||||||
|
|
||||||
mainWindow.Show();
|
mainWindow.Show();
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(600);
|
||||||
|
Dispatcher.Invoke(() => _taskbar?.ForceCreate());
|
||||||
|
});
|
||||||
|
_ = Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
if (_pendingLocalizedResourceSet != null)
|
||||||
|
{
|
||||||
|
LoadLocalizedResources(_pendingLocalizedResourceSet);
|
||||||
|
_pendingLocalizedResourceSet = null;
|
||||||
|
}
|
||||||
|
}), DispatcherPriority.ApplicationIdle);
|
||||||
|
|
||||||
// 处理启动时的URI参数
|
// 处理启动时的URI参数
|
||||||
string startupUriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
|
string startupUriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
|
||||||
@@ -1118,7 +1174,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"App | 处理启动URI参数: {startupUriArg}", LogHelper.LogType.Event);
|
LogHelper.WriteLogToFile($"App | 处理启动URI参数: {startupUriArg}", LogHelper.LogType.Event);
|
||||||
// 延迟一点执行,确保窗口初始化完成
|
// 延迟一点执行,确保窗口初始化完成
|
||||||
Task.Delay(1000).ContinueWith(_ =>
|
_ = Task.Delay(1000).ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
mainWindow.Dispatcher.Invoke(() =>
|
mainWindow.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
@@ -1127,40 +1183,139 @@ namespace Ink_Canvas
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册.icstk文件关联
|
_ = RunDeferredStartupTasksAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunDeferredStartupTasksAsync()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
await Task.Delay(1200);
|
||||||
FileAssociationManager.RegisterFileAssociation();
|
|
||||||
FileAssociationManager.ShowFileAssociationStatus();
|
try
|
||||||
|
{
|
||||||
|
IACoreDllExtractor.ExtractIACoreDlls();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var shapeMode = ShapeRecognitionRouter.FromSettingsInt(
|
||||||
|
Ink_Canvas.Windows.SettingsViews.Helpers.SettingsManager.Settings?.InkToShape?.ShapeRecognitionEngine ?? 0);
|
||||||
|
if (!ShapeRecognitionRouter.ResolveUseWinRt(shapeMode))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("启动 IACore IPC 辅助进程");
|
||||||
|
bool ipcStarted = IpcIACoreClient.Instance.Start();
|
||||||
|
LogHelper.WriteLogToFile($"IACore IPC 辅助进程{(ipcStarted ? "启动成功" : "启动失败")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"启动 IACore IPC 辅助进程时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
||||||
|
FileAssociationManager.RegisterFileAssociation();
|
||||||
|
FileAssociationManager.ShowFileAssociationStatus();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("启动IPC监听器");
|
||||||
|
FileAssociationManager.StartIpcListener();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("初始化上传帮助类");
|
||||||
|
Helpers.UploadHelper.Initialize();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("开始加载插件");
|
||||||
|
await PluginManager.Instance.LoadAllAsync();
|
||||||
|
LogHelper.WriteLogToFile(string.Format("插件加载完成,共加载 {0} 个插件", PluginManager.Instance.Plugins.Count));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile(string.Format("加载插件时出错: {0}", ex.Message), LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(1500);
|
||||||
|
DeviceIdentifier.RecordAppLaunch();
|
||||||
|
var systemVersion = DeviceIdentifier.GetSystemVersion();
|
||||||
|
if (!string.IsNullOrWhiteSpace(systemVersion))
|
||||||
|
{
|
||||||
|
SentrySdk.ConfigureScope(scope =>
|
||||||
|
{
|
||||||
|
scope.SetTag("system_version", systemVersion);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
||||||
|
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
|
||||||
|
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"App | 初始化设备统计与遥测标签失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"启动阶段任务执行失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 启动IPC监听器
|
private void TryApplyPreferredLanguageFromSettings()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("启动IPC监听器");
|
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
|
||||||
FileAssociationManager.StartIpcListener();
|
if (!File.Exists(settingsPath)) return;
|
||||||
|
|
||||||
|
var json = File.ReadAllText(settingsPath);
|
||||||
|
dynamic obj = JsonConvert.DeserializeObject(json);
|
||||||
|
string preferredLanguage = obj?["appearance"]?["language"]?.ToString();
|
||||||
|
if (!string.IsNullOrWhiteSpace(preferredLanguage))
|
||||||
|
{
|
||||||
|
LocalizationHelper.TrySetCulture(preferredLanguage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"启动时预加载语言失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化上传帮助类
|
private void LoadLocalizedResources(System.Resources.ResourceSet resourceSet)
|
||||||
try
|
{
|
||||||
|
foreach (System.Collections.DictionaryEntry entry in resourceSet)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("初始化上传帮助类");
|
if (entry.Key is string key && entry.Value is string value)
|
||||||
Helpers.UploadHelper.Initialize();
|
Current.Resources[key] = value;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||||
@@ -1437,6 +1592,26 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
private void App_Exit(object sender, ExitEventArgs e)
|
private void App_Exit(object sender, ExitEventArgs e)
|
||||||
{
|
{
|
||||||
|
CleanupTerminationMonitoring();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IpcIACoreClient.Instance.Dispose();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// 卸载所有插件
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("正在卸载插件...");
|
||||||
|
PluginManager.Instance.UnloadAll();
|
||||||
|
LogHelper.WriteLogToFile("插件卸载完成");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
// 仅在软件内主动退出时关闭看门狗,并写入退出信号
|
// 仅在软件内主动退出时关闭看门狗,并写入退出信号
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Reflection;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
// set of attributes. Change these attribute values to modify the information
|
// set of attributes. Change these attribute values to modify the information
|
||||||
// associated with an assembly.
|
// associated with an assembly.
|
||||||
@@ -43,5 +44,5 @@ using System.Windows;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.7.18.9")]
|
[assembly: AssemblyVersion("1.7.18.10")]
|
||||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
[assembly: AssemblyFileVersion("1.7.18.10")]
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<UserControl x:Class="Ink_Canvas.Controls.ImageSelectionOverlay"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||||
|
Background="{x:Null}"
|
||||||
|
IsHitTestVisible="True">
|
||||||
|
<Canvas x:Name="OverlayRoot" Background="Transparent" IsHitTestVisible="True" ClipToBounds="False">
|
||||||
|
<Rectangle x:Name="MoveSurface"
|
||||||
|
Fill="Transparent"
|
||||||
|
Cursor="SizeAll"
|
||||||
|
IsHitTestVisible="True" />
|
||||||
|
<Rectangle x:Name="FrameBorder"
|
||||||
|
Stroke="#22D3A9"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
Fill="Transparent"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
SnapsToDevicePixels="True" />
|
||||||
|
|
||||||
|
<Line x:Name="RotationLine"
|
||||||
|
Stroke="#22D3A9"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
SnapsToDevicePixels="True" />
|
||||||
|
|
||||||
|
<Ellipse x:Name="RotationHandle"
|
||||||
|
Width="14" Height="14"
|
||||||
|
Fill="#22D3A9"
|
||||||
|
Stroke="White"
|
||||||
|
StrokeThickness="2"
|
||||||
|
Cursor="Hand" />
|
||||||
|
|
||||||
|
<Ellipse x:Name="TopLeftHandle"
|
||||||
|
Width="12" Height="12"
|
||||||
|
Fill="White"
|
||||||
|
Stroke="#22D3A9"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
Cursor="SizeNWSE" />
|
||||||
|
<Ellipse x:Name="TopRightHandle"
|
||||||
|
Width="12" Height="12"
|
||||||
|
Fill="White"
|
||||||
|
Stroke="#22D3A9"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
Cursor="SizeNESW" />
|
||||||
|
<Ellipse x:Name="BottomLeftHandle"
|
||||||
|
Width="12" Height="12"
|
||||||
|
Fill="White"
|
||||||
|
Stroke="#22D3A9"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
Cursor="SizeNESW" />
|
||||||
|
<Ellipse x:Name="BottomRightHandle"
|
||||||
|
Width="12" Height="12"
|
||||||
|
Fill="White"
|
||||||
|
Stroke="#22D3A9"
|
||||||
|
StrokeThickness="1.5"
|
||||||
|
Cursor="SizeNWSE" />
|
||||||
|
</Canvas>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls
|
||||||
|
{
|
||||||
|
public enum ImageResizeCorner
|
||||||
|
{
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageResizeDeltaEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public ImageResizeCorner Corner { get; }
|
||||||
|
public Vector CanvasDelta { get; }
|
||||||
|
public bool LockAspectRatio { get; }
|
||||||
|
|
||||||
|
public ImageResizeDeltaEventArgs(ImageResizeCorner corner, Vector canvasDelta, bool lockAspect)
|
||||||
|
{
|
||||||
|
Corner = corner;
|
||||||
|
CanvasDelta = canvasDelta;
|
||||||
|
LockAspectRatio = lockAspect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageMoveDeltaEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public Vector CanvasDelta { get; }
|
||||||
|
public ImageMoveDeltaEventArgs(Vector delta) { CanvasDelta = delta; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageRotateDeltaEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public double AngleDelta { get; }
|
||||||
|
public ImageRotateDeltaEventArgs(double angleDelta) { AngleDelta = angleDelta; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ImageSelectionOverlay : UserControl
|
||||||
|
{
|
||||||
|
private const double HandleSize = 12;
|
||||||
|
private const double RotationHandleSize = 14;
|
||||||
|
private const double RotationHandleOffset = 28;
|
||||||
|
|
||||||
|
public event EventHandler<ImageResizeDeltaEventArgs> ResizeDelta;
|
||||||
|
public event EventHandler<ImageMoveDeltaEventArgs> MoveDelta;
|
||||||
|
public event EventHandler<ImageRotateDeltaEventArgs> RotateDelta;
|
||||||
|
public event EventHandler InteractionStarted;
|
||||||
|
public event EventHandler InteractionEnded;
|
||||||
|
|
||||||
|
public IInputElement CoordinateSource { get; set; }
|
||||||
|
|
||||||
|
private Point _rotationCenterCanvas;
|
||||||
|
private readonly RotateTransform _overlayRotation = new RotateTransform(0);
|
||||||
|
|
||||||
|
private bool _isResizing;
|
||||||
|
private bool _isRotating;
|
||||||
|
private bool _isMoving;
|
||||||
|
private ImageResizeCorner _activeCorner;
|
||||||
|
private Point _lastPoint;
|
||||||
|
private double _lastRotationAngle;
|
||||||
|
|
||||||
|
public ImageSelectionOverlay()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
RenderTransform = _overlayRotation;
|
||||||
|
|
||||||
|
TopLeftHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.TopLeft, e, TopLeftHandle);
|
||||||
|
TopRightHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.TopRight, e, TopRightHandle);
|
||||||
|
BottomLeftHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.BottomLeft, e, BottomLeftHandle);
|
||||||
|
BottomRightHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.BottomRight, e, BottomRightHandle);
|
||||||
|
|
||||||
|
TopLeftHandle.MouseMove += ResizeMove;
|
||||||
|
TopRightHandle.MouseMove += ResizeMove;
|
||||||
|
BottomLeftHandle.MouseMove += ResizeMove;
|
||||||
|
BottomRightHandle.MouseMove += ResizeMove;
|
||||||
|
|
||||||
|
TopLeftHandle.MouseLeftButtonUp += EndResize;
|
||||||
|
TopRightHandle.MouseLeftButtonUp += EndResize;
|
||||||
|
BottomLeftHandle.MouseLeftButtonUp += EndResize;
|
||||||
|
BottomRightHandle.MouseLeftButtonUp += EndResize;
|
||||||
|
|
||||||
|
RotationHandle.MouseLeftButtonDown += BeginRotate;
|
||||||
|
RotationHandle.MouseMove += RotateMove;
|
||||||
|
RotationHandle.MouseLeftButtonUp += EndRotate;
|
||||||
|
|
||||||
|
MoveSurface.MouseLeftButtonDown += BeginMove;
|
||||||
|
MoveSurface.MouseMove += MoveMove;
|
||||||
|
MoveSurface.MouseLeftButtonUp += EndMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Position overlay so its logical rect (width × height) is centered at centerCanvas,
|
||||||
|
/// then rotated by rotationAngleDegrees around that center to match the target element.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateFrame(Point centerCanvas, double width, double height, double rotationAngleDegrees)
|
||||||
|
{
|
||||||
|
if (width <= 0 || height <= 0) return;
|
||||||
|
|
||||||
|
_rotationCenterCanvas = centerCanvas;
|
||||||
|
|
||||||
|
double left = centerCanvas.X - width / 2;
|
||||||
|
double top = centerCanvas.Y - height / 2;
|
||||||
|
Margin = new Thickness(left, top, 0, 0);
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
RenderTransformOrigin = new Point(0, 0);
|
||||||
|
_overlayRotation.Angle = rotationAngleDegrees;
|
||||||
|
_overlayRotation.CenterX = width / 2;
|
||||||
|
_overlayRotation.CenterY = height / 2;
|
||||||
|
|
||||||
|
FrameBorder.Width = width;
|
||||||
|
FrameBorder.Height = height;
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(FrameBorder, 0);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(FrameBorder, 0);
|
||||||
|
|
||||||
|
MoveSurface.Width = width;
|
||||||
|
MoveSurface.Height = height;
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(MoveSurface, 0);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(MoveSurface, 0);
|
||||||
|
|
||||||
|
double h = HandleSize / 2;
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(TopLeftHandle, -h);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(TopLeftHandle, -h);
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(TopRightHandle, width - h);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(TopRightHandle, -h);
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(BottomLeftHandle, -h);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(BottomLeftHandle, height - h);
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(BottomRightHandle, width - h);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(BottomRightHandle, height - h);
|
||||||
|
|
||||||
|
double rh = RotationHandleSize / 2;
|
||||||
|
double midX = width / 2;
|
||||||
|
System.Windows.Controls.Canvas.SetLeft(RotationHandle, midX - rh);
|
||||||
|
System.Windows.Controls.Canvas.SetTop(RotationHandle, -RotationHandleOffset - rh);
|
||||||
|
|
||||||
|
RotationLine.X1 = midX;
|
||||||
|
RotationLine.Y1 = 0;
|
||||||
|
RotationLine.X2 = midX;
|
||||||
|
RotationLine.Y2 = -RotationHandleOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IInputElement GetSource() => CoordinateSource ?? (IInputElement)Parent;
|
||||||
|
|
||||||
|
private void BeginResize(ImageResizeCorner corner, MouseButtonEventArgs e, Ellipse handle)
|
||||||
|
{
|
||||||
|
var source = GetSource();
|
||||||
|
if (source == null) return;
|
||||||
|
_isResizing = true;
|
||||||
|
_activeCorner = corner;
|
||||||
|
_lastPoint = e.GetPosition(source);
|
||||||
|
handle.CaptureMouse();
|
||||||
|
InteractionStarted?.Invoke(this, EventArgs.Empty);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResizeMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isResizing || !(sender is Ellipse handle) || !handle.IsMouseCaptured) return;
|
||||||
|
var source = GetSource();
|
||||||
|
if (source == null) return;
|
||||||
|
var current = e.GetPosition(source);
|
||||||
|
var delta = current - _lastPoint;
|
||||||
|
bool lockAspect = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
|
||||||
|
ResizeDelta?.Invoke(this, new ImageResizeDeltaEventArgs(_activeCorner, delta, lockAspect));
|
||||||
|
_lastPoint = current;
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndResize(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isResizing) return;
|
||||||
|
if (sender is Ellipse handle) handle.ReleaseMouseCapture();
|
||||||
|
_isResizing = false;
|
||||||
|
InteractionEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginRotate(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var source = GetSource();
|
||||||
|
if (source == null) return;
|
||||||
|
_isRotating = true;
|
||||||
|
var p = e.GetPosition(source);
|
||||||
|
_lastRotationAngle = AngleFromCenter(p);
|
||||||
|
RotationHandle.CaptureMouse();
|
||||||
|
InteractionStarted?.Invoke(this, EventArgs.Empty);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isRotating || !RotationHandle.IsMouseCaptured) return;
|
||||||
|
var source = GetSource();
|
||||||
|
if (source == null) return;
|
||||||
|
var p = e.GetPosition(source);
|
||||||
|
double angle = AngleFromCenter(p);
|
||||||
|
double delta = angle - _lastRotationAngle;
|
||||||
|
if (delta > 180) delta -= 360;
|
||||||
|
else if (delta < -180) delta += 360;
|
||||||
|
_lastRotationAngle = angle;
|
||||||
|
RotateDelta?.Invoke(this, new ImageRotateDeltaEventArgs(delta));
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndRotate(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isRotating) return;
|
||||||
|
RotationHandle.ReleaseMouseCapture();
|
||||||
|
_isRotating = false;
|
||||||
|
InteractionEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginMove(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
var source = GetSource();
|
||||||
|
if (source == null) return;
|
||||||
|
_isMoving = true;
|
||||||
|
_lastPoint = e.GetPosition(source);
|
||||||
|
MoveSurface.CaptureMouse();
|
||||||
|
InteractionStarted?.Invoke(this, EventArgs.Empty);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isMoving || !MoveSurface.IsMouseCaptured) return;
|
||||||
|
var source = GetSource();
|
||||||
|
if (source == null) return;
|
||||||
|
var current = e.GetPosition(source);
|
||||||
|
var delta = current - _lastPoint;
|
||||||
|
_lastPoint = current;
|
||||||
|
MoveDelta?.Invoke(this, new ImageMoveDeltaEventArgs(delta));
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndMove(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isMoving) return;
|
||||||
|
MoveSurface.ReleaseMouseCapture();
|
||||||
|
_isMoving = false;
|
||||||
|
InteractionEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double AngleFromCenter(Point p)
|
||||||
|
{
|
||||||
|
double dx = p.X - _rotationCenterCanvas.X;
|
||||||
|
double dy = p.Y - _rotationCenterCanvas.Y;
|
||||||
|
return Math.Atan2(dy, dx) * 180.0 / Math.PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<UserControl x:Class="Ink_Canvas.Controls.PptNavBar"
|
||||||
|
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:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
xmlns:local="clr-namespace:Ink_Canvas.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="50" d:DesignWidth="200">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<SolidColorBrush x:Key="PptNavBarItemForeground" Color="#71717a"/>
|
||||||
|
<DataTemplate x:Key="PptPreviewItemTemplate">
|
||||||
|
<Grid Margin="2,4">
|
||||||
|
<Border CornerRadius="8" Background="Transparent" Padding="6">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="28"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
|
FontSize="12" FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource PptNavBarItemForeground}"
|
||||||
|
Text="{Binding SlideNumber}"/>
|
||||||
|
<Image Grid.Column="1" Source="{Binding Thumbnail}" Stretch="Uniform"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
<Style x:Key="PptPreviewItemContainerStyle" TargetType="ListBoxItem">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ListBoxItem">
|
||||||
|
<Border x:Name="Bd" CornerRadius="8" Background="Transparent"
|
||||||
|
BorderBrush="Transparent" BorderThickness="2">
|
||||||
|
<ContentPresenter/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="Background" Value="#1affffff"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="BorderBrush" Value="#66CCFF"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Border x:Name="RootBorder" BorderThickness="1" BorderBrush="#a1a1aa"
|
||||||
|
Background="#f4f4f5" Opacity="1" CornerRadius="6">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect Color="#000000" BlurRadius="14" ShadowDepth="2" Opacity="0.16"/>
|
||||||
|
</Border.Effect>
|
||||||
|
<DockPanel x:Name="LayoutRoot" LastChildFill="True">
|
||||||
|
<ListBox x:Name="PreviewList"
|
||||||
|
Visibility="Collapsed"
|
||||||
|
Background="Transparent" BorderThickness="0"
|
||||||
|
Padding="6,8,6,4"
|
||||||
|
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
ItemTemplate="{StaticResource PptPreviewItemTemplate}"
|
||||||
|
ItemContainerStyle="{StaticResource PptPreviewItemContainerStyle}"
|
||||||
|
MouseUp="PreviewList_MouseUp"/>
|
||||||
|
<ikw:SimpleStackPanel x:Name="ButtonRow" Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Border x:Name="PreviousButtonBorder" Width="50" Height="50"
|
||||||
|
MouseDown="PreviousButton_MouseDown"
|
||||||
|
MouseUp="PreviousButton_MouseUp"
|
||||||
|
MouseLeave="PreviousButton_MouseLeave"
|
||||||
|
CornerRadius="5 5 0 0" Background="Transparent">
|
||||||
|
<Grid>
|
||||||
|
<Border x:Name="PreviousButtonFeedbackBorder" Margin="3" CornerRadius="5"
|
||||||
|
Background="#18181b" Opacity="0"/>
|
||||||
|
<Image Height="28" Width="28">
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||||
|
<GeometryDrawing x:Name="PreviousButtonGeometry" Brush="#27272a"/>
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<Border x:Name="PageButtonBorder" Visibility="Visible" TextBlock.Foreground="#171717"
|
||||||
|
MouseDown="PageButton_MouseDown"
|
||||||
|
MouseUp="PageButton_MouseUp"
|
||||||
|
MouseLeave="PageButton_MouseLeave"
|
||||||
|
MinWidth="50" MinHeight="50" Background="Transparent">
|
||||||
|
<Grid>
|
||||||
|
<Border x:Name="PageButtonFeedbackBorder" Margin="0,3" CornerRadius="5"
|
||||||
|
Background="#18181b" Opacity="0"/>
|
||||||
|
<ikw:SimpleStackPanel VerticalAlignment="Center" Orientation="Vertical"
|
||||||
|
Spacing="0.5" Margin="12,0">
|
||||||
|
<TextBlock x:Name="PageNowText" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
FontSize="17" FontWeight="Bold" Text="?"/>
|
||||||
|
<TextBlock x:Name="PageTotalText" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
FontSize="10" Text="/ ?"/>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<Border x:Name="NextButtonBorder" Width="50" Height="50"
|
||||||
|
MouseDown="NextButton_MouseDown"
|
||||||
|
MouseUp="NextButton_MouseUp"
|
||||||
|
MouseLeave="NextButton_MouseLeave"
|
||||||
|
CornerRadius="0 0 5 5" Background="Transparent">
|
||||||
|
<Grid>
|
||||||
|
<Border x:Name="NextButtonFeedbackBorder" Margin="3" CornerRadius="5"
|
||||||
|
Background="#18181b" Opacity="0"/>
|
||||||
|
<Image VerticalAlignment="Center" HorizontalAlignment="Center" Height="28" Width="28">
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||||
|
<GeometryDrawing x:Name="NextButtonGeometry" Brush="#27272a"/>
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,392 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PPT 翻页 + 增强预览一体化控件。
|
||||||
|
/// 通过 <see cref="Direction"/> 切换底部条 (LB/RB) 与侧边条 (LS/RS) 布局,
|
||||||
|
/// 预览列表内嵌于同一个 Border,展开时占据按钮组之外的剩余空间。
|
||||||
|
/// </summary>
|
||||||
|
public partial class PptNavBar : UserControl
|
||||||
|
{
|
||||||
|
public sealed class PreviewItem
|
||||||
|
{
|
||||||
|
public int SlideNumber { get; set; }
|
||||||
|
public BitmapImage Thumbnail { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NavDirection
|
||||||
|
{
|
||||||
|
LeftBottom,
|
||||||
|
RightBottom,
|
||||||
|
LeftSide,
|
||||||
|
RightSide
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register(
|
||||||
|
nameof(Direction), typeof(NavDirection), typeof(PptNavBar),
|
||||||
|
new PropertyMetadata(NavDirection.LeftBottom, OnDirectionChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty CurrentSlideProperty = DependencyProperty.Register(
|
||||||
|
nameof(CurrentSlide), typeof(int), typeof(PptNavBar),
|
||||||
|
new PropertyMetadata(0, OnPageChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty TotalSlidesProperty = DependencyProperty.Register(
|
||||||
|
nameof(TotalSlides), typeof(int), typeof(PptNavBar),
|
||||||
|
new PropertyMetadata(0, OnPageChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty PreviewItemsProperty = DependencyProperty.Register(
|
||||||
|
nameof(PreviewItems), typeof(IList<PreviewItem>), typeof(PptNavBar),
|
||||||
|
new PropertyMetadata(null, OnPreviewItemsChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty IsPreviewExpandedProperty = DependencyProperty.Register(
|
||||||
|
nameof(IsPreviewExpanded), typeof(bool), typeof(PptNavBar),
|
||||||
|
new PropertyMetadata(false, OnIsPreviewExpandedChanged));
|
||||||
|
|
||||||
|
public NavDirection Direction
|
||||||
|
{
|
||||||
|
get => (NavDirection)GetValue(DirectionProperty);
|
||||||
|
set => SetValue(DirectionProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CurrentSlide
|
||||||
|
{
|
||||||
|
get => (int)GetValue(CurrentSlideProperty);
|
||||||
|
set => SetValue(CurrentSlideProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TotalSlides
|
||||||
|
{
|
||||||
|
get => (int)GetValue(TotalSlidesProperty);
|
||||||
|
set => SetValue(TotalSlidesProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<PreviewItem> PreviewItems
|
||||||
|
{
|
||||||
|
get => (IList<PreviewItem>)GetValue(PreviewItemsProperty);
|
||||||
|
set => SetValue(PreviewItemsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPreviewExpanded
|
||||||
|
{
|
||||||
|
get => (bool)GetValue(IsPreviewExpandedProperty);
|
||||||
|
set => SetValue(IsPreviewExpandedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler PreviousClick;
|
||||||
|
public event EventHandler NextClick;
|
||||||
|
public event EventHandler PageClick;
|
||||||
|
public event EventHandler<int> SlideSelected;
|
||||||
|
public event EventHandler PreviousPressedDown;
|
||||||
|
public event EventHandler NextPressedDown;
|
||||||
|
public event EventHandler PressEnded;
|
||||||
|
public event EventHandler<bool> PreviewExpandedChanged;
|
||||||
|
|
||||||
|
// 静态几何(左下/右下:水平箭头;左侧/右侧:垂直箭头)
|
||||||
|
private static readonly Geometry HArrowLeft = Geometry.Parse("F0 M24,24z M0,0z M3.3994,12.9642C2.86687,12.4317,2.86687,11.5683,3.3994,11.0358L9.94485,4.49031C10.4774,3.95777 11.3408,3.95777 11.8733,4.49031 12.4059,5.02284 12.4059,5.88625 11.8733,6.41878L7.65575,10.6364 19.6364,10.6364C20.3895,10.6364 21,11.2469 21,12 21,12.7531 20.3895,13.3636 19.6364,13.3636L7.65575,13.3636 11.8733,17.5812C12.4059,18.1137 12.4059,18.9772 11.8733,19.5097 11.3408,20.0422 10.4774,20.0422 9.94485,19.5097L3.3994,12.9642z");
|
||||||
|
private static readonly Geometry HArrowRight = Geometry.Parse("F0 M24,24z M0,0z M20.6006,12.9642C21.1331,12.4317,21.1331,11.5683,20.6006,11.0358L14.0551,4.49031C13.5226,3.95777 12.6592,3.95777 12.1267,4.49031 11.5941,5.02284 11.5941,5.88625 12.1267,6.41878L16.3443,10.6364 4.36364,10.6364C3.61052,10.6364 3,11.2469 3,12 3,12.7531 3.61052,13.3636 4.36364,13.3636L16.3443,13.3636 12.1267,17.5812C11.5941,18.1137 11.5941,18.9772 12.1267,19.5097 12.6592,20.0422 13.5226,20.0422 14.0551,19.5097L20.6006,12.9642z");
|
||||||
|
private static readonly Geometry VArrowUp = Geometry.Parse("F0 M24,24z M0,0z M11.0357,3.3994C11.5682,2.86687,12.4316,2.86687,12.9641,3.3994L19.5096,9.94485C20.0421,10.4774 20.0421,11.3408 19.5096,11.8733 18.9771,12.4059 18.1137,12.4059 17.5811,11.8733L13.3635,7.65575 13.3635,19.6364C13.3635,20.3895 12.753,21 11.9999,21 11.2468,21 10.6363,20.3895 10.6363,19.6364L10.6363,7.65575 6.41869,11.8733C5.88616,12.4059 5.02275,12.4059 4.49022,11.8733 3.95769,11.3408 3.95769,10.4774 4.49022,9.94485L11.0357,3.3994z");
|
||||||
|
private static readonly Geometry VArrowDown = Geometry.Parse("F0 M24,24z M0,0z M11.0357,20.6006C11.5682,21.1331,12.4316,21.1331,12.9641,20.6006L19.5096,14.0551C20.0421,13.5226 20.0421,12.6592 19.5096,12.1267 18.9771,11.5941 18.1137,11.5941 17.5811,12.1267L13.3635,16.3443 13.3635,4.36364C13.3635,3.61052 12.753,3 11.9999,3 11.2468,3 10.6363,3.61052 10.6363,4.36364L10.6363,16.3443 6.41869,12.1267C5.88616,11.5941 5.02275,11.5941 4.49022,12.1267 3.95769,12.6592 3.95769,13.5226 4.49022,14.0551L11.0357,20.6006z");
|
||||||
|
|
||||||
|
public PptNavBar()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
ApplyDirection(Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is PptNavBar bar) bar.ApplyDirection((NavDirection)e.NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is PptNavBar bar) bar.RefreshPageText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnPreviewItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is PptNavBar bar)
|
||||||
|
{
|
||||||
|
bar.PreviewList.ItemsSource = e.NewValue as IList<PreviewItem>;
|
||||||
|
bar.SyncPreviewSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnIsPreviewExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is PptNavBar bar)
|
||||||
|
{
|
||||||
|
bool expanded = (bool)e.NewValue;
|
||||||
|
bar.PreviewList.Visibility = expanded ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
bar.ApplyLayout();
|
||||||
|
if (expanded)
|
||||||
|
{
|
||||||
|
bar.SyncPreviewSelection();
|
||||||
|
bar.HookOutsideClick();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bar.UnhookOutsideClick();
|
||||||
|
}
|
||||||
|
bar.PreviewExpandedChanged?.Invoke(bar, expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Window _hookedWindow;
|
||||||
|
private void HookOutsideClick()
|
||||||
|
{
|
||||||
|
if (_hookedWindow != null) return;
|
||||||
|
_hookedWindow = Window.GetWindow(this);
|
||||||
|
if (_hookedWindow != null)
|
||||||
|
{
|
||||||
|
_hookedWindow.PreviewMouseDown += OnWindowPreviewMouseDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void UnhookOutsideClick()
|
||||||
|
{
|
||||||
|
if (_hookedWindow != null)
|
||||||
|
{
|
||||||
|
_hookedWindow.PreviewMouseDown -= OnWindowPreviewMouseDown;
|
||||||
|
_hookedWindow = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void OnWindowPreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.OriginalSource is DependencyObject d && !IsDescendantOf(d, this))
|
||||||
|
{
|
||||||
|
IsPreviewExpanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static bool IsDescendantOf(DependencyObject child, DependencyObject ancestor)
|
||||||
|
{
|
||||||
|
while (child != null)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(child, ancestor)) return true;
|
||||||
|
child = System.Windows.Media.VisualTreeHelper.GetParent(child)
|
||||||
|
?? System.Windows.LogicalTreeHelper.GetParent(child);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyDirection(NavDirection dir) => ApplyLayout();
|
||||||
|
|
||||||
|
private void ApplyLayout()
|
||||||
|
{
|
||||||
|
var dir = Direction;
|
||||||
|
bool expanded = IsPreviewExpanded;
|
||||||
|
|
||||||
|
// 重置可能在不同状态下被设置的属性
|
||||||
|
ButtonRow.ClearValue(WidthProperty);
|
||||||
|
ButtonRow.ClearValue(HeightProperty);
|
||||||
|
ButtonRow.ClearValue(HorizontalAlignmentProperty);
|
||||||
|
PreviewList.ClearValue(WidthProperty);
|
||||||
|
PreviewList.ClearValue(HeightProperty);
|
||||||
|
PreviewList.ClearValue(MaxHeightProperty);
|
||||||
|
PreviewList.ClearValue(MaxWidthProperty);
|
||||||
|
PreviewList.ClearValue(HorizontalAlignmentProperty);
|
||||||
|
ClearValue(HeightProperty);
|
||||||
|
ClearValue(MaxHeightProperty);
|
||||||
|
|
||||||
|
double availableHeight = ComputeAvailableHeight();
|
||||||
|
|
||||||
|
switch (dir)
|
||||||
|
{
|
||||||
|
case NavDirection.LeftBottom:
|
||||||
|
case NavDirection.RightBottom:
|
||||||
|
DockPanel.SetDock(PreviewList, Dock.Top);
|
||||||
|
DockPanel.SetDock(ButtonRow, Dock.Bottom);
|
||||||
|
ButtonRow.Orientation = Orientation.Horizontal;
|
||||||
|
ButtonRow.Height = 50;
|
||||||
|
if (expanded)
|
||||||
|
{
|
||||||
|
// 预览面板拉宽到 280,贴向同侧角落
|
||||||
|
PreviewList.Width = 280;
|
||||||
|
PreviewList.MaxHeight = Math.Max(200, availableHeight - 50);
|
||||||
|
PreviewList.HorizontalAlignment = dir == NavDirection.LeftBottom
|
||||||
|
? HorizontalAlignment.Left
|
||||||
|
: HorizontalAlignment.Right;
|
||||||
|
// 按钮组宽度限制为原始内容宽度,并贴向同侧,保持按钮位置不变
|
||||||
|
ButtonRow.HorizontalAlignment = dir == NavDirection.LeftBottom
|
||||||
|
? HorizontalAlignment.Left
|
||||||
|
: HorizontalAlignment.Right;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PreviewList.SetBinding(WidthProperty, new System.Windows.Data.Binding(nameof(ButtonRow.ActualWidth)) { Source = ButtonRow });
|
||||||
|
PreviewList.MaxHeight = 380;
|
||||||
|
}
|
||||||
|
PreviousButtonGeometry.Geometry = HArrowLeft;
|
||||||
|
NextButtonGeometry.Geometry = HArrowRight;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NavDirection.LeftSide:
|
||||||
|
DockPanel.SetDock(PreviewList, Dock.Right);
|
||||||
|
DockPanel.SetDock(ButtonRow, Dock.Left);
|
||||||
|
ButtonRow.Orientation = Orientation.Vertical;
|
||||||
|
ButtonRow.Width = 50;
|
||||||
|
PreviewList.Width = 240;
|
||||||
|
PreviewList.MaxHeight = 480;
|
||||||
|
PreviousButtonGeometry.Geometry = VArrowUp;
|
||||||
|
NextButtonGeometry.Geometry = VArrowDown;
|
||||||
|
break;
|
||||||
|
case NavDirection.RightSide:
|
||||||
|
DockPanel.SetDock(PreviewList, Dock.Left);
|
||||||
|
DockPanel.SetDock(ButtonRow, Dock.Right);
|
||||||
|
ButtonRow.Orientation = Orientation.Vertical;
|
||||||
|
ButtonRow.Width = 50;
|
||||||
|
PreviewList.Width = 240;
|
||||||
|
PreviewList.MaxHeight = 480;
|
||||||
|
PreviousButtonGeometry.Geometry = VArrowUp;
|
||||||
|
NextButtonGeometry.Geometry = VArrowDown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double ComputeAvailableHeight()
|
||||||
|
{
|
||||||
|
var window = Window.GetWindow(this);
|
||||||
|
double h = window != null ? window.ActualHeight : SystemParameters.PrimaryScreenHeight;
|
||||||
|
return Math.Max(240, h - 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshPageText()
|
||||||
|
{
|
||||||
|
if (CurrentSlide > 0 && TotalSlides > 0)
|
||||||
|
{
|
||||||
|
PageNowText.Text = CurrentSlide.ToString();
|
||||||
|
PageTotalText.Text = $"/ {TotalSlides}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PageNowText.Text = "?";
|
||||||
|
PageTotalText.Text = "/ ?";
|
||||||
|
}
|
||||||
|
SyncPreviewSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncPreviewSelection()
|
||||||
|
{
|
||||||
|
if (PreviewItems == null || CurrentSlide <= 0) return;
|
||||||
|
foreach (var item in PreviewItems)
|
||||||
|
{
|
||||||
|
if (item.SlideNumber == CurrentSlide)
|
||||||
|
{
|
||||||
|
PreviewList.SelectedItem = item;
|
||||||
|
PreviewList.ScrollIntoView(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetFeedback(Border feedback, double opacity) => feedback.Opacity = opacity;
|
||||||
|
|
||||||
|
private object _lastDown;
|
||||||
|
|
||||||
|
private void PreviousButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_lastDown = sender;
|
||||||
|
SetFeedback(PreviousButtonFeedbackBorder, 0.15);
|
||||||
|
PreviousPressedDown?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviousButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
SetFeedback(PreviousButtonFeedbackBorder, 0);
|
||||||
|
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
if (_lastDown != sender) return;
|
||||||
|
_lastDown = null;
|
||||||
|
PreviousClick?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviousButton_MouseLeave(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
SetFeedback(PreviousButtonFeedbackBorder, 0);
|
||||||
|
_lastDown = null;
|
||||||
|
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_lastDown = sender;
|
||||||
|
SetFeedback(NextButtonFeedbackBorder, 0.15);
|
||||||
|
NextPressedDown?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
SetFeedback(NextButtonFeedbackBorder, 0);
|
||||||
|
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
if (_lastDown != sender) return;
|
||||||
|
_lastDown = null;
|
||||||
|
NextClick?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NextButton_MouseLeave(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
SetFeedback(NextButtonFeedbackBorder, 0);
|
||||||
|
_lastDown = null;
|
||||||
|
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PageButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_lastDown = sender;
|
||||||
|
SetFeedback(PageButtonFeedbackBorder, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PageButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
SetFeedback(PageButtonFeedbackBorder, 0);
|
||||||
|
if (_lastDown != sender) return;
|
||||||
|
_lastDown = null;
|
||||||
|
PageClick?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PageButton_MouseLeave(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
SetFeedback(PageButtonFeedbackBorder, 0);
|
||||||
|
_lastDown = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PreviewList_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (PreviewList.SelectedItem is PreviewItem item)
|
||||||
|
{
|
||||||
|
SlideSelected?.Invoke(this, item.SlideNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyTheme(bool isDark)
|
||||||
|
{
|
||||||
|
var fgBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(39, 39, 42));
|
||||||
|
var feedbackBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||||
|
var bgBrush = isDark
|
||||||
|
? new SolidColorBrush(Color.FromRgb(39, 39, 42))
|
||||||
|
: new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||||
|
var borderBrush = isDark
|
||||||
|
? new SolidColorBrush(Color.FromRgb(82, 82, 91))
|
||||||
|
: new SolidColorBrush(Color.FromRgb(161, 161, 170));
|
||||||
|
|
||||||
|
PreviousButtonGeometry.Brush = fgBrush;
|
||||||
|
NextButtonGeometry.Brush = fgBrush;
|
||||||
|
PreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||||
|
NextButtonFeedbackBorder.Background = feedbackBrush;
|
||||||
|
PageButtonFeedbackBorder.Background = feedbackBrush;
|
||||||
|
PageNowText.Foreground = fgBrush;
|
||||||
|
PageTotalText.Foreground = fgBrush;
|
||||||
|
RootBorder.Background = bgBrush;
|
||||||
|
RootBorder.BorderBrush = borderBrush;
|
||||||
|
Resources["PptNavBarItemForeground"] = fgBrush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPageButtonVisibility(Visibility v) => PageButtonBorder.Visibility = v;
|
||||||
|
public void SetBarOpacity(double opacity) => RootBorder.Opacity = opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 工具栏按钮插件与宿主之间的桥梁。Phase 1 粗粒度暴露 MainWindow,后续收窄。
|
||||||
|
/// </summary>
|
||||||
|
public interface IToolbarHost
|
||||||
|
{
|
||||||
|
MainWindow Window { get; }
|
||||||
|
|
||||||
|
/// <summary>按 id 登记按钮的 view 实例(供 MainWindow 字段回填和互相查找)。</summary>
|
||||||
|
void RegisterView(string id, FrameworkElement view);
|
||||||
|
|
||||||
|
/// <summary>按 id 获取之前注册的 view。不存在返回 null。</summary>
|
||||||
|
FrameworkElement FindView(string id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
public interface IToolbarItem
|
||||||
|
{
|
||||||
|
string Id { get; }
|
||||||
|
|
||||||
|
ToolbarSlot DefaultSlot { get; }
|
||||||
|
|
||||||
|
int DefaultOrder { get; }
|
||||||
|
|
||||||
|
bool DefaultVisible { get; }
|
||||||
|
|
||||||
|
ToolbarInsertPosition DefaultPosition { get; }
|
||||||
|
|
||||||
|
string DefaultAnchorName { get; }
|
||||||
|
|
||||||
|
string DisplayName { get; }
|
||||||
|
|
||||||
|
string MenuPanelName { get; }
|
||||||
|
|
||||||
|
FrameworkElement BuildView(IToolbarHost host);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 清空按钮。位置:夹在颜色面板与 StackPanelCanvasControls 之间,
|
||||||
|
/// 所以用 BeforeAnchor 锚到 StackPanelCanvasControls。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ClearToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.clear";
|
||||||
|
public override string LocalizationKey => "FloatingBar_Clear";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||||
|
public override int DefaultOrder => 0;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.BeforeAnchor;
|
||||||
|
public override string DefaultAnchorName => "StackPanelCanvasControls";
|
||||||
|
|
||||||
|
protected override string IconBrushResourceKey => "RedBrush";
|
||||||
|
protected override string LabelBrushResourceKey => "RedBrush";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.SymbolIconDelete_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachSymbolIconDelete(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class CursorToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.cursor";
|
||||||
|
public override string LocalizationKey => "FloatingBar_Mouse";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||||
|
public override int DefaultOrder => 100;
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.CursorIcon_Click(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachCursorIconView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class CursorWithDelToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.cursorWithDel";
|
||||||
|
public override string LocalizationKey => "FloatingBar_ClearAndMouse";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 320;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.CursorWithDelIcon_Click(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachCursorWithDelBtn(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class EraserByStrokesToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.eraserByStrokes";
|
||||||
|
public override string LocalizationKey => "FloatingBar_StrokeEraser";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 110;
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.EraserIconByStrokes_Click(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachEraserByStrokesIcon(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class EraserToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.eraser";
|
||||||
|
public override string LocalizationKey => "FloatingBar_AreaEraser";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 100;
|
||||||
|
public override string MenuPanelName => "EraserSizePanel";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.EraserIcon_Click(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachEraserIcon(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class FoldToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.fold";
|
||||||
|
public override string LocalizationKey => "FloatingBar_Hide";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||||
|
public override int DefaultOrder => 120;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||||
|
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.FoldFloatingBar_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachFoldIcon(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class PenToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.pen";
|
||||||
|
public override string LocalizationKey => "FloatingBar_Annotate";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||||
|
public override int DefaultOrder => 110;
|
||||||
|
public override string MenuPanelName => "PenPalette";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.PenIcon_Click(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachPenIconView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class RedoToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.redo";
|
||||||
|
public override string LocalizationKey => "Board_Redo";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 310;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.SymbolIconRedo_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
{
|
||||||
|
host.Window.AttachSymbolIconRedo(view);
|
||||||
|
view.SetBinding(System.Windows.UIElement.IsEnabledProperty,
|
||||||
|
new System.Windows.Data.Binding("IsEnabled") { ElementName = "BtnRedo" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class SelectToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.select";
|
||||||
|
public override string LocalizationKey => "FloatingBar_LassoSelect";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 120;
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.SymbolIconSelect_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachSymbolIconSelect(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class ShapeDrawToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.shapeDraw";
|
||||||
|
public override string LocalizationKey => "FloatingBar_Geometry";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 130;
|
||||||
|
public override string MenuPanelName => "BorderDrawShape";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.ImageDrawShape_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachShapeDrawBtn(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Ink_Canvas.Properties;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal abstract class ToolbarImageButtonItemBase : IToolbarItem
|
||||||
|
{
|
||||||
|
public abstract string Id { get; }
|
||||||
|
public abstract string LocalizationKey { get; }
|
||||||
|
public abstract ToolbarSlot DefaultSlot { get; }
|
||||||
|
public abstract int DefaultOrder { get; }
|
||||||
|
public virtual bool DefaultVisible => true;
|
||||||
|
public virtual ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Prepend;
|
||||||
|
public virtual string DefaultAnchorName => null;
|
||||||
|
|
||||||
|
public string DisplayName => Strings.GetString(LocalizationKey) ?? LocalizationKey;
|
||||||
|
public virtual string MenuPanelName => null;
|
||||||
|
|
||||||
|
protected virtual string IconBrushResourceKey => null;
|
||||||
|
protected virtual string LabelBrushResourceKey => null;
|
||||||
|
|
||||||
|
protected abstract void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e);
|
||||||
|
|
||||||
|
protected virtual void AfterBuild(IToolbarHost host, ToolbarImageButton view) { }
|
||||||
|
|
||||||
|
public FrameworkElement BuildView(IToolbarHost host)
|
||||||
|
{
|
||||||
|
var btn = new ToolbarImageButton
|
||||||
|
{
|
||||||
|
Label = Strings.GetString(LocalizationKey) ?? LocalizationKey,
|
||||||
|
Tag = "ToolbarRegistryInjected"
|
||||||
|
};
|
||||||
|
if (!string.IsNullOrEmpty(IconBrushResourceKey))
|
||||||
|
{
|
||||||
|
if (btn.TryFindResource(IconBrushResourceKey) is Brush brush) btn.IconBrush = brush;
|
||||||
|
else btn.SetResourceReference(ToolbarImageButton.IconBrushProperty, IconBrushResourceKey);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(LabelBrushResourceKey))
|
||||||
|
{
|
||||||
|
if (btn.TryFindResource(LabelBrushResourceKey) is Brush brush) btn.LabelBrush = brush;
|
||||||
|
else btn.SetResourceReference(ToolbarImageButton.LabelBrushProperty, LabelBrushResourceKey);
|
||||||
|
}
|
||||||
|
btn.ButtonMouseUp += (s, e) => OnClick(host, s, e);
|
||||||
|
AfterBuild(host, btn);
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class ToolsToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.tools";
|
||||||
|
public override string LocalizationKey => "Board_Tools";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||||
|
public override int DefaultOrder => 110;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||||
|
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||||
|
public override string MenuPanelName => "BorderTools";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.SymbolIconTools_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachToolsBtn(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class UndoToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.undo";
|
||||||
|
public override string LocalizationKey => "Board_Undo";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||||
|
public override int DefaultOrder => 300;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.SymbolIconUndo_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
{
|
||||||
|
host.Window.AttachSymbolIconUndo(view);
|
||||||
|
view.SetBinding(System.Windows.UIElement.IsEnabledProperty,
|
||||||
|
new System.Windows.Data.Binding("IsEnabled") { ElementName = "BtnUndo" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||||
|
{
|
||||||
|
internal sealed class WhiteboardToolItem : ToolbarImageButtonItemBase
|
||||||
|
{
|
||||||
|
public override string Id => "builtin.whiteboard";
|
||||||
|
public override string LocalizationKey => "FloatingBar_Whiteboard";
|
||||||
|
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||||
|
public override int DefaultOrder => 100;
|
||||||
|
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||||
|
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||||
|
|
||||||
|
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||||
|
=> host.Window.ImageBlackboard_MouseUp(sender, e);
|
||||||
|
|
||||||
|
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||||
|
=> host.Window.AttachWhiteboardBtn(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// MainWindow 版的 IToolbarHost 实现。Phase 1 直接把 MainWindow 引用暴露给插件,
|
||||||
|
/// 插件可通过 host.Window 访问私有/内部成员(partial class 扩展或 internal 字段)。
|
||||||
|
/// 后续阶段逐步把具体行为抽成 Host 上的方法/事件,收窄这个接口。
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ToolbarHost : IToolbarHost
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, FrameworkElement> _views = new Dictionary<string, FrameworkElement>();
|
||||||
|
|
||||||
|
public ToolbarHost(MainWindow window)
|
||||||
|
{
|
||||||
|
Window = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainWindow Window { get; }
|
||||||
|
|
||||||
|
public void RegisterView(string id, FrameworkElement view)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(id) || view == null) return;
|
||||||
|
_views[id] = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameworkElement FindView(string id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(id)) return null;
|
||||||
|
return _views.TryGetValue(id, out var v) ? v : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
public enum ToolbarInsertPosition
|
||||||
|
{
|
||||||
|
/// <summary>从容器头部依次插入;Order 小的在前。</summary>
|
||||||
|
Prepend,
|
||||||
|
/// <summary>追加到容器末尾。</summary>
|
||||||
|
Append,
|
||||||
|
/// <summary>插入到由 AnchorName 指定的已有元素之前。</summary>
|
||||||
|
BeforeAnchor,
|
||||||
|
/// <summary>插入到由 AnchorName 指定的已有元素之后(同一锚点多项按 Order 依次排列)。</summary>
|
||||||
|
AfterAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 单个工具栏按钮的用户配置(可见性、顺序、所属 slot、插入位置)。
|
||||||
|
/// 由 Settings.Toolbar 持久化。
|
||||||
|
/// </summary>
|
||||||
|
public class ToolbarItemConfig
|
||||||
|
{
|
||||||
|
[JsonProperty("visible")]
|
||||||
|
public bool Visible { get; set; } = true;
|
||||||
|
|
||||||
|
[JsonProperty("order")]
|
||||||
|
public int Order { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("slot")]
|
||||||
|
public ToolbarSlot Slot { get; set; } = ToolbarSlot.FloatingBarMain;
|
||||||
|
|
||||||
|
[JsonProperty("position")]
|
||||||
|
public ToolbarInsertPosition Position { get; set; } = ToolbarInsertPosition.Prepend;
|
||||||
|
|
||||||
|
[JsonProperty("anchorName")]
|
||||||
|
public string AnchorName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ToolbarLayoutSettings
|
||||||
|
{
|
||||||
|
[JsonProperty("items")]
|
||||||
|
public Dictionary<string, ToolbarItemConfig> Items { get; set; } = new Dictionary<string, ToolbarItemConfig>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
using Ink_Canvas.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
public static class ToolbarRegistry
|
||||||
|
{
|
||||||
|
private static List<IToolbarItem> _items;
|
||||||
|
internal const string InjectedTag = "ToolbarRegistryInjected";
|
||||||
|
|
||||||
|
public static IReadOnlyList<IToolbarItem> Discover()
|
||||||
|
{
|
||||||
|
if (_items != null) return _items;
|
||||||
|
|
||||||
|
var itemType = typeof(IToolbarItem);
|
||||||
|
_items = Assembly.GetExecutingAssembly()
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => !t.IsAbstract && !t.IsInterface && itemType.IsAssignableFrom(t))
|
||||||
|
.Select(t =>
|
||||||
|
{
|
||||||
|
try { return (IToolbarItem)Activator.CreateInstance(t); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 实例化 {t.FullName} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Where(i => i != null)
|
||||||
|
.ToList();
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: Discover 完成, 发现 {_items.Count} 个条目", LogHelper.LogType.Info);
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearInjected(Panel container)
|
||||||
|
{
|
||||||
|
if (container == null) return;
|
||||||
|
var toRemove = container.Children.OfType<FrameworkElement>()
|
||||||
|
.Where(e => e.Tag as string == InjectedTag)
|
||||||
|
.ToList();
|
||||||
|
foreach (var element in toRemove)
|
||||||
|
container.Children.Remove(element);
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: ClearInjected 清除 {toRemove.Count} 个元素 [{container.Name}]", LogHelper.LogType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Populate(IToolbarHost host, IDictionary<ToolbarSlot, Panel> slots, ToolbarLayoutSettings layout)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: Populate 开始", LogHelper.LogType.Info);
|
||||||
|
if (host == null || slots == null) { LogHelper.WriteLogToFile("ToolbarRegistry: Populate host 或 slots 为空", LogHelper.LogType.Warning); return; }
|
||||||
|
layout = layout ?? new ToolbarLayoutSettings();
|
||||||
|
|
||||||
|
var grouped = new Dictionary<ToolbarSlot, List<(IToolbarItem item, ToolbarItemConfig cfg)>>();
|
||||||
|
foreach (var item in Discover())
|
||||||
|
{
|
||||||
|
if (!layout.Items.TryGetValue(item.Id, out var cfg))
|
||||||
|
{
|
||||||
|
cfg = new ToolbarItemConfig
|
||||||
|
{
|
||||||
|
Visible = item.DefaultVisible,
|
||||||
|
Order = item.DefaultOrder,
|
||||||
|
Slot = item.DefaultSlot,
|
||||||
|
Position = item.DefaultPosition,
|
||||||
|
AnchorName = item.DefaultAnchorName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!cfg.Visible) continue;
|
||||||
|
if (!grouped.TryGetValue(cfg.Slot, out var list))
|
||||||
|
{
|
||||||
|
list = new List<(IToolbarItem, ToolbarItemConfig)>();
|
||||||
|
grouped[cfg.Slot] = list;
|
||||||
|
}
|
||||||
|
list.Add((item, cfg));
|
||||||
|
}
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 分组完成, {grouped.Count} 个 slot 有可见条目", LogHelper.LogType.Info);
|
||||||
|
|
||||||
|
foreach (var kv in grouped)
|
||||||
|
{
|
||||||
|
if (!slots.TryGetValue(kv.Key, out var container) || container == null) continue;
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 注入到 {kv.Key}, 条目数={kv.Value.Count}", LogHelper.LogType.Info);
|
||||||
|
InjectIntoContainer(host, container, kv.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyMenuVisibility(host, layout);
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: Populate 完成", LogHelper.LogType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplyMenuVisibility(IToolbarHost host, ToolbarLayoutSettings layout)
|
||||||
|
{
|
||||||
|
if (host == null || layout == null) return;
|
||||||
|
foreach (var item in Discover())
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(item.MenuPanelName)) continue;
|
||||||
|
bool visible = true;
|
||||||
|
if (layout.Items.TryGetValue(item.Id, out var cfg))
|
||||||
|
visible = cfg.Visible;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var menuElement = host.Window.FindName(item.MenuPanelName);
|
||||||
|
if (menuElement is System.Windows.Controls.Primitives.Popup popup)
|
||||||
|
{
|
||||||
|
popup.IsOpen = visible;
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 菜单 Popup [{item.MenuPanelName}] -> {(visible ? "Open" : "Closed")}", LogHelper.LogType.Info);
|
||||||
|
}
|
||||||
|
else if (menuElement is FrameworkElement fe)
|
||||||
|
{
|
||||||
|
fe.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 菜单 [{item.MenuPanelName}] -> {(visible ? "Visible" : "Collapsed")}", LogHelper.LogType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 找不到菜单面板 [{item.MenuPanelName}]", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 设置菜单可见性异常 [{item.MenuPanelName}]: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InjectIntoContainer(IToolbarHost host, Panel container,
|
||||||
|
List<(IToolbarItem item, ToolbarItemConfig cfg)> entries)
|
||||||
|
{
|
||||||
|
var prepend = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.Prepend).OrderBy(e => e.cfg.Order).ToList();
|
||||||
|
var append = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.Append).OrderBy(e => e.cfg.Order).ToList();
|
||||||
|
var before = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.BeforeAnchor).ToList();
|
||||||
|
var after = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.AfterAnchor).ToList();
|
||||||
|
|
||||||
|
var prependIndex = 0;
|
||||||
|
foreach (var entry in prepend)
|
||||||
|
{
|
||||||
|
var view = BuildAndRegister(host, entry.item);
|
||||||
|
if (view == null) continue;
|
||||||
|
container.Children.Insert(prependIndex++, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in append)
|
||||||
|
{
|
||||||
|
var view = BuildAndRegister(host, entry.item);
|
||||||
|
if (view == null) continue;
|
||||||
|
container.Children.Add(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in before.GroupBy(e => e.cfg.AnchorName))
|
||||||
|
{
|
||||||
|
var anchor = FindNamedChild(container, group.Key);
|
||||||
|
if (anchor == null)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 未找到锚点 '{group.Key}' (BeforeAnchor)", LogHelper.LogType.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var idx = container.Children.IndexOf(anchor);
|
||||||
|
foreach (var entry in group.OrderBy(e => e.cfg.Order))
|
||||||
|
{
|
||||||
|
var view = BuildAndRegister(host, entry.item);
|
||||||
|
if (view == null) continue;
|
||||||
|
container.Children.Insert(idx++, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var group in after.GroupBy(e => e.cfg.AnchorName))
|
||||||
|
{
|
||||||
|
var anchor = FindNamedChild(container, group.Key);
|
||||||
|
if (anchor == null)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 未找到锚点 '{group.Key}' (AfterAnchor)", LogHelper.LogType.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var idx = container.Children.IndexOf(anchor) + 1;
|
||||||
|
foreach (var entry in group.OrderBy(e => e.cfg.Order))
|
||||||
|
{
|
||||||
|
var view = BuildAndRegister(host, entry.item);
|
||||||
|
if (view == null) continue;
|
||||||
|
container.Children.Insert(idx++, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UIElement FindNamedChild(Panel container, string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name)) return null;
|
||||||
|
foreach (UIElement child in container.Children)
|
||||||
|
{
|
||||||
|
if (child is FrameworkElement fe && fe.Name == name) return child;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FrameworkElement BuildAndRegister(IToolbarHost host, IToolbarItem item)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var view = item.BuildView(host);
|
||||||
|
if (view == null) return null;
|
||||||
|
host.RegisterView(item.Id, view);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"ToolbarRegistry: 构建 {item.Id} 失败: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Ink_Canvas.Controls.Toolbar
|
||||||
|
{
|
||||||
|
public enum ToolbarSlot
|
||||||
|
{
|
||||||
|
FloatingBarMain,
|
||||||
|
FloatingBarCanvasControls,
|
||||||
|
FloatingBarEnd,
|
||||||
|
BlackboardLeft,
|
||||||
|
BlackboardRight
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
|
|
||||||
@@ -7,6 +10,138 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
internal class AnimationsHelper
|
internal class AnimationsHelper
|
||||||
{
|
{
|
||||||
|
#region Win32 API - 用于提升 Popup 层级和刷新位置
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct RECT
|
||||||
|
{
|
||||||
|
public int Left;
|
||||||
|
public int Top;
|
||||||
|
public int Right;
|
||||||
|
public int Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const uint GW_HWNDPREV = 3;
|
||||||
|
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||||
|
private static readonly IntPtr HWND_TOP = new IntPtr(0);
|
||||||
|
private const uint SWP_NOMOVE = 0x0002;
|
||||||
|
private const uint SWP_NOSIZE = 0x0001;
|
||||||
|
private const uint SWP_NOACTIVATE = 0x0010;
|
||||||
|
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 强制刷新 Popup 的实际窗口位置(终极方案)
|
||||||
|
/// 通过 Win32 API 直接操作窗口句柄
|
||||||
|
/// </summary>
|
||||||
|
public static void ForceRefreshPopupPosition(Popup popup)
|
||||||
|
{
|
||||||
|
if (popup?.Child == null || !popup.IsOpen) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var source = PresentationSource.FromVisual(popup.Child) as HwndSource;
|
||||||
|
if (source?.Handle == null) return;
|
||||||
|
|
||||||
|
var hwnd = source.Handle;
|
||||||
|
|
||||||
|
// 获取当前窗口位置
|
||||||
|
if (GetWindowRect(hwnd, out RECT rect))
|
||||||
|
{
|
||||||
|
// 使用相同的参数调用 SetWindowPos,但加上 SWP_SHOWWINDOW
|
||||||
|
// 这会强制窗口管理器重新评估并更新窗口位置
|
||||||
|
SetWindowPos(
|
||||||
|
hwnd,
|
||||||
|
HWND_TOP,
|
||||||
|
rect.Left, rect.Top,
|
||||||
|
rect.Right - rect.Left,
|
||||||
|
rect.Bottom - rect.Top,
|
||||||
|
SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] Force refreshed position: ({rect.Left}, {rect.Top})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] ForceRefreshPopupPosition failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}), System.Windows.Threading.DispatcherPriority.Render);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] ForceRefreshPopupPosition error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 Popup 窗口提升到最顶层,确保不被其他控件遮挡
|
||||||
|
/// 采用多重策略确保置顶生效
|
||||||
|
/// </summary>
|
||||||
|
private static void BringPopupToFront(Popup popup)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (popup?.Child == null) return;
|
||||||
|
|
||||||
|
Action bringToTopAction = () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var source = PresentationSource.FromVisual(popup.Child) as HwndSource;
|
||||||
|
if (source?.Handle == null) return;
|
||||||
|
|
||||||
|
var hwnd = source.Handle;
|
||||||
|
|
||||||
|
// 策略1:直接设置为 TOPMOST(最高优先级)
|
||||||
|
SetWindowPos(hwnd, HWND_TOPMOST,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] Set TOPMOST for popup");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] BringPopupToFront failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 立即执行第一次
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
|
||||||
|
System.Windows.Threading.DispatcherPriority.Render);
|
||||||
|
|
||||||
|
// 延迟 50ms 后再次执行(确保在其他窗口操作之后)
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
|
||||||
|
System.Windows.Threading.DispatcherPriority.Normal);
|
||||||
|
|
||||||
|
// 延迟 100ms 后第三次执行(最终确认)
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
|
||||||
|
System.Windows.Threading.DispatcherPriority.Background);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] BringPopupToFront error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static UIElement ResolveAnimationTarget(UIElement element)
|
||||||
|
{
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
public static void ShowWithFadeIn(UIElement element, double duration = 0.15)
|
public static void ShowWithFadeIn(UIElement element, double duration = 0.15)
|
||||||
{
|
{
|
||||||
if (element.Visibility == Visibility.Visible) return;
|
if (element.Visibility == Visibility.Visible) return;
|
||||||
@@ -36,14 +171,17 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (element.Visibility == Visibility.Visible) return;
|
|
||||||
|
|
||||||
if (element == null)
|
if (element == null)
|
||||||
throw new ArgumentNullException(nameof(element));
|
throw new ArgumentNullException(nameof(element));
|
||||||
|
|
||||||
|
if (element.Visibility == Visibility.Visible) return;
|
||||||
|
|
||||||
|
element.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
var target = ResolveAnimationTarget(element);
|
||||||
|
|
||||||
var sb = new Storyboard();
|
var sb = new Storyboard();
|
||||||
|
|
||||||
// 渐变动画
|
|
||||||
var fadeInAnimation = new DoubleAnimation
|
var fadeInAnimation = new DoubleAnimation
|
||||||
{
|
{
|
||||||
From = 0.5,
|
From = 0.5,
|
||||||
@@ -54,10 +192,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||||
|
|
||||||
// 滑动动画
|
|
||||||
var slideAnimation = new DoubleAnimation
|
var slideAnimation = new DoubleAnimation
|
||||||
{
|
{
|
||||||
From = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
From = 10,
|
||||||
To = 0,
|
To = 0,
|
||||||
Duration = TimeSpan.FromSeconds(duration)
|
Duration = TimeSpan.FromSeconds(duration)
|
||||||
};
|
};
|
||||||
@@ -68,10 +205,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
sb.Children.Add(fadeInAnimation);
|
sb.Children.Add(fadeInAnimation);
|
||||||
sb.Children.Add(slideAnimation);
|
sb.Children.Add(slideAnimation);
|
||||||
|
|
||||||
element.Visibility = Visibility.Visible;
|
target.RenderTransform = new TranslateTransform();
|
||||||
element.RenderTransform = new TranslateTransform();
|
|
||||||
|
|
||||||
sb.Begin((FrameworkElement)element);
|
sb.Begin((FrameworkElement)target);
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
}
|
}
|
||||||
@@ -207,14 +343,15 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (element.Visibility == Visibility.Collapsed) return;
|
|
||||||
|
|
||||||
if (element == null)
|
if (element == null)
|
||||||
throw new ArgumentNullException(nameof(element));
|
throw new ArgumentNullException(nameof(element));
|
||||||
|
|
||||||
|
if (element.Visibility == Visibility.Collapsed) return;
|
||||||
|
|
||||||
|
var target = ResolveAnimationTarget(element);
|
||||||
|
|
||||||
var sb = new Storyboard();
|
var sb = new Storyboard();
|
||||||
|
|
||||||
// 渐变动画
|
|
||||||
var fadeOutAnimation = new DoubleAnimation
|
var fadeOutAnimation = new DoubleAnimation
|
||||||
{
|
{
|
||||||
From = 1,
|
From = 1,
|
||||||
@@ -224,11 +361,10 @@ namespace Ink_Canvas.Helpers
|
|||||||
fadeOutAnimation.EasingFunction = new CubicEase();
|
fadeOutAnimation.EasingFunction = new CubicEase();
|
||||||
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||||
|
|
||||||
// 滑动动画
|
|
||||||
var slideAnimation = new DoubleAnimation
|
var slideAnimation = new DoubleAnimation
|
||||||
{
|
{
|
||||||
From = 0,
|
From = 0,
|
||||||
To = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
To = 10,
|
||||||
Duration = TimeSpan.FromSeconds(duration)
|
Duration = TimeSpan.FromSeconds(duration)
|
||||||
};
|
};
|
||||||
slideAnimation.EasingFunction = new CubicEase();
|
slideAnimation.EasingFunction = new CubicEase();
|
||||||
@@ -243,8 +379,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
element.Visibility = Visibility.Collapsed;
|
element.Visibility = Visibility.Collapsed;
|
||||||
};
|
};
|
||||||
|
|
||||||
element.RenderTransform = new TranslateTransform();
|
target.RenderTransform = new TranslateTransform();
|
||||||
sb.Begin((FrameworkElement)element);
|
sb.Begin((FrameworkElement)target);
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
}
|
}
|
||||||
@@ -258,7 +394,6 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
var sb = new Storyboard();
|
var sb = new Storyboard();
|
||||||
|
|
||||||
// 渐变动画
|
|
||||||
var fadeOutAnimation = new DoubleAnimation
|
var fadeOutAnimation = new DoubleAnimation
|
||||||
{
|
{
|
||||||
From = 1,
|
From = 1,
|
||||||
@@ -277,5 +412,110 @@ namespace Ink_Canvas.Helpers
|
|||||||
sb.Begin((FrameworkElement)element);
|
sb.Begin((FrameworkElement)element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ShowPopupWithSlideAndFade(Popup popup, double duration = 0.15)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (popup == null)
|
||||||
|
throw new ArgumentNullException(nameof(popup));
|
||||||
|
|
||||||
|
if (popup.IsOpen) return;
|
||||||
|
|
||||||
|
var child = popup.Child as FrameworkElement;
|
||||||
|
if (child == null)
|
||||||
|
{
|
||||||
|
popup.IsOpen = true;
|
||||||
|
BringPopupToFront(popup);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
child.Opacity = 0.5;
|
||||||
|
child.RenderTransform = new TranslateTransform(0, 10);
|
||||||
|
|
||||||
|
popup.IsOpen = true;
|
||||||
|
|
||||||
|
// 提升 Popup 到最顶层
|
||||||
|
BringPopupToFront(popup);
|
||||||
|
|
||||||
|
var sb = new Storyboard();
|
||||||
|
|
||||||
|
var fadeInAnimation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = 0.5,
|
||||||
|
To = 1,
|
||||||
|
Duration = TimeSpan.FromSeconds(duration)
|
||||||
|
};
|
||||||
|
fadeInAnimation.EasingFunction = new CubicEase();
|
||||||
|
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||||
|
|
||||||
|
var slideAnimation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = 10,
|
||||||
|
To = 0,
|
||||||
|
Duration = TimeSpan.FromSeconds(duration)
|
||||||
|
};
|
||||||
|
slideAnimation.EasingFunction = new CubicEase();
|
||||||
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
||||||
|
|
||||||
|
sb.Children.Add(fadeInAnimation);
|
||||||
|
sb.Children.Add(slideAnimation);
|
||||||
|
|
||||||
|
sb.Begin(child);
|
||||||
|
}
|
||||||
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void HidePopupWithSlideAndFade(Popup popup, double duration = 0.15)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (popup == null)
|
||||||
|
throw new ArgumentNullException(nameof(popup));
|
||||||
|
|
||||||
|
if (!popup.IsOpen) return;
|
||||||
|
|
||||||
|
var child = popup.Child as FrameworkElement;
|
||||||
|
if (child == null)
|
||||||
|
{
|
||||||
|
popup.IsOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new Storyboard();
|
||||||
|
|
||||||
|
var fadeOutAnimation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = 1,
|
||||||
|
To = 0,
|
||||||
|
Duration = TimeSpan.FromSeconds(duration)
|
||||||
|
};
|
||||||
|
fadeOutAnimation.EasingFunction = new CubicEase();
|
||||||
|
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||||
|
|
||||||
|
var slideAnimation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = 0,
|
||||||
|
To = 10,
|
||||||
|
Duration = TimeSpan.FromSeconds(duration)
|
||||||
|
};
|
||||||
|
slideAnimation.EasingFunction = new CubicEase();
|
||||||
|
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
||||||
|
|
||||||
|
sb.Children.Add(fadeOutAnimation);
|
||||||
|
sb.Children.Add(slideAnimation);
|
||||||
|
|
||||||
|
sb.Completed += (s, e) =>
|
||||||
|
{
|
||||||
|
popup.IsOpen = false;
|
||||||
|
child.Opacity = 1;
|
||||||
|
child.RenderTransform = new TranslateTransform();
|
||||||
|
};
|
||||||
|
|
||||||
|
child.RenderTransform = new TranslateTransform();
|
||||||
|
sb.Begin(child);
|
||||||
|
}
|
||||||
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using Ink_Canvas.Windows.SettingsViews.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
public static class AppRestartHelper
|
||||||
|
{
|
||||||
|
public static bool IsRunningAsAdmin()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var identity = WindowsIdentity.GetCurrent();
|
||||||
|
var principal = new WindowsPrincipal(identity);
|
||||||
|
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestartApp(bool asAdmin)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
App.IsAppExitByUser = true;
|
||||||
|
|
||||||
|
(Application.Current as App)?.ReleaseMutexForRestart();
|
||||||
|
|
||||||
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||||
|
|
||||||
|
if (asAdmin)
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo(exePath) { UseShellExecute = true, Verb = "runas" };
|
||||||
|
Process.Start(psi);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 当前已是管理员时,直接通过用户令牌降权启动,避免经由 explorer 中转的延迟
|
||||||
|
if (IsRunningAsAdmin() && UIAccessHelper.RestartAsNormalUser())
|
||||||
|
{
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process.Start("explorer.exe", "\"" + exePath + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"重启应用时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestartWithCurrentPrivileges()
|
||||||
|
{
|
||||||
|
RestartApp(IsRunningAsAdmin());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestartAsAdmin()
|
||||||
|
{
|
||||||
|
RestartApp(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RestartAsNormal()
|
||||||
|
{
|
||||||
|
RestartApp(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SwitchToUIATopMostAndRestart()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SettingsManager.Settings.Advanced.EnableUIAccessTopMost = true;
|
||||||
|
|
||||||
|
if (!SettingsManager.Settings.Advanced.IsAlwaysOnTop)
|
||||||
|
{
|
||||||
|
SettingsManager.Settings.Advanced.IsAlwaysOnTop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsManager.SaveSettingsToFile();
|
||||||
|
|
||||||
|
App.IsUIAccessTopMostEnabled = true;
|
||||||
|
RestartApp(true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"切换到UIA置顶模式时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SwitchToNormalTopMostAndRestart()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SettingsManager.Settings.Advanced.EnableUIAccessTopMost = false;
|
||||||
|
SettingsManager.SaveSettingsToFile();
|
||||||
|
|
||||||
|
App.IsUIAccessTopMostEnabled = false;
|
||||||
|
RestartApp(IsRunningAsAdmin());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"切换到普通置顶模式时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,37 @@ namespace Ink_Canvas.Helpers
|
|||||||
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||||
private static string statusFilePath;
|
private static string statusFilePath;
|
||||||
|
|
||||||
|
// 全局下载取消令牌;UI 通过 RequestCancelDownload 取消当前下载
|
||||||
|
private static CancellationTokenSource _activeDownloadCts;
|
||||||
|
private static readonly object _activeDownloadLock = new object();
|
||||||
|
|
||||||
|
public static void RequestCancelDownload()
|
||||||
|
{
|
||||||
|
lock (_activeDownloadLock)
|
||||||
|
{
|
||||||
|
try { _activeDownloadCts?.Cancel(); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CancellationTokenSource BeginDownloadSession()
|
||||||
|
{
|
||||||
|
lock (_activeDownloadLock)
|
||||||
|
{
|
||||||
|
try { _activeDownloadCts?.Cancel(); } catch { }
|
||||||
|
_activeDownloadCts = new CancellationTokenSource();
|
||||||
|
return _activeDownloadCts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EndDownloadSession(CancellationTokenSource cts)
|
||||||
|
{
|
||||||
|
lock (_activeDownloadLock)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(_activeDownloadCts, cts)) _activeDownloadCts = null;
|
||||||
|
}
|
||||||
|
try { cts?.Dispose(); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsX64UpdatePackageSelected()
|
public static bool IsX64UpdatePackageSelected()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -383,6 +414,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 获取所有可用线路组,按延迟排序
|
// 获取所有可用线路组,按延迟排序
|
||||||
public static async Task<List<UpdateLineGroup>> GetAvailableLineGroupsOrdered(UpdateChannel channel)
|
public static async Task<List<UpdateLineGroup>> GetAvailableLineGroupsOrdered(UpdateChannel channel)
|
||||||
{
|
{
|
||||||
|
var cached = TryGetCachedOrderedGroups(channel);
|
||||||
|
if (cached != null) return cached;
|
||||||
var groups = ChannelLineGroups[channel];
|
var groups = ChannelLineGroups[channel];
|
||||||
var availableGroups = new List<(UpdateLineGroup group, long delay)>();
|
var availableGroups = new List<(UpdateLineGroup group, long delay)>();
|
||||||
|
|
||||||
@@ -468,9 +501,46 @@ namespace Ink_Canvas.Helpers
|
|||||||
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CacheOrderedGroups(channel, orderedGroups);
|
||||||
return orderedGroups;
|
return orderedGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 缓存按延迟排序后的线路组,避免短时间内重复测速
|
||||||
|
private static readonly Dictionary<UpdateChannel, (List<UpdateLineGroup> groups, DateTime cachedAt)> _orderedGroupsCache
|
||||||
|
= new Dictionary<UpdateChannel, (List<UpdateLineGroup>, DateTime)>();
|
||||||
|
private static readonly TimeSpan _orderedGroupsCacheTtl = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
|
private static List<UpdateLineGroup> TryGetCachedOrderedGroups(UpdateChannel channel)
|
||||||
|
{
|
||||||
|
lock (_orderedGroupsCache)
|
||||||
|
{
|
||||||
|
if (_orderedGroupsCache.TryGetValue(channel, out var entry) &&
|
||||||
|
entry.groups != null && entry.groups.Count > 0 &&
|
||||||
|
DateTime.UtcNow - entry.cachedAt < _orderedGroupsCacheTtl)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"AutoUpdate | 复用线路组延迟检测缓存({entry.groups.Count} 个)");
|
||||||
|
return new List<UpdateLineGroup>(entry.groups);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CacheOrderedGroups(UpdateChannel channel, List<UpdateLineGroup> groups)
|
||||||
|
{
|
||||||
|
lock (_orderedGroupsCache)
|
||||||
|
{
|
||||||
|
_orderedGroupsCache[channel] = (new List<UpdateLineGroup>(groups), DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InvalidateOrderedGroupsCache()
|
||||||
|
{
|
||||||
|
lock (_orderedGroupsCache)
|
||||||
|
{
|
||||||
|
_orderedGroupsCache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<long> GetDownloadUrlDelay(string url)
|
private static async Task<long> GetDownloadUrlDelay(string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -945,6 +1015,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 使用多线路组下载新版(支持自动切换)
|
// 使用多线路组下载新版(支持自动切换)
|
||||||
public static async Task<bool> DownloadSetupFileWithFallback(string version, List<UpdateLineGroup> groups, Action<double, string> progressCallback = null)
|
public static async Task<bool> DownloadSetupFileWithFallback(string version, List<UpdateLineGroup> groups, Action<double, string> progressCallback = null)
|
||||||
{
|
{
|
||||||
|
var session = BeginDownloadSession();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
version = NormalizeVersionForUpdate(version);
|
version = NormalizeVersionForUpdate(version);
|
||||||
@@ -979,8 +1050,19 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 依次尝试每个线路组
|
// 依次尝试每个线路组
|
||||||
|
CancellationToken groupLoopToken;
|
||||||
|
lock (_activeDownloadLock)
|
||||||
|
{
|
||||||
|
groupLoopToken = _activeDownloadCts?.Token ?? CancellationToken.None;
|
||||||
|
}
|
||||||
foreach (var group in groups)
|
foreach (var group in groups)
|
||||||
{
|
{
|
||||||
|
if (groupLoopToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | 用户已取消,停止尝试后续线路组");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
string url = string.Format(group.DownloadUrlFormat, version);
|
string url = string.Format(group.DownloadUrlFormat, version);
|
||||||
url = AppendX64SuffixBeforeZipExtension(url);
|
url = AppendX64SuffixBeforeZipExtension(url);
|
||||||
// 智教联盟需要先获取真实下载地址
|
// 智教联盟需要先获取真实下载地址
|
||||||
@@ -1006,6 +1088,12 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback);
|
bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback);
|
||||||
|
|
||||||
|
if (groupLoopToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | 用户已取消,停止尝试后续线路组");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (downloadSuccess)
|
if (downloadSuccess)
|
||||||
{
|
{
|
||||||
SaveDownloadStatus(true);
|
SaveDownloadStatus(true);
|
||||||
@@ -1021,6 +1109,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
progressCallback?.Invoke(0, "所有线路组下载均失败");
|
progressCallback?.Invoke(0, "所有线路组下载均失败");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | 下载已被用户取消", LogHelper.LogType.Warning);
|
||||||
|
SaveDownloadStatus(false);
|
||||||
|
progressCallback?.Invoke(0, "下载已取消");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
@@ -1033,6 +1128,10 @@ namespace Ink_Canvas.Helpers
|
|||||||
progressCallback?.Invoke(0, $"下载异常: {ex.Message}");
|
progressCallback?.Invoke(0, $"下载异常: {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EndDownloadSession(session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载文件的具体实现
|
// 下载文件的具体实现
|
||||||
@@ -1043,6 +1142,12 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 降低并发数,减少网络压力
|
// 降低并发数,减少网络压力
|
||||||
int[] threadOptions = { 32, 16, 8, 4, 1 };
|
int[] threadOptions = { 32, 16, 8, 4, 1 };
|
||||||
|
|
||||||
|
CancellationToken externalToken;
|
||||||
|
lock (_activeDownloadLock)
|
||||||
|
{
|
||||||
|
externalToken = _activeDownloadCts?.Token ?? CancellationToken.None;
|
||||||
|
}
|
||||||
|
|
||||||
// 检查服务器是否支持Range分块下载
|
// 检查服务器是否支持Range分块下载
|
||||||
bool supportRange = false;
|
bool supportRange = false;
|
||||||
long totalSize = -1;
|
long totalSize = -1;
|
||||||
@@ -1146,7 +1251,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 增加连接超时设置
|
// 增加连接超时设置
|
||||||
client.Timeout = TimeSpan.FromSeconds(30);
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
|
var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, externalToken);
|
||||||
var lastReadTime = DateTime.UtcNow;
|
var lastReadTime = DateTime.UtcNow;
|
||||||
bool dataReceived = false;
|
bool dataReceived = false;
|
||||||
|
|
||||||
@@ -1206,8 +1311,20 @@ namespace Ink_Canvas.Helpers
|
|||||||
success = true;
|
success = true;
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功");
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功");
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException)
|
catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException || ex is OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
// 用户主动取消:不再重试
|
||||||
|
if (externalToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载已被用户取消", LogHelper.LogType.Warning);
|
||||||
|
if (File.Exists(tempPath))
|
||||||
|
{
|
||||||
|
try { File.Delete(tempPath); } catch { }
|
||||||
|
}
|
||||||
|
cts.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning);
|
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}");
|
progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}");
|
||||||
|
|
||||||
@@ -1218,7 +1335,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 增加重试间隔,避免频繁重试
|
// 增加重试间隔,避免频繁重试
|
||||||
await Task.Delay(2000 * (retry + 1));
|
try { await Task.Delay(2000 * (retry + 1), externalToken); }
|
||||||
|
catch (OperationCanceledException) { cts.Cancel(); return; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (success)
|
if (success)
|
||||||
@@ -1339,12 +1457,18 @@ namespace Ink_Canvas.Helpers
|
|||||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}");
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}");
|
||||||
progressCallback?.Invoke(0, "开始单线程下载");
|
progressCallback?.Invoke(0, "开始单线程下载");
|
||||||
|
|
||||||
|
CancellationToken token;
|
||||||
|
lock (_activeDownloadLock)
|
||||||
|
{
|
||||||
|
token = _activeDownloadCts?.Token ?? CancellationToken.None;
|
||||||
|
}
|
||||||
|
|
||||||
using (var client = new HttpClient())
|
using (var client = new HttpClient())
|
||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||||
client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间
|
client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间
|
||||||
|
|
||||||
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
|
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, token))
|
||||||
{
|
{
|
||||||
resp.EnsureSuccessStatusCode();
|
resp.EnsureSuccessStatusCode();
|
||||||
using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||||
@@ -1355,9 +1479,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
long downloaded = 0;
|
long downloaded = 0;
|
||||||
var lastProgressUpdate = DateTime.UtcNow;
|
var lastProgressUpdate = DateTime.UtcNow;
|
||||||
|
|
||||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
|
||||||
{
|
{
|
||||||
await fs.WriteAsync(buffer, 0, read);
|
await fs.WriteAsync(buffer, 0, read, token);
|
||||||
downloaded += read;
|
downloaded += read;
|
||||||
|
|
||||||
// 限制进度更新频率,避免UI卡顿
|
// 限制进度更新频率,避免UI卡顿
|
||||||
@@ -1379,6 +1503,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载完成");
|
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载完成");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载已被取消", LogHelper.LogType.Warning);
|
||||||
|
progressCallback?.Invoke(0, "下载已取消");
|
||||||
|
try { if (File.Exists(destinationPath)) File.Delete(destinationPath); } catch { }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
@@ -2201,9 +2332,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}");
|
LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}");
|
||||||
|
|
||||||
// 获取远程版本号(自动选择最快线路组,始终下载远程版本,版本修复模式)
|
// 获取远程版本号(始终下载远程版本,版本修复模式)
|
||||||
var (remoteVersion, group, _) = await CheckForUpdates(channel, true, true);
|
var (remoteVersion, preferredGroup, _) = await CheckForUpdates(channel, true, true);
|
||||||
if (string.IsNullOrEmpty(remoteVersion) || group == null)
|
if (string.IsNullOrEmpty(remoteVersion))
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error);
|
||||||
return false;
|
return false;
|
||||||
@@ -2211,8 +2342,22 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | 修复版本远程版本: {remoteVersion}");
|
LogHelper.WriteLogToFile($"AutoUpdate | 修复版本远程版本: {remoteVersion}");
|
||||||
|
|
||||||
|
var availableGroups = await GetAvailableLineGroupsOrdered(channel);
|
||||||
|
if (availableGroups.Count == 0)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时无可用线路组", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredGroup != null)
|
||||||
|
{
|
||||||
|
availableGroups.RemoveAll(g => g.GroupName == preferredGroup.GroupName);
|
||||||
|
availableGroups.Insert(0, preferredGroup);
|
||||||
|
LogHelper.WriteLogToFile($"AutoUpdate | 修复版本下载优先使用线路组: {preferredGroup.GroupName}");
|
||||||
|
}
|
||||||
|
|
||||||
// 无论版本是否为最新,都下载远程版本
|
// 无论版本是否为最新,都下载远程版本
|
||||||
bool downloadResult = await DownloadSetupFile(remoteVersion, group);
|
bool downloadResult = await DownloadSetupFileWithFallback(remoteVersion, availableGroups);
|
||||||
if (!downloadResult)
|
if (!downloadResult)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时下载更新失败", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时下载更新失败", LogHelper.LogType.Error);
|
||||||
|
|||||||
@@ -519,7 +519,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步上传文件
|
/// 异步上传文件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
public Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -528,19 +528,19 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 检查是否启用
|
// 检查是否启用
|
||||||
if (!IsUploadEnabled())
|
if (!IsUploadEnabled())
|
||||||
{
|
{
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基本验证
|
// 基本验证
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValidFile(filePath))
|
if (!IsValidFile(filePath))
|
||||||
{
|
{
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保队列已初始化
|
// 确保队列已初始化
|
||||||
@@ -552,7 +552,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 加入队列
|
// 加入队列
|
||||||
EnqueueFile(filePath, 0, cancellationToken);
|
EnqueueFile(filePath, 0, cancellationToken);
|
||||||
|
|
||||||
return true;
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -562,7 +562,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"[{GetType().Name}] 加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
namespace Ink_Canvas.Converter
|
namespace Ink_Canvas.Converter
|
||||||
{
|
{
|
||||||
@@ -152,4 +153,27 @@ namespace Ink_Canvas.Converter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class StringToGeometryConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (value is string geometryString && !string.IsNullOrEmpty(geometryString))
|
||||||
|
{
|
||||||
|
return Geometry.Parse(geometryString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
public static class DebugConsoleManager
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern bool AllocConsole();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern bool FreeConsole();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern IntPtr GetConsoleWindow();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern bool SetConsoleTitle(string lpConsoleTitle);
|
||||||
|
|
||||||
|
private const int SW_HIDE = 0;
|
||||||
|
private const int SW_SHOW = 5;
|
||||||
|
private const uint SC_CLOSE = 0xF060;
|
||||||
|
private const uint MF_BYCOMMAND = 0x00000000;
|
||||||
|
|
||||||
|
private static bool _allocated;
|
||||||
|
|
||||||
|
public static bool IsVisible { get; private set; }
|
||||||
|
|
||||||
|
public static void Show()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_allocated)
|
||||||
|
{
|
||||||
|
if (GetConsoleWindow() == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
if (!AllocConsole()) return;
|
||||||
|
}
|
||||||
|
_allocated = true;
|
||||||
|
|
||||||
|
Console.OutputEncoding = Encoding.UTF8;
|
||||||
|
SetConsoleTitle("InkCanvasForClass - Debug Console");
|
||||||
|
|
||||||
|
// 移除关闭菜单,避免用户点 X 时直接结束进程
|
||||||
|
var hWnd = GetConsoleWindow();
|
||||||
|
if (hWnd != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var hMenu = GetSystemMenu(hWnd, false);
|
||||||
|
if (hMenu != IntPtr.Zero) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var hWnd = GetConsoleWindow();
|
||||||
|
if (hWnd != IntPtr.Zero) ShowWindow(hWnd, SW_SHOW);
|
||||||
|
}
|
||||||
|
IsVisible = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[DebugConsoleManager] Show failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Hide()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hWnd = GetConsoleWindow();
|
||||||
|
if (hWnd != IntPtr.Zero) ShowWindow(hWnd, SW_HIDE);
|
||||||
|
IsVisible = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[DebugConsoleManager] Hide failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteLine(string line)
|
||||||
|
{
|
||||||
|
if (!IsVisible) return;
|
||||||
|
try { Console.WriteLine(line); }
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
private static readonly string DeviceId;
|
private static readonly string DeviceId;
|
||||||
private static readonly object fileLock = new object();
|
private static readonly object fileLock = new object();
|
||||||
|
private static UsageStats usageStatsCache;
|
||||||
|
private static DateTime usageStatsCacheTime;
|
||||||
|
private static readonly TimeSpan UsageStatsCacheDuration = TimeSpan.FromMinutes(2);
|
||||||
|
|
||||||
static DeviceIdentifier()
|
static DeviceIdentifier()
|
||||||
{
|
{
|
||||||
@@ -116,114 +120,26 @@ namespace Ink_Canvas.Helpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static string GenerateHardwareFingerprint()
|
private static string GenerateHardwareFingerprint()
|
||||||
{
|
{
|
||||||
// 收集硬件信息
|
|
||||||
var hardwareInfo = new StringBuilder();
|
var hardwareInfo = new StringBuilder();
|
||||||
|
AppendFingerprintPart(hardwareInfo, "CPU",
|
||||||
|
GetWmiProperty("SELECT ProcessorId FROM Win32_Processor", "ProcessorId"),
|
||||||
|
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0", "ProcessorNameString"),
|
||||||
|
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0", "Identifier"));
|
||||||
|
|
||||||
try
|
AppendFingerprintPart(hardwareInfo, "BOARD",
|
||||||
{
|
GetWmiProperty("SELECT SerialNumber FROM Win32_BaseBoard", "SerialNumber"),
|
||||||
var assembly = Assembly.Load("System.Management");
|
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BaseBoardSerialNumber"),
|
||||||
if (assembly != null)
|
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BaseBoardProduct"));
|
||||||
{
|
|
||||||
// CPU信息
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
|
||||||
var searcher = Activator.CreateInstance(searcherType, "SELECT ProcessorId FROM Win32_Processor");
|
|
||||||
var getMethod = searcherType.GetMethod("Get");
|
|
||||||
var enumerator = getMethod.Invoke(searcher, null);
|
|
||||||
|
|
||||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
AppendFingerprintPart(hardwareInfo, "BIOS",
|
||||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
GetWmiProperty("SELECT SerialNumber FROM Win32_BIOS", "SerialNumber"),
|
||||||
|
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BIOSVersion"),
|
||||||
|
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BIOSVendor"));
|
||||||
|
|
||||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
AppendFingerprintPart(hardwareInfo, "DISK",
|
||||||
{
|
GetWmiProperty("SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType='Fixed hard disk media'", "SerialNumber"),
|
||||||
var obj = currentProperty.GetValue(enumerator);
|
GetSystemDriveVolumeSerial(),
|
||||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
GetRegistryValue(@"SOFTWARE\Microsoft\Cryptography", "MachineGuid"));
|
||||||
var processorId = indexer.GetValue(obj, new object[] { "ProcessorId" });
|
|
||||||
hardwareInfo.Append(processorId?.ToString() ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
|
||||||
disposeMethod?.Invoke(searcher, null);
|
|
||||||
}
|
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
|
|
||||||
// 主板序列号
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
|
||||||
var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BaseBoard");
|
|
||||||
var getMethod = searcherType.GetMethod("Get");
|
|
||||||
var enumerator = getMethod.Invoke(searcher, null);
|
|
||||||
|
|
||||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
|
||||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
|
||||||
|
|
||||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
|
||||||
{
|
|
||||||
var obj = currentProperty.GetValue(enumerator);
|
|
||||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
|
||||||
var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" });
|
|
||||||
hardwareInfo.Append(serialNumber?.ToString() ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
|
||||||
disposeMethod?.Invoke(searcher, null);
|
|
||||||
}
|
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
|
|
||||||
// BIOS序列号
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
|
||||||
var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BIOS");
|
|
||||||
var getMethod = searcherType.GetMethod("Get");
|
|
||||||
var enumerator = getMethod.Invoke(searcher, null);
|
|
||||||
|
|
||||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
|
||||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
|
||||||
|
|
||||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
|
||||||
{
|
|
||||||
var obj = currentProperty.GetValue(enumerator);
|
|
||||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
|
||||||
var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" });
|
|
||||||
hardwareInfo.Append(serialNumber?.ToString() ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
|
||||||
disposeMethod?.Invoke(searcher, null);
|
|
||||||
}
|
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
|
|
||||||
// 主硬盘序列号
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
|
||||||
var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType='Fixed hard disk media'");
|
|
||||||
var getMethod = searcherType.GetMethod("Get");
|
|
||||||
var enumerator = getMethod.Invoke(searcher, null);
|
|
||||||
|
|
||||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
|
||||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
|
||||||
|
|
||||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
|
||||||
{
|
|
||||||
var obj = currentProperty.GetValue(enumerator);
|
|
||||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
|
||||||
var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" });
|
|
||||||
hardwareInfo.Append(serialNumber?.ToString() ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
|
||||||
disposeMethod?.Invoke(searcher, null);
|
|
||||||
}
|
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hardwareInfo.Length < 10)
|
if (hardwareInfo.Length < 10)
|
||||||
{
|
{
|
||||||
@@ -235,6 +151,108 @@ namespace Ink_Canvas.Helpers
|
|||||||
return hardwareInfo.ToString();
|
return hardwareInfo.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AppendFingerprintPart(StringBuilder hardwareInfo, string key, params string[] candidates)
|
||||||
|
{
|
||||||
|
foreach (var candidate in candidates)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(candidate))
|
||||||
|
{
|
||||||
|
hardwareInfo.Append(key).Append(':').Append(candidate.Trim()).Append(';');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetWmiProperty(string query, string propertyName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = Assembly.Load("System.Management");
|
||||||
|
var searcherType = assembly?.GetType("System.Management.ManagementObjectSearcher");
|
||||||
|
if (searcherType == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var searcher = Activator.CreateInstance(searcherType, query);
|
||||||
|
var getMethod = searcherType.GetMethod("Get");
|
||||||
|
var resultCollection = getMethod?.Invoke(searcher, null);
|
||||||
|
if (resultCollection == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enumerator = resultCollection.GetType().GetMethod("GetEnumerator")?.Invoke(resultCollection, null);
|
||||||
|
var moveNextMethod = enumerator?.GetType().GetMethod("MoveNext");
|
||||||
|
var currentProperty = enumerator?.GetType().GetProperty("Current");
|
||||||
|
if (enumerator == null || moveNextMethod == null || currentProperty == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(bool)moveNextMethod.Invoke(enumerator, null))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentObject = currentProperty.GetValue(enumerator);
|
||||||
|
var indexer = currentObject?.GetType().GetProperty("Item", new[] { typeof(string) });
|
||||||
|
var result = indexer?.GetValue(currentObject, new object[] { propertyName })?.ToString();
|
||||||
|
|
||||||
|
searcher?.GetType().GetMethod("Dispose")?.Invoke(searcher, null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRegistryValue(string subKey, string valueName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Microsoft.Win32.Registry.GetValue($@"HKEY_LOCAL_MACHINE\{subKey}", valueName, null)?.ToString();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSystemDriveVolumeSerial()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var rootPath = Path.GetPathRoot(Environment.SystemDirectory);
|
||||||
|
if (string.IsNullOrWhiteSpace(rootPath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetVolumeInformation(rootPath, null, 0, out uint serialNumber, out _, out _, null, 0))
|
||||||
|
{
|
||||||
|
return serialNumber.ToString("X8");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
private static extern bool GetVolumeInformation(
|
||||||
|
string rootPathName,
|
||||||
|
StringBuilder volumeNameBuffer,
|
||||||
|
uint volumeNameSize,
|
||||||
|
out uint volumeSerialNumber,
|
||||||
|
out uint maximumComponentLength,
|
||||||
|
out uint fileSystemFlags,
|
||||||
|
StringBuilder fileSystemNameBuffer,
|
||||||
|
uint nFileSystemNameSize);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基于硬件指纹生成25字符的设备ID
|
/// 基于硬件指纹生成25字符的设备ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -654,7 +672,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stats = LoadUsageStats();
|
var stats = GetUsageStatsCached();
|
||||||
return stats.SystemVersion;
|
return stats.SystemVersion;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -773,7 +791,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stats = LoadUsageStats();
|
var stats = GetUsageStatsCached();
|
||||||
return stats.UpdatePriority;
|
return stats.UpdatePriority;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -790,7 +808,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var stats = LoadUsageStats();
|
var stats = GetUsageStatsCached();
|
||||||
return stats.UsageFrequency;
|
return stats.UsageFrequency;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -892,6 +910,23 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UsageStats GetUsageStatsCached(bool forceRefresh = false)
|
||||||
|
{
|
||||||
|
lock (fileLock)
|
||||||
|
{
|
||||||
|
if (!forceRefresh
|
||||||
|
&& usageStatsCache != null
|
||||||
|
&& (DateTime.Now - usageStatsCacheTime) < UsageStatsCacheDuration)
|
||||||
|
{
|
||||||
|
return usageStatsCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
usageStatsCache = LoadUsageStats();
|
||||||
|
usageStatsCacheTime = DateTime.Now;
|
||||||
|
return usageStatsCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存使用统计
|
/// 保存使用统计
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -902,6 +937,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
// 保存到备份文件
|
// 保存到备份文件
|
||||||
SaveUsageStatsToFile(UsageStatsBackupPath, stats);
|
SaveUsageStatsToFile(UsageStatsBackupPath, stats);
|
||||||
|
|
||||||
|
usageStatsCache = stats;
|
||||||
|
usageStatsCacheTime = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1242,15 +1280,20 @@ namespace Ink_Canvas.Helpers
|
|||||||
int versionDiff = CalculateVersionGenerationDifference(localVersion, updateVersion);
|
int versionDiff = CalculateVersionGenerationDifference(localVersion, updateVersion);
|
||||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 无法获取版本发布时间,使用版本号差异判断 - 本地版本: {localVersion}, 远程版本: {updateVersion}, 代数差异: {versionDiff}");
|
LogHelper.WriteLogToFile($"DeviceIdentifier | 无法获取版本发布时间,使用版本号差异判断 - 本地版本: {localVersion}, 远程版本: {updateVersion}, 代数差异: {versionDiff}");
|
||||||
|
|
||||||
if (versionDiff >= 1)
|
if (versionDiff <= 0)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})>=1,允许更新");
|
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})<=0,可能是相同版本或降级,暂不更新");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})<1,可能是相同版本或降级,暂不更新");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当代数差异较大(>=3)时直接放行,避免被分级策略卡住
|
||||||
|
if (versionDiff >= 3)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})>=3,跳过分级策略直接推送");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})>=1,进入分级策略判断");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算最近活跃度(最后一次使用距今的天数)
|
// 计算最近活跃度(最后一次使用距今的天数)
|
||||||
@@ -1466,32 +1509,22 @@ namespace Ink_Canvas.Helpers
|
|||||||
int.TryParse(remoteParts[2], out int remoteBuild) &&
|
int.TryParse(remoteParts[2], out int remoteBuild) &&
|
||||||
int.TryParse(remoteParts[3], out int remoteRevision))
|
int.TryParse(remoteParts[3], out int remoteRevision))
|
||||||
{
|
{
|
||||||
// 计算代数差异:主版本号差异 * 1000 + 次版本号差异 * 100 + 构建号差异 * 10 + 修订号差异
|
var localSemver = new Version(localMajor, localMinor, localBuild, localRevision);
|
||||||
int majorDiff = remoteMajor - localMajor;
|
var remoteSemver = new Version(remoteMajor, remoteMinor, remoteBuild, remoteRevision);
|
||||||
int minorDiff = remoteMinor - localMinor;
|
int direction = remoteSemver.CompareTo(localSemver);
|
||||||
int buildDiff = remoteBuild - localBuild;
|
if (direction == 0) return 0;
|
||||||
int revisionDiff = remoteRevision - localRevision;
|
int sign = direction > 0 ? 1 : -1;
|
||||||
|
|
||||||
// 如果主版本号不同,则代数差异很大
|
int majorDiff = Math.Abs(remoteMajor - localMajor);
|
||||||
if (majorDiff != 0)
|
if (majorDiff != 0) return sign * (majorDiff * 1000);
|
||||||
{
|
|
||||||
return majorDiff * 1000 + minorDiff * 100 + buildDiff * 10 + revisionDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果次版本号不同,则代数差异中等
|
int minorDiff = Math.Abs(remoteMinor - localMinor);
|
||||||
if (minorDiff != 0)
|
if (minorDiff != 0) return sign * (minorDiff * 100);
|
||||||
{
|
|
||||||
return minorDiff * 100 + buildDiff * 10 + revisionDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果构建号不同,则代数差异较小
|
int buildDiff = Math.Abs(remoteBuild - localBuild);
|
||||||
if (buildDiff != 0)
|
if (buildDiff != 0) return sign * (buildDiff * 10);
|
||||||
{
|
|
||||||
return buildDiff * 10 + revisionDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只有修订号不同,代数差异最小
|
return sign * Math.Abs(remoteRevision - localRevision);
|
||||||
return revisionDiff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -54,6 +54,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
private static extern IntPtr MonitorFromRect(ref RECT lprc, uint dwFlags);
|
private static extern IntPtr MonitorFromRect(ref RECT lprc, uint dwFlags);
|
||||||
|
|
||||||
|
public static IntPtr GetForegroundWindowHandle()
|
||||||
|
{
|
||||||
|
return GetForegroundWindow();
|
||||||
|
}
|
||||||
|
|
||||||
public static string WindowTitle()
|
public static string WindowTitle()
|
||||||
{
|
{
|
||||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.ExceptionServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
@@ -189,9 +188,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 确保窗口全屏的Hook
|
/// 确保窗口全屏的Hook
|
||||||
/// 使用HandleProcessCorruptedStateExceptions,防止访问内存过程中因为一些致命异常导致程序崩溃
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HandleProcessCorruptedStateExceptions]
|
|
||||||
private static IntPtr KeepFullScreenHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
private static IntPtr KeepFullScreenHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||||
{
|
{
|
||||||
//处理WM_WINDOWPOSCHANGING消息
|
//处理WM_WINDOWPOSCHANGING消息
|
||||||
|
|||||||
@@ -567,6 +567,36 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 刷新多屏相关设置(开关和跟随鼠标策略)。
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshMultiScreenSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var advanced = MainWindow.Settings.Advanced;
|
||||||
|
_isMultiScreenMode = advanced.EnableMultiScreenSupport && ScreenDetectionHelper.HasMultipleScreens();
|
||||||
|
_enableScreenSpecificHotkeys = _isMultiScreenMode;
|
||||||
|
|
||||||
|
if (_isMultiScreenMode)
|
||||||
|
{
|
||||||
|
_currentScreen = advanced.FollowMouseForScreenSelection
|
||||||
|
? Screen.FromPoint(Control.MousePosition)
|
||||||
|
: ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentScreen = ScreenDetectionHelper.GetPrimaryScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshHotkeysForCurrentScreen();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"刷新多屏设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取当前屏幕信息
|
/// 获取当前屏幕信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -624,13 +654,15 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 检测是否有多个屏幕
|
var advanced = MainWindow.Settings.Advanced;
|
||||||
_isMultiScreenMode = ScreenDetectionHelper.HasMultipleScreens();
|
_isMultiScreenMode = advanced.EnableMultiScreenSupport && ScreenDetectionHelper.HasMultipleScreens();
|
||||||
|
_enableScreenSpecificHotkeys = _isMultiScreenMode;
|
||||||
|
|
||||||
if (_isMultiScreenMode)
|
if (_isMultiScreenMode)
|
||||||
{
|
{
|
||||||
// 获取当前窗口所在的屏幕
|
_currentScreen = advanced.FollowMouseForScreenSelection
|
||||||
_currentScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
? Screen.FromPoint(Control.MousePosition)
|
||||||
|
: ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||||
|
|
||||||
// 监听窗口位置变化事件
|
// 监听窗口位置变化事件
|
||||||
_mainWindow.LocationChanged += OnWindowLocationChanged;
|
_mainWindow.LocationChanged += OnWindowLocationChanged;
|
||||||
@@ -688,6 +720,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (MainWindow.Settings.Advanced.FollowMouseForScreenSelection)
|
||||||
|
return;
|
||||||
|
|
||||||
var newScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
var newScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||||
if (newScreen != null && newScreen != _currentScreen)
|
if (newScreen != null && newScreen != _currentScreen)
|
||||||
{
|
{
|
||||||
@@ -800,9 +835,16 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 检查鼠标是否在当前窗口所在的屏幕上
|
|
||||||
var mousePosition = Control.MousePosition;
|
var mousePosition = Control.MousePosition;
|
||||||
var currentScreen = Screen.FromPoint(mousePosition);
|
var mouseScreen = Screen.FromPoint(mousePosition);
|
||||||
|
|
||||||
|
if (MainWindow.Settings.Advanced.FollowMouseForScreenSelection &&
|
||||||
|
mouseScreen != null &&
|
||||||
|
mouseScreen != _currentScreen)
|
||||||
|
{
|
||||||
|
_currentScreen = mouseScreen;
|
||||||
|
RefreshHotkeysForCurrentScreen();
|
||||||
|
}
|
||||||
|
|
||||||
// 无论屏幕是否变化,都检查热键状态
|
// 无论屏幕是否变化,都检查热键状态
|
||||||
// 这样可以确保热键状态始终与当前上下文保持一致
|
// 这样可以确保热键状态始终与当前上下文保持一致
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
private static InkRecognitionManager _instance;
|
private static InkRecognitionManager _instance;
|
||||||
private static readonly object _lock = new object();
|
private static readonly object _lock = new object();
|
||||||
|
private readonly object _initSync = new object();
|
||||||
|
|
||||||
private ModernInkProcessor _modernProcessor;
|
private ModernInkProcessor _modernProcessor;
|
||||||
private ModernInkAnalyzer _modernAnalyzer;
|
|
||||||
private bool _isModernSystemAvailable;
|
private bool _isModernSystemAvailable;
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
|
|
||||||
@@ -31,35 +31,16 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private InkRecognitionManager()
|
private InkRecognitionManager() { }
|
||||||
{
|
|
||||||
Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tryModern = WinRtInkShapeRecognizer.IsApiAvailable && Environment.Is64BitProcess;
|
// 启动阶段只做能力探测,不做 WinRT 组件实例化(避免冷启动延迟)
|
||||||
|
_isModernSystemAvailable = WinRtInkShapeRecognizer.IsApiAvailable;
|
||||||
_isModernSystemAvailable = false;
|
|
||||||
if (tryModern)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_modernProcessor = new ModernInkProcessor();
|
|
||||||
_modernAnalyzer = new ModernInkAnalyzer();
|
|
||||||
_isModernSystemAvailable = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("WinRT 墨迹初始化失败: " + ex.Message, LogHelper.LogType.Warning);
|
|
||||||
_isModernSystemAvailable = false;
|
|
||||||
_modernProcessor = null;
|
|
||||||
_modernAnalyzer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -69,10 +50,41 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
lock (_initSync)
|
||||||
|
{
|
||||||
|
if (_isInitialized) return;
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureModernAnalyzerInitialized()
|
||||||
|
{
|
||||||
|
if (_modernProcessor != null || !_isModernSystemAvailable) return;
|
||||||
|
|
||||||
|
lock (_initSync)
|
||||||
|
{
|
||||||
|
if (_modernProcessor != null || !_isModernSystemAvailable) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_modernProcessor ??= new ModernInkProcessor();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("WinRT 墨迹模块懒加载失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||||
|
_isModernSystemAvailable = false;
|
||||||
|
_modernProcessor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task<InkShapeRecognitionResult> RecognizeShapeAsync(
|
public Task<InkShapeRecognitionResult> RecognizeShapeAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode)
|
ShapeRecognitionEngineMode mode)
|
||||||
{
|
{
|
||||||
|
EnsureInitialized();
|
||||||
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
||||||
return Task.FromResult(InkShapeRecognitionResult.Empty);
|
return Task.FromResult(InkShapeRecognitionResult.Empty);
|
||||||
|
|
||||||
@@ -84,8 +96,10 @@ namespace Ink_Canvas.Helpers
|
|||||||
return RecognizeShapeWinRtOnDispatcherContext(strokes);
|
return RecognizeShapeWinRtOnDispatcherContext(strokes);
|
||||||
}
|
}
|
||||||
|
|
||||||
var legacy = InkRecognizeHelper.RecognizeShapeIACore(strokes);
|
// IACore 必须走 IPC 辅助进程(x86/.NET 4.7.2)。
|
||||||
return Task.FromResult(InkRecognizeHelper.FromIACoreOrEmpty(legacy));
|
// 在 .NET 6 x64 主进程中本地加载 IAWinFX 会失败,故不再本地回退。
|
||||||
|
var ipcResult = IpcIACoreClient.Instance.Recognize(strokes);
|
||||||
|
return Task.FromResult(ipcResult);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -108,6 +122,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
bool applyHandwritingBeautify = false,
|
bool applyHandwritingBeautify = false,
|
||||||
string handwritingFontFamilyList = null)
|
string handwritingFontFamilyList = null)
|
||||||
{
|
{
|
||||||
|
EnsureInitialized();
|
||||||
if (!_isInitialized)
|
if (!_isInitialized)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("[手写体] CorrectInkAsync 跳过:InkRecognitionManager 未初始化。", LogHelper.LogType.Info);
|
LogHelper.WriteLogToFile("[手写体] CorrectInkAsync 跳过:InkRecognitionManager 未初始化。", LogHelper.LogType.Info);
|
||||||
@@ -140,18 +155,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
return Task.FromResult(strokes);
|
return Task.FromResult(strokes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Environment.Is64BitProcess)
|
EnsureModernAnalyzerInitialized();
|
||||||
|
if (_modernProcessor == null)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile(
|
LogHelper.WriteLogToFile(
|
||||||
"[手写体] CorrectInkAsync 跳过:非 64 位进程,WinRT 手写体替换不可用。笔画数=" + strokes.Count,
|
"[手写体] CorrectInkAsync 跳过:ModernInkProcessor 未就绪(WinRT 初始化失败?)。笔画数=" +
|
||||||
LogHelper.LogType.Info);
|
|
||||||
return Task.FromResult(strokes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_modernAnalyzer == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile(
|
|
||||||
"[手写体] CorrectInkAsync 跳过:ModernInkAnalyzer 未就绪(WinRT 初始化失败?)。笔画数=" +
|
|
||||||
strokes.Count,
|
strokes.Count,
|
||||||
LogHelper.LogType.Warning);
|
LogHelper.LogType.Warning);
|
||||||
return Task.FromResult(strokes);
|
return Task.FromResult(strokes);
|
||||||
@@ -161,7 +169,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
"[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count +
|
"[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count +
|
||||||
",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()),
|
",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()),
|
||||||
LogHelper.LogType.Info);
|
LogHelper.LogType.Info);
|
||||||
return _modernAnalyzer.AnalyzeAndCorrectAsync(strokes, handwritingFontFamilyList);
|
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(strokes, handwritingFontFamilyList);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -171,19 +179,19 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WinRT 手写体识别(需 64 位进程、Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。
|
/// WinRT 手写体识别(需 Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
public Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode)
|
ShapeRecognitionEngineMode mode)
|
||||||
{
|
{
|
||||||
|
EnsureInitialized();
|
||||||
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
||||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Environment.Is64BitProcess
|
if (!ShapeRecognitionRouter.ResolveUseWinRt(mode)
|
||||||
|| !ShapeRecognitionRouter.ResolveUseWinRt(mode)
|
|
||||||
|| !WinRtHandwritingRecognizer.IsApiAvailable)
|
|| !WinRtHandwritingRecognizer.IsApiAvailable)
|
||||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||||
|
|
||||||
@@ -208,15 +216,16 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
public string GetSystemInfo()
|
public string GetSystemInfo()
|
||||||
{
|
{
|
||||||
return _isModernSystemAvailable
|
if (_isModernSystemAvailable)
|
||||||
? $"现代化64位墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}"
|
return $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}";
|
||||||
: $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}";
|
if (IpcIACoreClient.Instance.IsAvailable)
|
||||||
|
return $"传统墨迹识别系统 (IACore via IPC) - 进程架构: {Environment.Is64BitProcess}";
|
||||||
|
return $"传统墨迹识别系统 (IACore 本地) - 进程架构: {Environment.Is64BitProcess}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_modernProcessor?.Dispose();
|
_modernProcessor?.Dispose();
|
||||||
_modernAnalyzer?.Dispose();
|
|
||||||
_isInitialized = false;
|
_isInitialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,20 +247,4 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class ModernInkAnalyzer : IDisposable
|
|
||||||
{
|
|
||||||
public Task<StrokeCollection> AnalyzeAndCorrectAsync(
|
|
||||||
StrokeCollection strokes,
|
|
||||||
string handwritingFontFamilyList)
|
|
||||||
{
|
|
||||||
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
|
|
||||||
strokes,
|
|
||||||
handwritingFontFamilyList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,15 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 墨迹形状/手写识别的对外门面。
|
||||||
|
/// IACore 路径通过 IPC 调用 x86 辅助进程;WinRT 路径在主进程内直接调用。
|
||||||
|
/// 主进程 (.NET 6 x64) 不再直接引用 IAWinFX 类型。
|
||||||
|
/// </summary>
|
||||||
public class InkRecognizeHelper
|
public class InkRecognizeHelper
|
||||||
{
|
{
|
||||||
/// <summary>IACore / IAWinFX 形状识别(典型用于 32 位进程)。</summary>
|
|
||||||
public static ShapeRecognizeResult RecognizeShapeIACore(StrokeCollection strokes)
|
|
||||||
{
|
|
||||||
if (strokes == null || strokes.Count == 0)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
var analyzer = new InkAnalyzer();
|
|
||||||
analyzer.AddStrokes(strokes);
|
|
||||||
analyzer.SetStrokesType(strokes, StrokeType.Drawing);
|
|
||||||
|
|
||||||
AnalysisAlternate analysisAlternate = null;
|
|
||||||
int strokesCount = strokes.Count;
|
|
||||||
var sfsaf = analyzer.Analyze();
|
|
||||||
if (sfsaf.Successful)
|
|
||||||
{
|
|
||||||
var alternates = analyzer.GetAlternates();
|
|
||||||
if (alternates.Count > 0)
|
|
||||||
{
|
|
||||||
while (strokesCount >= 2)
|
|
||||||
{
|
|
||||||
var alt0 = alternates[0];
|
|
||||||
if (alt0?.AlternateNodes == null || alt0.AlternateNodes.Count == 0)
|
|
||||||
break;
|
|
||||||
var drawNode = alt0.AlternateNodes[0] as InkDrawingNode;
|
|
||||||
if (drawNode == null)
|
|
||||||
break;
|
|
||||||
var shapeOk = IsContainShapeType(drawNode.GetShapeName());
|
|
||||||
if (alt0.Strokes.Contains(strokes.Last()) && shapeOk)
|
|
||||||
break;
|
|
||||||
analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]);
|
|
||||||
strokesCount--;
|
|
||||||
sfsaf = analyzer.Analyze();
|
|
||||||
if (sfsaf.Successful)
|
|
||||||
alternates = analyzer.GetAlternates();
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
if (alternates.Count == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (alternates.Count > 0)
|
|
||||||
{
|
|
||||||
var altFinal = alternates[0];
|
|
||||||
if (altFinal?.AlternateNodes != null && altFinal.AlternateNodes.Count > 0)
|
|
||||||
analysisAlternate = altFinal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
analyzer.Dispose();
|
|
||||||
|
|
||||||
if (analysisAlternate != null && analysisAlternate.AlternateNodes != null && analysisAlternate.AlternateNodes.Count > 0)
|
|
||||||
{
|
|
||||||
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
|
||||||
if (node == null)
|
|
||||||
return default;
|
|
||||||
return new ShapeRecognizeResult(node.Centroid, node.HotPoints, analysisAlternate, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>兼容旧调用:等价于 <see cref="RecognizeShapeIACore"/>。</summary>
|
|
||||||
public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes) =>
|
|
||||||
RecognizeShapeIACore(strokes);
|
|
||||||
|
|
||||||
/// <summary>按设置选择 WinRT(<see cref="InkRecognitionManager"/>)或 IACore;WinRT 请用 <see cref="RecognizeShapeUnifiedAsync"/>。</summary>
|
|
||||||
public static InkShapeRecognitionResult RecognizeShapeUnified(
|
public static InkShapeRecognitionResult RecognizeShapeUnified(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode)
|
ShapeRecognitionEngineMode mode)
|
||||||
@@ -84,11 +20,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||||
return InkShapeRecognitionResult.Empty;
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
var legacy = RecognizeShapeIACore(strokes);
|
return IpcIACoreClient.Instance.Recognize(strokes);
|
||||||
return FromIACoreOrEmpty(legacy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>与 CE 反编译版 <c>InkRecognitionManager.RecognizeShapeAsync</c> 对齐的统一入口。</summary>
|
|
||||||
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
|
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode)
|
ShapeRecognitionEngineMode mode)
|
||||||
@@ -103,14 +37,15 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = InkRecognitionManager.Instance;
|
|
||||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||||
{
|
{
|
||||||
WinRtInkShapeRecognizer.Warmup();
|
WinRtInkShapeRecognizer.Warmup();
|
||||||
WinRtHandwritingRecognizer.Warmup();
|
WinRtHandwritingRecognizer.Warmup();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
RecognizeShapeIACore(new StrokeCollection());
|
{
|
||||||
|
IpcIACoreClient.Instance.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -118,13 +53,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>WinRT 手写识别(64 位 + Windows 10+)。</summary>
|
|
||||||
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
|
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode) =>
|
ShapeRecognitionEngineMode mode) =>
|
||||||
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
|
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
|
||||||
|
|
||||||
/// <summary>WinRT 下将识别成功的词替换为手写体字形墨迹;是否应用由设置「WinRT 识别转手写体字形」控制。</summary>
|
|
||||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode) =>
|
ShapeRecognitionEngineMode mode) =>
|
||||||
@@ -134,7 +67,6 @@ namespace Ink_Canvas.Helpers
|
|||||||
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
|
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
|
||||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||||
|
|
||||||
/// <summary>显式指定是否应用手写体字形替换(忽略开关);字体仍从设置读取。</summary>
|
|
||||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||||
StrokeCollection strokes,
|
StrokeCollection strokes,
|
||||||
ShapeRecognitionEngineMode mode,
|
ShapeRecognitionEngineMode mode,
|
||||||
@@ -145,47 +77,18 @@ namespace Ink_Canvas.Helpers
|
|||||||
applyHandwritingBeautify,
|
applyHandwritingBeautify,
|
||||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||||
|
|
||||||
internal static InkShapeRecognitionResult FromIACoreOrEmpty(ShapeRecognizeResult legacy)
|
|
||||||
{
|
|
||||||
if (legacy?.InkDrawingNode == null)
|
|
||||||
return InkShapeRecognitionResult.Empty;
|
|
||||||
|
|
||||||
var node = legacy.InkDrawingNode;
|
|
||||||
var shape = node.GetShape();
|
|
||||||
var hot = ClonePointCollection(node.HotPoints);
|
|
||||||
return new InkShapeRecognitionResult(
|
|
||||||
node.GetShapeName(),
|
|
||||||
legacy.Centroid,
|
|
||||||
hot,
|
|
||||||
shape.Width,
|
|
||||||
shape.Height,
|
|
||||||
node.Strokes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PointCollection ClonePointCollection(PointCollection src)
|
|
||||||
{
|
|
||||||
var dst = new PointCollection();
|
|
||||||
if (src == null) return dst;
|
|
||||||
foreach (System.Windows.Point p in src)
|
|
||||||
dst.Add(p);
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsContainShapeType(string name)
|
public static bool IsContainShapeType(string name)
|
||||||
{
|
{
|
||||||
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
if (string.IsNullOrEmpty(name))
|
||||||
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
return false;
|
||||||
name.Contains("Parallelogram") || name.Contains("Square")
|
|
||||||
|| name.Contains("Ellipse"))
|
return name.Contains("Triangle") || name.Contains("Circle") ||
|
||||||
{
|
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||||
return true;
|
name.Contains("Parallelogram") || name.Contains("Square") ||
|
||||||
}
|
name.Contains("Ellipse");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Recognizer 的实现
|
|
||||||
|
|
||||||
public enum RecognizeLanguage
|
public enum RecognizeLanguage
|
||||||
{
|
{
|
||||||
SimplifiedChinese = 0x0804,
|
SimplifiedChinese = 0x0804,
|
||||||
@@ -193,127 +96,17 @@ namespace Ink_Canvas.Helpers
|
|||||||
English = 0x0809
|
English = 0x0809
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ShapeRecognizeResult
|
|
||||||
{
|
|
||||||
public ShapeRecognizeResult(Point centroid, PointCollection hotPoints, AnalysisAlternate analysisAlternate, InkDrawingNode node)
|
|
||||||
{
|
|
||||||
Centroid = centroid;
|
|
||||||
HotPoints = hotPoints;
|
|
||||||
AnalysisAlternate = analysisAlternate;
|
|
||||||
InkDrawingNode = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnalysisAlternate AnalysisAlternate { get; }
|
|
||||||
|
|
||||||
public Point Centroid { get; set; }
|
|
||||||
|
|
||||||
public PointCollection HotPoints { get; }
|
|
||||||
|
|
||||||
public InkDrawingNode InkDrawingNode { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 图形识别类
|
|
||||||
/// </summary>
|
|
||||||
//public class ShapeRecogniser
|
|
||||||
//{
|
|
||||||
// public InkAnalyzer _inkAnalyzer = null;
|
|
||||||
|
|
||||||
// private ShapeRecogniser()
|
|
||||||
// {
|
|
||||||
// this._inkAnalyzer = new InkAnalyzer
|
|
||||||
// {
|
|
||||||
// AnalysisModes = AnalysisModes.AutomaticReconciliationEnabled
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 根据笔迹集合返回图形名称字符串
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="strokeCollection"></param>
|
|
||||||
// /// <returns></returns>
|
|
||||||
// public InkDrawingNode Recognition(StrokeCollection strokeCollection)
|
|
||||||
// {
|
|
||||||
// if (strokeCollection == null)
|
|
||||||
// {
|
|
||||||
// //MessageBox.Show("dddddd");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// InkDrawingNode result = null;
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// this._inkAnalyzer.AddStrokes(strokeCollection);
|
|
||||||
// if (this._inkAnalyzer.Analyze().Successful)
|
|
||||||
// {
|
|
||||||
// result = _internalAnalyzer(this._inkAnalyzer);
|
|
||||||
// this._inkAnalyzer.RemoveStrokes(strokeCollection);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (System.Exception ex)
|
|
||||||
// {
|
|
||||||
// //result = ex.Message;
|
|
||||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// 实现笔迹的分析,返回图形对应的字符串
|
|
||||||
// /// 你在实际的应用中根据返回的字符串来生成对应的Shape
|
|
||||||
// /// </summary>
|
|
||||||
// /// <param name="ink"></param>
|
|
||||||
// /// <returns></returns>
|
|
||||||
// private InkDrawingNode _internalAnalyzer(InkAnalyzer ink)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// ContextNodeCollection nodecollections = ink.FindNodesOfType(ContextNodeType.InkDrawing);
|
|
||||||
// foreach (ContextNode node in nodecollections)
|
|
||||||
// {
|
|
||||||
// InkDrawingNode drawingNode = node as InkDrawingNode;
|
|
||||||
// if (drawingNode != null)
|
|
||||||
// {
|
|
||||||
// return drawingNode;//.GetShapeName();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (System.Exception ex)
|
|
||||||
// {
|
|
||||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// private static ShapeRecogniser instance = null;
|
|
||||||
// public static ShapeRecogniser Instance
|
|
||||||
// {
|
|
||||||
// get
|
|
||||||
// {
|
|
||||||
// return instance == null ? (instance = new ShapeRecogniser()) : instance;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
//用于自动控制其他形状相对于圆的位置
|
|
||||||
|
|
||||||
public class Circle
|
public class Circle
|
||||||
{
|
{
|
||||||
public Circle(Point centroid, double r, Stroke stroke)
|
public Circle(System.Windows.Point centroid, double r, Stroke stroke)
|
||||||
{
|
{
|
||||||
Centroid = centroid;
|
Centroid = centroid;
|
||||||
R = r;
|
R = r;
|
||||||
Stroke = stroke;
|
Stroke = stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point Centroid { get; set; }
|
public System.Windows.Point Centroid { get; set; }
|
||||||
|
|
||||||
public double R { get; set; }
|
public double R { get; set; }
|
||||||
|
|
||||||
public Stroke Stroke { get; set; }
|
public Stroke Stroke { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using OSVersionExtension;
|
using OSVersionExtension;
|
||||||
using System;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
@@ -17,13 +16,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
public static class ShapeRecognitionRouter
|
public static class ShapeRecognitionRouter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 自动模式:按当前进程位数选择——<c>64</c> 位进程用 WinRT,<c>32</c> 位进程(含 x86 目标在 WOW64 下运行)用 IACore。
|
/// 自动模式:在 Windows 10 及以上系统默认使用 WinRT,否则使用 IACore。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode)
|
public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode)
|
||||||
{
|
{
|
||||||
if (mode == ShapeRecognitionEngineMode.WinRT) return true;
|
if (mode == ShapeRecognitionEngineMode.WinRT) return true;
|
||||||
if (mode == ShapeRecognitionEngineMode.IACore) return false;
|
if (mode == ShapeRecognitionEngineMode.IACore) return false;
|
||||||
return Environment.Is64BitProcess;
|
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode)
|
public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode)
|
||||||
@@ -31,7 +30,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (!inkToShapeEnabled) return false;
|
if (!inkToShapeEnabled) return false;
|
||||||
if (ResolveUseWinRt(mode))
|
if (ResolveUseWinRt(mode))
|
||||||
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||||
return !Environment.Is64BitProcess;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ShapeRecognitionEngineMode FromSettingsInt(int value)
|
public static ShapeRecognitionEngineMode FromSettingsInt(int value)
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Ink;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
public sealed class IpcIACoreClient : IDisposable
|
||||||
|
{
|
||||||
|
private static IpcIACoreClient _instance;
|
||||||
|
private static readonly object _instanceLock = new object();
|
||||||
|
|
||||||
|
public static IpcIACoreClient Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
lock (_instanceLock)
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new IpcIACoreClient();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Process _helperProcess;
|
||||||
|
private readonly object _pipeLock = new object();
|
||||||
|
private bool _disposed;
|
||||||
|
private bool _available;
|
||||||
|
|
||||||
|
private static string HelperExePath =>
|
||||||
|
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "InkCanvas.IACoreHelper.exe");
|
||||||
|
|
||||||
|
private string PipeName =>
|
||||||
|
string.Format("ICC_IACoreHelper_{0}", Process.GetCurrentProcess().Id);
|
||||||
|
|
||||||
|
private IpcIACoreClient() { }
|
||||||
|
|
||||||
|
public bool Start()
|
||||||
|
{
|
||||||
|
if (_disposed) return false;
|
||||||
|
if (IsAvailable) return true;
|
||||||
|
|
||||||
|
if (!File.Exists(HelperExePath))
|
||||||
|
{
|
||||||
|
_available = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LaunchHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAvailable => _available && _helperProcess != null && !_helperProcess.HasExited;
|
||||||
|
|
||||||
|
public InkShapeRecognitionResult Recognize(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
if (strokes == null || strokes.Count == 0)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
EnsureHelperAlive();
|
||||||
|
if (!IsAvailable)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
lock (_pipeLock)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SendRecognizeRequest(strokes);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
KillHelper();
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool LaunchHelper()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
KillHelper();
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = HelperExePath,
|
||||||
|
Arguments = Process.GetCurrentProcess().Id.ToString(),
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
|
||||||
|
};
|
||||||
|
_helperProcess = Process.Start(psi);
|
||||||
|
if (_helperProcess == null)
|
||||||
|
{
|
||||||
|
_available = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_helperProcess.EnableRaisingEvents = true;
|
||||||
|
_helperProcess.Exited += OnHelperExited;
|
||||||
|
|
||||||
|
bool pipeReady = WaitForPipe(3000);
|
||||||
|
_available = pipeReady;
|
||||||
|
return pipeReady;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_available = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool WaitForPipe(int timeoutMs)
|
||||||
|
{
|
||||||
|
int elapsed = 0;
|
||||||
|
while (elapsed < timeoutMs)
|
||||||
|
{
|
||||||
|
if (_helperProcess == null || _helperProcess.HasExited)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var probe = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
probe.Connect(200);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
elapsed += 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private InkShapeRecognitionResult SendRecognizeRequest(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
client.Connect(IpcTimeoutMs);
|
||||||
|
|
||||||
|
using (var writer = new BinaryWriter(client, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||||
|
using (var reader = new BinaryReader(client, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||||
|
{
|
||||||
|
writer.Write(CmdRecognize);
|
||||||
|
writer.Write(strokes.Count);
|
||||||
|
foreach (var stroke in strokes)
|
||||||
|
{
|
||||||
|
var pts = stroke.StylusPoints;
|
||||||
|
writer.Write(pts.Count);
|
||||||
|
foreach (var pt in pts)
|
||||||
|
{
|
||||||
|
writer.Write((float)pt.X);
|
||||||
|
writer.Write((float)pt.Y);
|
||||||
|
writer.Write(pt.PressureFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
bool success = reader.ReadBoolean();
|
||||||
|
string shape = reader.ReadString();
|
||||||
|
float cx = reader.ReadSingle();
|
||||||
|
float cy = reader.ReadSingle();
|
||||||
|
float width = reader.ReadSingle();
|
||||||
|
float height = reader.ReadSingle();
|
||||||
|
|
||||||
|
int hotLen = reader.ReadInt32();
|
||||||
|
var hotPoints = new PointCollection();
|
||||||
|
for (int i = 0; i < hotLen; i++)
|
||||||
|
hotPoints.Add(new Point(reader.ReadSingle(), reader.ReadSingle()));
|
||||||
|
|
||||||
|
int idxLen = reader.ReadInt32();
|
||||||
|
var indices = new int[idxLen];
|
||||||
|
for (int i = 0; i < idxLen; i++)
|
||||||
|
indices[i] = reader.ReadInt32();
|
||||||
|
|
||||||
|
if (!success || string.IsNullOrEmpty(shape))
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
var recognized = new StrokeCollection();
|
||||||
|
foreach (int idx in indices)
|
||||||
|
if (idx >= 0 && idx < strokes.Count)
|
||||||
|
recognized.Add(strokes[idx]);
|
||||||
|
|
||||||
|
return new InkShapeRecognitionResult(
|
||||||
|
shape,
|
||||||
|
new Point(cx, cy),
|
||||||
|
hotPoints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
recognized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureHelperAlive()
|
||||||
|
{
|
||||||
|
if (!IsAvailable)
|
||||||
|
LaunchHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHelperExited(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_available = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void KillHelper()
|
||||||
|
{
|
||||||
|
if (_helperProcess == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try { _helperProcess.Exited -= OnHelperExited; } catch { }
|
||||||
|
|
||||||
|
if (!_helperProcess.HasExited)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||||
|
{
|
||||||
|
client.Connect(500);
|
||||||
|
using (var w = new BinaryWriter(client))
|
||||||
|
w.Write(CmdShutdown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
if (!_helperProcess.WaitForExit(800))
|
||||||
|
_helperProcess.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_helperProcess?.Dispose();
|
||||||
|
_helperProcess = null;
|
||||||
|
_available = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
_disposed = true;
|
||||||
|
KillHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int IpcTimeoutMs = 5000;
|
||||||
|
private const byte CmdRecognize = 0x01;
|
||||||
|
private const byte CmdShutdown = 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,6 +83,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
string logLine = string.Format("{0} [T{1}] [{2}] [{3}] {4}", DateTime.Now.ToString("O"), threadId, strLogType, callerInfo, str);
|
string logLine = string.Format("{0} [T{1}] [{2}] [{3}] {4}", DateTime.Now.ToString("O"), threadId, strLogType, callerInfo, str);
|
||||||
|
DebugConsoleManager.WriteLine(logLine);
|
||||||
ProcessProtectionManager.WithWriteAccess(file, () =>
|
ProcessProtectionManager.WithWriteAccess(file, () =>
|
||||||
{
|
{
|
||||||
using (StreamWriter sw = new StreamWriter(file, true))
|
using (StreamWriter sw = new StreamWriter(file, true))
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// .NET Core / 5+ 未提供 <see cref="Marshal.GetActiveObject"/>,通过 OLE 实现等效行为。
|
||||||
|
/// </summary>
|
||||||
|
internal static class OleActiveObject
|
||||||
|
{
|
||||||
|
[DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||||
|
private static extern int CLSIDFromProgID(string lpszProgId, out Guid lpclsid);
|
||||||
|
|
||||||
|
[DllImport("oleaut32.dll", PreserveSig = true)]
|
||||||
|
private static extern int GetActiveObject(ref Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppunk);
|
||||||
|
|
||||||
|
public static object GetActiveObject(string progId)
|
||||||
|
{
|
||||||
|
int hr = CLSIDFromProgID(progId, out Guid clsid);
|
||||||
|
Marshal.ThrowExceptionForHR(hr);
|
||||||
|
hr = GetActiveObject(ref clsid, IntPtr.Zero, out object obj);
|
||||||
|
Marshal.ThrowExceptionForHR(hr);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -460,7 +460,6 @@ namespace Ink_Canvas.Helpers
|
|||||||
_memoryStreams = new MemoryStream[_maxSlides + 2];
|
_memoryStreams = new MemoryStream[_maxSlides + 2];
|
||||||
}
|
}
|
||||||
CurrentStrokes?.Clear();
|
CurrentStrokes?.Clear();
|
||||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application");
|
var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("PowerPoint.Application");
|
||||||
|
|
||||||
if (pptApp != null && Marshal.IsComObject(pptApp))
|
if (pptApp != null && Marshal.IsComObject(pptApp))
|
||||||
{
|
{
|
||||||
@@ -298,7 +298,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application");
|
var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("kwpp.Application");
|
||||||
|
|
||||||
if (wpsApp != null && Marshal.IsComObject(wpsApp))
|
if (wpsApp != null && Marshal.IsComObject(wpsApp))
|
||||||
{
|
{
|
||||||
@@ -410,6 +410,15 @@ namespace Ink_Canvas.Helpers
|
|||||||
// COM对象类型转换失败,通常是因为对象已经被释放
|
// COM对象类型转换失败,通常是因为对象已经被释放
|
||||||
LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
|
LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||||
}
|
}
|
||||||
|
catch (System.Reflection.TargetInvocationException tie) when (tie.InnerException is InvalidComObjectException)
|
||||||
|
{
|
||||||
|
// RCW 已分离:Office Interop 内部通过反射创建 EventProvider 时抛出,是正常情况
|
||||||
|
LogHelper.WriteLogToFile("PPT COM对象RCW已分离,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||||
|
}
|
||||||
|
catch (InvalidComObjectException)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("PPT COM对象RCW已分离,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning);
|
LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning);
|
||||||
@@ -1255,7 +1264,6 @@ namespace Ink_Canvas.Helpers
|
|||||||
object slideNavigation = null;
|
object slideNavigation = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace);
|
|
||||||
|
|
||||||
if (!IsConnected || !IsInSlideShow || PPTApplication == null)
|
if (!IsConnected || !IsInSlideShow || PPTApplication == null)
|
||||||
{
|
{
|
||||||
@@ -1288,7 +1296,6 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
dynamic sn = slideNavigation;
|
dynamic sn = slideNavigation;
|
||||||
sn.Visible = true;
|
sn.Visible = true;
|
||||||
LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
[DllImport("ole32.dll")]
|
[DllImport("ole32.dll")]
|
||||||
private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
|
private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
|
||||||
|
|
||||||
|
[DllImport("ole32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int CLSIDFromProgID(string lpszProgID, out Guid pclsid);
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application");
|
var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("PowerPoint.Application");
|
||||||
if (pptApp != null && Marshal.IsComObject(pptApp))
|
if (pptApp != null && Marshal.IsComObject(pptApp))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -124,7 +127,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application");
|
var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("kwpp.Application");
|
||||||
if (wpsApp != null && Marshal.IsComObject(wpsApp))
|
if (wpsApp != null && Marshal.IsComObject(wpsApp))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -189,6 +192,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
IMoniker[] moniker = new IMoniker[1];
|
IMoniker[] moniker = new IMoniker[1];
|
||||||
IntPtr fetched = IntPtr.Zero;
|
IntPtr fetched = IntPtr.Zero;
|
||||||
|
string[] applicationMonikersFromProgIds = GetApplicationMonikersFromProgIds();
|
||||||
|
|
||||||
while (enumMoniker.Next(1, moniker, fetched) == 0)
|
while (enumMoniker.Next(1, moniker, fetched) == 0)
|
||||||
{
|
{
|
||||||
@@ -205,17 +209,32 @@ namespace Ink_Canvas.Helpers
|
|||||||
CreateBindCtx(0, out bindCtx);
|
CreateBindCtx(0, out bindCtx);
|
||||||
moniker[0].GetDisplayName(bindCtx, null, out displayName);
|
moniker[0].GetDisplayName(bindCtx, null, out displayName);
|
||||||
|
|
||||||
if (LooksLikePresentationFile(displayName) || displayName == "!{91493441-5A91-11CF-8700-00AA0060263B}")
|
bool looksLikePresentationFile = LooksLikePresentationFile(displayName);
|
||||||
|
bool isApplicationMoniker = ContainsMoniker(applicationMonikersFromProgIds, displayName);
|
||||||
|
if (!isApplicationMoniker)
|
||||||
|
{
|
||||||
|
isApplicationMoniker = IsFallbackApplicationMoniker(displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (looksLikePresentationFile || isApplicationMoniker)
|
||||||
{
|
{
|
||||||
rot.GetObject(moniker[0], out comObject);
|
rot.GetObject(moniker[0], out comObject);
|
||||||
if (comObject != null)
|
if (comObject != null)
|
||||||
{
|
{
|
||||||
try
|
if (isApplicationMoniker)
|
||||||
{
|
{
|
||||||
object appObj = comObject.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, comObject, null);
|
candidateApp = comObject;
|
||||||
candidateApp = appObj;
|
comObject = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
object appObj = comObject.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, comObject, null);
|
||||||
|
candidateApp = appObj;
|
||||||
|
}
|
||||||
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool isDuplicate = false;
|
bool isDuplicate = false;
|
||||||
@@ -398,6 +417,59 @@ namespace Ink_Canvas.Helpers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] GetApplicationMonikersFromProgIds()
|
||||||
|
{
|
||||||
|
string[] ApplicationProgIds = new[]
|
||||||
|
{
|
||||||
|
"PowerPoint.Application",
|
||||||
|
"KWPP.Application",
|
||||||
|
"Wpp.Application",
|
||||||
|
"WPP.Application",
|
||||||
|
};
|
||||||
|
|
||||||
|
List<string> monikers = new List<string>();
|
||||||
|
|
||||||
|
foreach (string progId in ApplicationProgIds)
|
||||||
|
{
|
||||||
|
Guid clsid;
|
||||||
|
if (CLSIDFromProgID(progId, out clsid) == 0 && clsid != Guid.Empty)
|
||||||
|
{
|
||||||
|
string moniker = "!" + clsid.ToString("B").ToUpperInvariant();
|
||||||
|
if (!ContainsMoniker(monikers, moniker))
|
||||||
|
{
|
||||||
|
monikers.Add(moniker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return monikers.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsMoniker(IEnumerable<string> monikers, string displayName)
|
||||||
|
{
|
||||||
|
if (monikers == null || string.IsNullOrEmpty(displayName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (string moniker in monikers)
|
||||||
|
{
|
||||||
|
if (string.Equals(displayName, moniker, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFallbackApplicationMoniker(string displayName)
|
||||||
|
{
|
||||||
|
string[] FallbackApplicationMonikers = new[]
|
||||||
|
{
|
||||||
|
"!{91493441-5A91-11CF-8700-00AA0060263B}",
|
||||||
|
"!{44720441-94BF-4940-926D-4F38FECF2A48}",
|
||||||
|
};
|
||||||
|
|
||||||
|
return ContainsMoniker(FallbackApplicationMonikers, displayName);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsSlideShowWindowActive(object sswObj)
|
public static bool IsSlideShowWindowActive(object sswObj)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
@@ -86,18 +84,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
||||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
|
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
// 只有在页数有效时才更新页码显示
|
// 同步页码到所有翻页条 + 兼容旧绑定的隐藏 placeholder
|
||||||
if (currentSlide > 0 && totalSlides > 0)
|
SetPageNumberOnAllBars(currentSlide, totalSlides);
|
||||||
{
|
|
||||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
|
||||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 页数无效时清空页码显示
|
|
||||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
|
||||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateNavigationPanelsVisibility();
|
UpdateNavigationPanelsVisibility();
|
||||||
UpdateNavigationButtonStyles();
|
UpdateNavigationButtonStyles();
|
||||||
@@ -112,6 +100,11 @@ namespace Ink_Canvas.Helpers
|
|||||||
MainWindow.MoveWindow(new WindowInteropHelper(_mainWindow).Handle, 0, 0,
|
MainWindow.MoveWindow(new WindowInteropHelper(_mainWindow).Handle, 0, 0,
|
||||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||||
|
|
||||||
|
// MoveWindow 触发的 WM_WINDOWPOSCHANGING + 重绘会打断面板的 ShowWithFadeIn 动画,
|
||||||
|
// 在窗口尺寸最终确定后重新评估一次翻页面板的可见性。
|
||||||
|
UpdateNavigationPanelsVisibility();
|
||||||
|
UpdateNavigationButtonStyles();
|
||||||
}), DispatcherPriority.ApplicationIdle);
|
}), DispatcherPriority.ApplicationIdle);
|
||||||
|
|
||||||
_mainWindow.isFullScreenApplied = true; // 标记已应用全屏处理
|
_mainWindow.isFullScreenApplied = true; // 标记已应用全屏处理
|
||||||
@@ -158,18 +151,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 只有在页数有效时才更新页码显示
|
SetPageNumberOnAllBars(currentSlide, totalSlides);
|
||||||
if (currentSlide > 0 && totalSlides > 0)
|
|
||||||
{
|
|
||||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
|
||||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 页数无效时清空页码显示
|
|
||||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
|
||||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -178,6 +160,34 @@ namespace Ink_Canvas.Helpers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetPageNumberOnAllBars(int currentSlide, int totalSlides)
|
||||||
|
{
|
||||||
|
var bars = new[]
|
||||||
|
{
|
||||||
|
_mainWindow.LeftBottomPanelForPPTNavigation,
|
||||||
|
_mainWindow.RightBottomPanelForPPTNavigation,
|
||||||
|
_mainWindow.LeftSidePanelForPPTNavigation,
|
||||||
|
_mainWindow.RightSidePanelForPPTNavigation,
|
||||||
|
};
|
||||||
|
foreach (var bar in bars)
|
||||||
|
{
|
||||||
|
if (bar == null) continue;
|
||||||
|
bar.CurrentSlide = currentSlide;
|
||||||
|
bar.TotalSlides = totalSlides;
|
||||||
|
}
|
||||||
|
// 兼容旧绑定(其它界面通过 ElementName 引用 PPTBtnPageNow / PPTBtnPageTotal)
|
||||||
|
if (currentSlide > 0 && totalSlides > 0)
|
||||||
|
{
|
||||||
|
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||||
|
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||||
|
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理PPT放映状态变化
|
/// 处理PPT放映状态变化
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -386,16 +396,17 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
// 页码按钮显示
|
// 页码按钮显示
|
||||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||||
_mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility;
|
_mainWindow.LeftSidePanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||||
_mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility;
|
_mainWindow.RightSidePanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||||
|
|
||||||
// 透明度设置 - 直接使用用户设置的透明度值
|
// 透明度
|
||||||
_mainWindow.PPTBtnLSBorder.Opacity = PPTLSButtonOpacity;
|
_mainWindow.LeftSidePanelForPPTNavigation.SetBarOpacity(PPTLSButtonOpacity);
|
||||||
_mainWindow.PPTBtnRSBorder.Opacity = PPTRSButtonOpacity;
|
_mainWindow.RightSidePanelForPPTNavigation.SetBarOpacity(PPTRSButtonOpacity);
|
||||||
|
|
||||||
// 颜色主题
|
// 颜色主题
|
||||||
bool isDarkTheme = options[2] == '2';
|
bool isDarkTheme = options[2] == '2';
|
||||||
ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true);
|
_mainWindow.LeftSidePanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||||
|
_mainWindow.RightSidePanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -414,113 +425,23 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
// 页码按钮显示
|
// 页码按钮显示
|
||||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||||
_mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility;
|
_mainWindow.LeftBottomPanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||||
_mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility;
|
_mainWindow.RightBottomPanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||||
|
|
||||||
// 透明度设置 - 直接使用用户设置的透明度值
|
// 透明度
|
||||||
_mainWindow.PPTBtnLBBorder.Opacity = PPTLBButtonOpacity;
|
_mainWindow.LeftBottomPanelForPPTNavigation.SetBarOpacity(PPTLBButtonOpacity);
|
||||||
_mainWindow.PPTBtnRBBorder.Opacity = PPTRBButtonOpacity;
|
_mainWindow.RightBottomPanelForPPTNavigation.SetBarOpacity(PPTRBButtonOpacity);
|
||||||
|
|
||||||
// 颜色主题
|
// 颜色主题
|
||||||
bool isDarkTheme = options[2] == '2';
|
bool isDarkTheme = options[2] == '2';
|
||||||
ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false);
|
_mainWindow.LeftBottomPanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||||
|
_mainWindow.RightBottomPanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error);
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+206
-768
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染保存文件名模板。支持占位符: {date} {time} {datetime} {mode} {page} {count} {type}。
|
||||||
|
/// 当模板为空、渲染结果非法或仅含分隔符时,回退到默认时间戳命名。
|
||||||
|
/// </summary>
|
||||||
|
public static class SaveFileNameHelper
|
||||||
|
{
|
||||||
|
private const string DefaultDateTime = "yyyy-MM-dd HH-mm-ss-fff";
|
||||||
|
|
||||||
|
// Windows 保留设备名(不区分大小写)。这些名称无论是否带扩展名,CreateFile 都会失败。
|
||||||
|
private static readonly HashSet<string> ReservedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"CON", "PRN", "AUX", "NUL",
|
||||||
|
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||||
|
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string Render(string template, SaveFileNameContext ctx)
|
||||||
|
{
|
||||||
|
if (ctx == null) ctx = new SaveFileNameContext();
|
||||||
|
var now = ctx.Time ?? DateTime.Now;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(template))
|
||||||
|
return now.ToString(DefaultDateTime);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string result = template
|
||||||
|
.Replace("{date}", now.ToString("yyyy-MM-dd"))
|
||||||
|
.Replace("{time}", now.ToString("HH-mm-ss"))
|
||||||
|
.Replace("{datetime}", now.ToString(DefaultDateTime))
|
||||||
|
.Replace("{mode}", ctx.Mode ?? "")
|
||||||
|
.Replace("{page}", ctx.Page?.ToString() ?? "")
|
||||||
|
.Replace("{count}", ctx.Count?.ToString() ?? "")
|
||||||
|
.Replace("{type}", ctx.Type ?? "");
|
||||||
|
|
||||||
|
result = SanitizeFileName(result);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(result) || Regex.IsMatch(result, @"^[\s\-_]+$"))
|
||||||
|
return now.ToString(DefaultDateTime);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return now.ToString(DefaultDateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeFileName(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name)) return name;
|
||||||
|
foreach (var c in Path.GetInvalidFileNameChars())
|
||||||
|
{
|
||||||
|
name = name.Replace(c, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows 禁止文件名以点号或空格结尾(会被静默截断甚至创建失败)。
|
||||||
|
name = name.Trim().TrimEnd('.', ' ');
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name)) return name;
|
||||||
|
|
||||||
|
// 保留设备名:比较时忽略扩展名,命中则加下划线前缀以规避。
|
||||||
|
var stem = Path.GetFileNameWithoutExtension(name);
|
||||||
|
if (!string.IsNullOrEmpty(stem) && ReservedNames.Contains(stem))
|
||||||
|
{
|
||||||
|
name = "_" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SaveFileNameContext
|
||||||
|
{
|
||||||
|
public DateTime? Time { get; set; }
|
||||||
|
/// <summary>"Annotation" or "BlackBoard" or "Screenshot" etc.</summary>
|
||||||
|
public string Mode { get; set; }
|
||||||
|
/// <summary>"User" or "Auto"</summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
public int? Page { get; set; }
|
||||||
|
public int? Count { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
public static class ThemeHelper
|
||||||
|
{
|
||||||
|
public static bool IsSystemThemeLight()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var registryKey = Registry.CurrentUser;
|
||||||
|
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||||
|
if (themeKey != null)
|
||||||
|
{
|
||||||
|
var value = themeKey.GetValue("AppsUseLightTheme");
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
bool result = (int)value == 1;
|
||||||
|
themeKey.Close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
themeKey.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSystemThemeLightLegacy()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var registryKey = Registry.CurrentUser;
|
||||||
|
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||||
|
if (themeKey != null)
|
||||||
|
{
|
||||||
|
int keyValue = (int)themeKey.GetValue("SystemUsesLightTheme");
|
||||||
|
themeKey.Close();
|
||||||
|
return keyValue == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ElementTheme GetEffectiveTheme(Settings settings)
|
||||||
|
{
|
||||||
|
if (settings.Appearance.Theme == 0)
|
||||||
|
return ElementTheme.Light;
|
||||||
|
if (settings.Appearance.Theme == 1)
|
||||||
|
return ElementTheme.Dark;
|
||||||
|
|
||||||
|
return IsSystemThemeLight() ? ElementTheme.Light : ElementTheme.Dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplyTheme(FrameworkElement element, Settings settings)
|
||||||
|
{
|
||||||
|
if (element == null || settings == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ThemeManager.SetRequestedTheme(element, GetEffectiveTheme(settings));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"应用主题失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplyTheme(FrameworkElement element, Settings settings, Action<string> onThemeApplied)
|
||||||
|
{
|
||||||
|
if (element == null || settings == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var theme = GetEffectiveTheme(settings);
|
||||||
|
ThemeManager.SetRequestedTheme(element, theme);
|
||||||
|
onThemeApplied?.Invoke(theme == ElementTheme.Dark ? "Dark" : "Light");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"应用主题失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// UIAccess DLL释放器
|
|
||||||
/// </summary>
|
|
||||||
public static class UIAccessDllExtractor
|
|
||||||
{
|
|
||||||
private static readonly string[] RequiredDlls = {
|
|
||||||
"UIAccessDLL_x64.dll",
|
|
||||||
"UIAccessDLL_x86.dll"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在应用启动时释放UIAccess相关DLL
|
|
||||||
/// </summary>
|
|
||||||
public static void ExtractUIAccessDlls()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
LogHelper.WriteLogToFile("开始检查并释放UIAccess相关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},可能影响UIA置顶功能", LogHelper.LogType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("UIAccess DLL释放检查完成");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"释放UIAccess 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.{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($"清理UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 通过 Winlogon 令牌模拟实现 UIAccess 提权重启。
|
||||||
|
/// 1. 找到当前会话中 winlogon.exe 的令牌,复制为模拟令牌;
|
||||||
|
/// 2. SetThreadToken 暂时模拟 winlogon(拥有 TCB 权限);
|
||||||
|
/// 3. 在自身令牌副本上 SetTokenInformation(TokenUIAccess, TRUE);
|
||||||
|
/// 4. RevertToSelf 后用 CreateProcessWithTokenW 启动新进程;
|
||||||
|
/// 5. 新进程具有 UIAccess 权限,可置顶于 UAC 提示之上。
|
||||||
|
/// </summary>
|
||||||
|
public static class UIAccessHelper
|
||||||
|
{
|
||||||
|
#region Constants
|
||||||
|
|
||||||
|
private const uint TOKEN_QUERY = 0x0008;
|
||||||
|
private const uint TOKEN_DUPLICATE = 0x0002;
|
||||||
|
private const uint TOKEN_IMPERSONATE = 0x0004;
|
||||||
|
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
|
||||||
|
private const uint TOKEN_ADJUST_DEFAULT = 0x0080;
|
||||||
|
private const uint TOKEN_ADJUST_SESSIONID = 0x0100;
|
||||||
|
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
|
||||||
|
|
||||||
|
private const int SecurityAnonymous = 0;
|
||||||
|
private const int SecurityImpersonation = 2;
|
||||||
|
private const int TokenPrimary = 1;
|
||||||
|
private const int TokenImpersonation = 2;
|
||||||
|
|
||||||
|
// TOKEN_INFORMATION_CLASS
|
||||||
|
private const int TokenSessionId = 12;
|
||||||
|
private const int TokenElevationType = 18;
|
||||||
|
private const int TokenUIAccess = 26;
|
||||||
|
|
||||||
|
// TOKEN_ELEVATION_TYPE
|
||||||
|
private const int TokenElevationTypeDefault = 1;
|
||||||
|
private const int TokenElevationTypeFull = 2;
|
||||||
|
private const int TokenElevationTypeLimited = 3;
|
||||||
|
|
||||||
|
private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
|
||||||
|
private const uint TH32CS_SNAPPROCESS = 0x00000002;
|
||||||
|
|
||||||
|
private const uint LOGON_WITH_PROFILE = 0x00000001;
|
||||||
|
private const uint CREATE_NEW_CONSOLE = 0x00000010;
|
||||||
|
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
||||||
|
|
||||||
|
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||||
|
private const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege";
|
||||||
|
|
||||||
|
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Structs
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct LUID
|
||||||
|
{
|
||||||
|
public uint LowPart;
|
||||||
|
public int HighPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct LUID_AND_ATTRIBUTES
|
||||||
|
{
|
||||||
|
public LUID Luid;
|
||||||
|
public uint Attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct TOKEN_PRIVILEGES
|
||||||
|
{
|
||||||
|
public uint PrivilegeCount;
|
||||||
|
public LUID_AND_ATTRIBUTES Privilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
private struct PROCESSENTRY32W
|
||||||
|
{
|
||||||
|
public uint dwSize;
|
||||||
|
public uint cntUsage;
|
||||||
|
public uint th32ProcessID;
|
||||||
|
public IntPtr th32DefaultHeapID;
|
||||||
|
public uint th32ModuleID;
|
||||||
|
public uint cntThreads;
|
||||||
|
public uint th32ParentProcessID;
|
||||||
|
public int pcPriClassBase;
|
||||||
|
public uint dwFlags;
|
||||||
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||||
|
public string szExeFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
private struct STARTUPINFOW
|
||||||
|
{
|
||||||
|
public uint cb;
|
||||||
|
public IntPtr lpReserved;
|
||||||
|
public IntPtr lpDesktop;
|
||||||
|
public IntPtr lpTitle;
|
||||||
|
public uint dwX, dwY, dwXSize, dwYSize;
|
||||||
|
public uint dwXCountChars, dwYCountChars;
|
||||||
|
public uint dwFillAttribute;
|
||||||
|
public uint dwFlags;
|
||||||
|
public ushort wShowWindow;
|
||||||
|
public ushort cbReserved2;
|
||||||
|
public IntPtr lpReserved2;
|
||||||
|
public IntPtr hStdInput, hStdOutput, hStdError;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct PROCESS_INFORMATION
|
||||||
|
{
|
||||||
|
public IntPtr hProcess;
|
||||||
|
public IntPtr hThread;
|
||||||
|
public uint dwProcessId;
|
||||||
|
public uint dwThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region P/Invoke
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern IntPtr GetCurrentProcess();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool CloseHandle(IntPtr hObject);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool DuplicateTokenEx(
|
||||||
|
IntPtr hExistingToken,
|
||||||
|
uint dwDesiredAccess,
|
||||||
|
IntPtr lpTokenAttributes,
|
||||||
|
int ImpersonationLevel,
|
||||||
|
int TokenType,
|
||||||
|
out IntPtr phNewToken);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool GetTokenInformation(
|
||||||
|
IntPtr TokenHandle,
|
||||||
|
int TokenInformationClass,
|
||||||
|
IntPtr TokenInformation,
|
||||||
|
uint TokenInformationLength,
|
||||||
|
out uint ReturnLength);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool SetTokenInformation(
|
||||||
|
IntPtr TokenHandle,
|
||||||
|
int TokenInformationClass,
|
||||||
|
IntPtr TokenInformation,
|
||||||
|
uint TokenInformationLength);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool SetThreadToken(IntPtr Thread, IntPtr Token);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool RevertToSelf();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool Process32FirstW(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool Process32NextW(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool LookupPrivilegeValueW(string lpSystemName, string lpName, out LUID lpLuid);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool AdjustTokenPrivileges(
|
||||||
|
IntPtr TokenHandle,
|
||||||
|
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
|
||||||
|
ref TOKEN_PRIVILEGES NewState,
|
||||||
|
uint BufferLength,
|
||||||
|
IntPtr PreviousState,
|
||||||
|
IntPtr ReturnLength);
|
||||||
|
|
||||||
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool CreateProcessWithTokenW(
|
||||||
|
IntPtr hToken,
|
||||||
|
uint dwLogonFlags,
|
||||||
|
string lpApplicationName,
|
||||||
|
StringBuilder lpCommandLine,
|
||||||
|
uint dwCreationFlags,
|
||||||
|
IntPtr lpEnvironment,
|
||||||
|
string lpCurrentDirectory,
|
||||||
|
ref STARTUPINFOW lpStartupInfo,
|
||||||
|
out PROCESS_INFORMATION lpProcessInformation);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
private static extern void GetStartupInfoW(ref STARTUPINFOW lpStartupInfo);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查当前进程是否已具有 UIAccess 标志。
|
||||||
|
/// </summary>
|
||||||
|
public static bool HasUIAccess()
|
||||||
|
{
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hToken))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IntPtr buf = Marshal.AllocHGlobal(sizeof(uint));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.WriteInt32(buf, 0);
|
||||||
|
if (!GetTokenInformation(hToken, TokenUIAccess, buf, sizeof(uint), out _))
|
||||||
|
return false;
|
||||||
|
return Marshal.ReadInt32(buf) != 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(hToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 以 UIAccess 令牌重启自身。当前进程必须已经以管理员身份运行。
|
||||||
|
/// 成功时新进程已启动,调用方应立即退出当前进程。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="extraArgs">追加到新进程的额外命令行参数(例如 --skip-mutex-check)。</param>
|
||||||
|
public static bool RestartWithUIAccess(string extraArgs = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (HasUIAccess())
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("UIAccess | 当前进程已具有 UIAccess,跳过重启");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CreateUIAccessToken(out IntPtr uiaToken))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | 创建 UIAccess 令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return LaunchWithToken(uiaToken, extraArgs);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(uiaToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | RestartWithUIAccess 异常: {ex}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 以普通用户权限(非提升)重启自身。
|
||||||
|
/// 通过获取 explorer.exe / ctfmon.exe 的非特权令牌,再用 CreateProcessWithTokenW 启动新进程,
|
||||||
|
/// 避免经由 explorer.exe 中转可能产生的 UAC 提示或丢失参数问题。
|
||||||
|
/// 成功时调用方应立即退出当前进程。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="extraArgs">追加到新进程的额外命令行参数。</param>
|
||||||
|
public static bool RestartAsNormalUser(string extraArgs = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GetCurrentProcessSessionId(out uint sessionId))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | 获取当前会话 ID 失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!GetUserPrimaryToken(sessionId, out IntPtr userToken))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | 获取用户令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return LaunchWithToken(userToken, extraArgs);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(userToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | RestartAsNormalUser 异常: {ex}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Token Manipulation
|
||||||
|
|
||||||
|
private static bool CreateUIAccessToken(out IntPtr uiaToken)
|
||||||
|
{
|
||||||
|
uiaToken = IntPtr.Zero;
|
||||||
|
|
||||||
|
// 1. 获取当前进程的 session id
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hSelfQuery))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | OpenProcessToken(query) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint sessionId;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GetTokenInformation(hSelfQuery, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | GetTokenInformation(SessionId) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sessionId = (uint)Marshal.ReadInt32(sesBuf);
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hSelfQuery); }
|
||||||
|
|
||||||
|
// 2. 找到同一会话的 winlogon 模拟令牌
|
||||||
|
if (!GetWinlogonImpersonationToken(sessionId, out IntPtr winlogonToken))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("UIAccess | 未能获取 winlogon 模拟令牌(需要管理员权限)", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 3. 模拟 winlogon
|
||||||
|
if (!SetThreadToken(IntPtr.Zero, winlogonToken))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | SetThreadToken(winlogon) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 4. 复制自身令牌为主令牌
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, out IntPtr hSelfDup))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | OpenProcessToken(dup) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr dupToken;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool ok = DuplicateTokenEx(
|
||||||
|
hSelfDup,
|
||||||
|
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
||||||
|
IntPtr.Zero,
|
||||||
|
SecurityAnonymous,
|
||||||
|
TokenPrimary,
|
||||||
|
out dupToken);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | DuplicateTokenEx 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hSelfDup); }
|
||||||
|
|
||||||
|
// 5. 在副本上设置 UIAccess = TRUE
|
||||||
|
IntPtr uiBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.WriteInt32(uiBuf, 1);
|
||||||
|
if (!SetTokenInformation(dupToken, TokenUIAccess, uiBuf, sizeof(uint)))
|
||||||
|
{
|
||||||
|
int err = Marshal.GetLastWin32Error();
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | SetTokenInformation(UIAccess) 失败: {err}", LogHelper.LogType.Error);
|
||||||
|
CloseHandle(dupToken);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(uiBuf); }
|
||||||
|
|
||||||
|
uiaToken = dupToken;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RevertToSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(winlogonToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool GetWinlogonImpersonationToken(uint sessionId, out IntPtr winlogonToken)
|
||||||
|
{
|
||||||
|
winlogonToken = IntPtr.Zero;
|
||||||
|
|
||||||
|
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | CreateToolhelp32Snapshot 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
|
||||||
|
bool more = Process32FirstW(snapshot, ref pe);
|
||||||
|
|
||||||
|
while (more)
|
||||||
|
{
|
||||||
|
if (string.Equals(pe.szExeFile, "winlogon.exe", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (TryDuplicateWinlogonToken(pe.th32ProcessID, sessionId, out winlogonToken))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
more = Process32NextW(snapshot, ref pe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { CloseHandle(snapshot); }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryDuplicateWinlogonToken(uint pid, uint sessionId, out IntPtr dupToken)
|
||||||
|
{
|
||||||
|
dupToken = IntPtr.Zero;
|
||||||
|
|
||||||
|
IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||||
|
if (hProc == IntPtr.Zero) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 检查 session id 匹配
|
||||||
|
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GetTokenInformation(hToken, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||||
|
return false;
|
||||||
|
if ((uint)Marshal.ReadInt32(sesBuf) != sessionId)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||||
|
|
||||||
|
if (!DuplicateTokenEx(
|
||||||
|
hToken,
|
||||||
|
TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE,
|
||||||
|
IntPtr.Zero,
|
||||||
|
SecurityImpersonation,
|
||||||
|
TokenImpersonation,
|
||||||
|
out dupToken))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启用 SeAssignPrimaryTokenPrivilege(Inkeys 行为)
|
||||||
|
var tkp = new TOKEN_PRIVILEGES
|
||||||
|
{
|
||||||
|
PrivilegeCount = 1,
|
||||||
|
Privilege = new LUID_AND_ATTRIBUTES { Attributes = SE_PRIVILEGE_ENABLED }
|
||||||
|
};
|
||||||
|
if (LookupPrivilegeValueW(null, SE_ASSIGNPRIMARYTOKEN_NAME, out tkp.Privilege.Luid))
|
||||||
|
{
|
||||||
|
AdjustTokenPrivileges(dupToken, false, ref tkp, (uint)Marshal.SizeOf(tkp), IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hToken); }
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hProc); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool GetCurrentProcessSessionId(out uint sessionId)
|
||||||
|
{
|
||||||
|
sessionId = 0;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hSelfQuery))
|
||||||
|
return false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GetTokenInformation(hSelfQuery, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||||
|
return false;
|
||||||
|
sessionId = (uint)Marshal.ReadInt32(sesBuf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hSelfQuery); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 explorer.exe / ctfmon.exe 取得普通用户(非提升)令牌的主令牌副本,用于降权启动。
|
||||||
|
/// 仅当当前进程为管理员时才能成功。
|
||||||
|
/// </summary>
|
||||||
|
private static bool GetUserPrimaryToken(uint sessionId, out IntPtr userToken)
|
||||||
|
{
|
||||||
|
userToken = IntPtr.Zero;
|
||||||
|
|
||||||
|
string[] candidates = { "explorer.exe", "ctfmon.exe" };
|
||||||
|
foreach (var name in candidates)
|
||||||
|
{
|
||||||
|
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
|
if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero) continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
|
||||||
|
bool more = Process32FirstW(snapshot, ref pe);
|
||||||
|
while (more)
|
||||||
|
{
|
||||||
|
if (string.Equals(pe.szExeFile, name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (TryDuplicateUserPrimaryToken(pe.th32ProcessID, sessionId, out userToken))
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | 已从 {name} (PID={pe.th32ProcessID}, Session={sessionId}) 取得用户令牌");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
more = Process32NextW(snapshot, ref pe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { CloseHandle(snapshot); }
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryDuplicateUserPrimaryToken(uint pid, uint sessionId, out IntPtr dupToken)
|
||||||
|
{
|
||||||
|
dupToken = IntPtr.Zero;
|
||||||
|
|
||||||
|
IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||||
|
if (hProc == IntPtr.Zero) return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 会话隔离:拒绝来自其他登录会话(RDP / 终端服务 / 快速用户切换)的令牌,
|
||||||
|
// 否则降权后进程会落到错误用户的桌面上下文中。
|
||||||
|
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GetTokenInformation(hToken, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||||
|
return false;
|
||||||
|
if ((uint)Marshal.ReadInt32(sesBuf) != sessionId)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||||
|
|
||||||
|
// 仅接受非提升令牌(否则降权失败)
|
||||||
|
IntPtr elevBuf = Marshal.AllocHGlobal(sizeof(int));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GetTokenInformation(hToken, TokenElevationType, elevBuf, sizeof(int), out _))
|
||||||
|
return false;
|
||||||
|
int elev = Marshal.ReadInt32(elevBuf);
|
||||||
|
if (elev == TokenElevationTypeFull)
|
||||||
|
return false; // 该进程是提升令牌,跳过
|
||||||
|
}
|
||||||
|
finally { Marshal.FreeHGlobal(elevBuf); }
|
||||||
|
|
||||||
|
return DuplicateTokenEx(
|
||||||
|
hToken,
|
||||||
|
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
||||||
|
IntPtr.Zero,
|
||||||
|
SecurityAnonymous,
|
||||||
|
TokenPrimary,
|
||||||
|
out dupToken);
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hToken); }
|
||||||
|
}
|
||||||
|
finally { CloseHandle(hProc); }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Process Launch
|
||||||
|
|
||||||
|
private static bool LaunchWithToken(IntPtr token, string extraArgs)
|
||||||
|
{
|
||||||
|
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||||
|
string workDir = System.IO.Path.GetDirectoryName(exePath);
|
||||||
|
|
||||||
|
// 重建命令行:保留原始参数,追加 --skip-mutex-check 防止单实例阻塞
|
||||||
|
var cmdBuilder = new StringBuilder(32768);
|
||||||
|
cmdBuilder.Append('"').Append(exePath).Append('"');
|
||||||
|
|
||||||
|
string[] args = Environment.GetCommandLineArgs();
|
||||||
|
for (int i = 1; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
cmdBuilder.Append(' ');
|
||||||
|
AppendQuoted(cmdBuilder, args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(extraArgs))
|
||||||
|
cmdBuilder.Append(' ').Append(extraArgs);
|
||||||
|
|
||||||
|
// 防止单实例 Mutex 阻塞新进程
|
||||||
|
if (Array.IndexOf(args, "--skip-mutex-check") < 0
|
||||||
|
&& (extraArgs == null || extraArgs.IndexOf("--skip-mutex-check", StringComparison.Ordinal) < 0))
|
||||||
|
{
|
||||||
|
cmdBuilder.Append(" --skip-mutex-check");
|
||||||
|
}
|
||||||
|
|
||||||
|
var si = new STARTUPINFOW { cb = (uint)Marshal.SizeOf(typeof(STARTUPINFOW)) };
|
||||||
|
GetStartupInfoW(ref si);
|
||||||
|
|
||||||
|
bool ok = CreateProcessWithTokenW(
|
||||||
|
token,
|
||||||
|
LOGON_WITH_PROFILE,
|
||||||
|
null,
|
||||||
|
cmdBuilder,
|
||||||
|
CREATE_UNICODE_ENVIRONMENT,
|
||||||
|
IntPtr.Zero,
|
||||||
|
workDir,
|
||||||
|
ref si,
|
||||||
|
out PROCESS_INFORMATION pi);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
int err = Marshal.GetLastWin32Error();
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | CreateProcessWithTokenW 失败: {err}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
LogHelper.WriteLogToFile($"UIAccess | 已使用 UIAccess 令牌启动新进程 (PID={pi.dwProcessId})");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendQuoted(StringBuilder sb, string arg)
|
||||||
|
{
|
||||||
|
if (arg == null) { sb.Append("\"\""); return; }
|
||||||
|
|
||||||
|
bool needQuote = arg.Length == 0 || arg.IndexOfAny(new[] { ' ', '\t', '"' }) >= 0;
|
||||||
|
if (!needQuote) { sb.Append(arg); return; }
|
||||||
|
|
||||||
|
sb.Append('"');
|
||||||
|
int backslashes = 0;
|
||||||
|
foreach (char c in arg)
|
||||||
|
{
|
||||||
|
if (c == '\\') { backslashes++; continue; }
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
sb.Append('\\', backslashes * 2 + 1);
|
||||||
|
sb.Append('"');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append('\\', backslashes);
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
backslashes = 0;
|
||||||
|
}
|
||||||
|
sb.Append('\\', backslashes * 2);
|
||||||
|
sb.Append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,31 +29,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
public static bool IsApiAvailable =>
|
public static bool IsApiAvailable =>
|
||||||
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动阶段不再预热线程内 WinRT 手写管线。历史上曾用 <see cref="WinRtInkShapeRecognizer.CreateMinimalWarmupStrokeCollection"/> 跑全链路,
|
||||||
|
/// 会显著拖慢启动;与更早的「空 <see cref="StrokeCollection"/>」一样,此处不再在 Idle 上做任何工作。
|
||||||
|
/// 首次真正需要手写识别时由 <see cref="RecognizeHandwritingAsync"/> 承担冷启动成本。
|
||||||
|
/// </summary>
|
||||||
public static void Warmup()
|
public static void Warmup()
|
||||||
{
|
{
|
||||||
if (!IsApiAvailable || !Environment.Is64BitProcess) return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var d = Application.Current?.Dispatcher;
|
|
||||||
if (d == null) return;
|
|
||||||
d.BeginInvoke(new Action(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await RecognizeHandwritingAsync(
|
|
||||||
WinRtInkShapeRecognizer.CreateMinimalWarmupStrokeCollection(),
|
|
||||||
verboseTrace: false).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -740,64 +722,60 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
var m = new Matrix(scale, 0, 0, scale, tx, ty);
|
var m = new Matrix(scale, 0, 0, scale, tx, ty);
|
||||||
geom.Transform = new MatrixTransform(m);
|
geom.Transform = new MatrixTransform(m);
|
||||||
return StrokesFromOutlinedGeometry(geom, templateDa, 0.35);
|
|
||||||
|
var filled = FilledGlyphStroke.TryCreate(geom, templateDa);
|
||||||
|
if (filled == null)
|
||||||
|
return list;
|
||||||
|
|
||||||
|
list.Add(filled);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 把字形几何作为「实心填充」绘制的笔画。仍是 WPF <see cref="Stroke"/>,可被 InkCanvas 选择/移动/删除,
|
||||||
|
/// 但渲染时直接 DrawGeometry(brush, null, geom),不再走 StylusPoints 描边路径。
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class FilledGlyphStroke : Stroke
|
||||||
|
{
|
||||||
|
private readonly Geometry _geometry;
|
||||||
|
|
||||||
|
private FilledGlyphStroke(StylusPointCollection pts, Geometry geometry, DrawingAttributes da)
|
||||||
|
: base(pts)
|
||||||
|
{
|
||||||
|
_geometry = geometry;
|
||||||
|
if (da != null)
|
||||||
|
DrawingAttributes = da.Clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Stroke> StrokesFromOutlinedGeometry(Geometry geometry, DrawingAttributes da, double tolerance)
|
public static FilledGlyphStroke TryCreate(Geometry geometry, DrawingAttributes templateDa)
|
||||||
{
|
{
|
||||||
var list = new List<Stroke>();
|
if (geometry == null || geometry.IsEmpty())
|
||||||
if (geometry == null || geometry.IsEmpty() || da == null)
|
return null;
|
||||||
return list;
|
|
||||||
|
|
||||||
Geometry outlined;
|
var b = geometry.Bounds;
|
||||||
try
|
if (b.IsEmpty || b.Width < 0.5 || b.Height < 0.5)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// StylusPoints 用 bounds 四角,保证命中测试 / 选区 / 包围盒计算正常。
|
||||||
|
var pts = new StylusPointCollection
|
||||||
{
|
{
|
||||||
outlined = geometry.GetOutlinedPathGeometry(tolerance, ToleranceType.Absolute);
|
new StylusPoint(b.Left, b.Top, 0.5f),
|
||||||
}
|
new StylusPoint(b.Right, b.Top, 0.5f),
|
||||||
catch
|
new StylusPoint(b.Right, b.Bottom, 0.5f),
|
||||||
{
|
new StylusPoint(b.Left, b.Bottom, 0.5f),
|
||||||
return list;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (outlined == null || outlined.IsEmpty())
|
return new FilledGlyphStroke(pts, geometry, templateDa);
|
||||||
return list;
|
}
|
||||||
|
|
||||||
Geometry flat;
|
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
|
||||||
try
|
{
|
||||||
{
|
if (drawingContext == null || _geometry == null)
|
||||||
flat = outlined.GetFlattenedPathGeometry(tolerance, ToleranceType.Absolute);
|
return;
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(flat is PathGeometry pg))
|
var color = drawingAttributes != null ? drawingAttributes.Color : Colors.Black;
|
||||||
return list;
|
drawingContext.DrawGeometry(new SolidColorBrush(color), null, _geometry);
|
||||||
|
|
||||||
foreach (var fig in pg.Figures)
|
|
||||||
{
|
|
||||||
var pts = new StylusPointCollection();
|
|
||||||
pts.Add(new StylusPoint(fig.StartPoint.X, fig.StartPoint.Y, 0.5f));
|
|
||||||
foreach (var seg in fig.Segments)
|
|
||||||
{
|
|
||||||
switch (seg)
|
|
||||||
{
|
|
||||||
case LineSegment ls:
|
|
||||||
pts.Add(new StylusPoint(ls.Point.X, ls.Point.Y, 0.5f));
|
|
||||||
break;
|
|
||||||
case PolyLineSegment pls:
|
|
||||||
foreach (var p in pls.Points)
|
|
||||||
pts.Add(new StylusPoint(p.X, p.Y, 0.5f));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pts.Count >= 2)
|
|
||||||
list.Add(new Stroke(pts) { DrawingAttributes = da.Clone() });
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using OSVersionExtension;
|
using OSVersionExtension;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
@@ -11,6 +12,128 @@ using WinRtInkAnalyzer = global::Windows.UI.Input.Inking.Analysis.InkAnalyzer;
|
|||||||
|
|
||||||
namespace Ink_Canvas.Helpers
|
namespace Ink_Canvas.Helpers
|
||||||
{
|
{
|
||||||
|
internal class ModernInkAnalyzer : IDisposable
|
||||||
|
{
|
||||||
|
public static readonly Guid ShapeStrokePropertyGuid = new Guid("11111111-2222-3333-4444-555555555555");
|
||||||
|
|
||||||
|
private global::Windows.UI.Input.Inking.Analysis.InkAnalyzer _internalAnalyzer;
|
||||||
|
private readonly Dictionary<Stroke, uint> _strokeIdMap = new Dictionary<Stroke, uint>();
|
||||||
|
private readonly Dictionary<uint, Stroke> _reverseIdMap = new Dictionary<uint, Stroke>();
|
||||||
|
private readonly object _syncLock = new object();
|
||||||
|
|
||||||
|
public ModernInkAnalyzer()
|
||||||
|
{
|
||||||
|
if (!WinRtInkShapeRecognizer.IsApiAvailable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_internalAnalyzer = new global::Windows.UI.Input.Inking.Analysis.InkAnalyzer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddStrokeInternal(Stroke stroke)
|
||||||
|
{
|
||||||
|
if (stroke.ContainsPropertyData(ShapeStrokePropertyGuid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var inkStroke = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(stroke);
|
||||||
|
if (inkStroke == null) return;
|
||||||
|
|
||||||
|
_internalAnalyzer.AddDataForStroke(inkStroke);
|
||||||
|
_internalAnalyzer.SetStrokeDataKind(
|
||||||
|
inkStroke.Id,
|
||||||
|
global::Windows.UI.Input.Inking.Analysis.InkAnalysisStrokeKind.Drawing);
|
||||||
|
|
||||||
|
_strokeIdMap[stroke] = inkStroke.Id;
|
||||||
|
_reverseIdMap[inkStroke.Id] = stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource _cts;
|
||||||
|
|
||||||
|
public async Task<InkShapeRecognitionResult> AnalyzeAsync(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
if (_internalAnalyzer == null || strokes == null || strokes.Count == 0)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
_cts?.Cancel();
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
var token = _cts.Token;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
_internalAnalyzer.ClearDataForAllStrokes();
|
||||||
|
_strokeIdMap.Clear();
|
||||||
|
_reverseIdMap.Clear();
|
||||||
|
|
||||||
|
foreach (var stroke in strokes)
|
||||||
|
{
|
||||||
|
AddStrokeInternal(stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_strokeIdMap.Count == 0)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
var result = await _internalAnalyzer.AnalyzeAsync().AsTask(token).ConfigureAwait(true);
|
||||||
|
|
||||||
|
if (token.IsCancellationRequested) return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
// Use the internal method from WinRtInkShapeRecognizer to find the primary drawing
|
||||||
|
var drawing = WinRtInkShapeRecognizer.FindPrimaryDrawing(_internalAnalyzer);
|
||||||
|
if (drawing == null)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
if (drawing.DrawingKind == global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Drawing)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
var name = WinRtInkShapeRecognizer.MapDrawingKindToShapeName(drawing.DrawingKind);
|
||||||
|
if (string.IsNullOrEmpty(name) || name == "Drawing")
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
var winPts = WinRtInkShapeRecognizer.CopyWinRtPoints(drawing);
|
||||||
|
var hot = WinRtInkShapeRecognizer.ToWpfPointCollection(winPts);
|
||||||
|
var c = drawing.Center;
|
||||||
|
var centroid = new SysPoint(c.X, c.Y);
|
||||||
|
WinRtInkShapeRecognizer.BoundsFromPoints(winPts, out double w, out double h);
|
||||||
|
|
||||||
|
var toRemove = new StrokeCollection();
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
foreach (var id in drawing.GetStrokeIds())
|
||||||
|
{
|
||||||
|
if (_reverseIdMap.TryGetValue(id, out var stroke))
|
||||||
|
{
|
||||||
|
toRemove.Add(stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toRemove.Count == 0)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
|
return new InkShapeRecognitionResult(name, centroid, hot, w, h, toRemove);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<StrokeCollection> AnalyzeAndCorrectAsync(
|
||||||
|
StrokeCollection strokes,
|
||||||
|
string handwritingFontFamilyList)
|
||||||
|
{
|
||||||
|
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
|
||||||
|
strokes,
|
||||||
|
handwritingFontFamilyList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_internalAnalyzer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。</summary>
|
/// <summary>基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。</summary>
|
||||||
internal static class WinRtInkShapeRecognizer
|
internal static class WinRtInkShapeRecognizer
|
||||||
{
|
{
|
||||||
@@ -124,6 +247,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var da = stroke.DrawingAttributes;
|
var da = stroke.DrawingAttributes;
|
||||||
|
if (da == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
var wda = new global::Windows.UI.Input.Inking.InkDrawingAttributes
|
var wda = new global::Windows.UI.Input.Inking.InkDrawingAttributes
|
||||||
{
|
{
|
||||||
PenTip = global::Windows.UI.Input.Inking.PenTipShape.Circle,
|
PenTip = global::Windows.UI.Input.Inking.PenTipShape.Circle,
|
||||||
@@ -147,8 +273,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
return builder.CreateStroke(points);
|
return builder.CreateStroke(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing(
|
internal static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing(
|
||||||
WinRtInkAnalyzer analyzer)
|
global::Windows.UI.Input.Inking.Analysis.InkAnalyzer analyzer)
|
||||||
{
|
{
|
||||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null;
|
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null;
|
||||||
double bestArea = -1;
|
double bestArea = -1;
|
||||||
@@ -187,7 +313,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
return w * h;
|
return w * h;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static global::Windows.Foundation.Point[] CopyWinRtPoints(
|
internal static global::Windows.Foundation.Point[] CopyWinRtPoints(
|
||||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing)
|
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing)
|
||||||
{
|
{
|
||||||
var src = drawing?.Points;
|
var src = drawing?.Points;
|
||||||
@@ -204,7 +330,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void BoundsFromPoints(
|
internal static void BoundsFromPoints(
|
||||||
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points,
|
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points,
|
||||||
out double w,
|
out double w,
|
||||||
out double h)
|
out double h)
|
||||||
@@ -229,7 +355,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
h = Math.Max(0, maxY - minY);
|
h = Math.Max(0, maxY - minY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PointCollection ToWpfPointCollection(
|
internal static PointCollection ToWpfPointCollection(
|
||||||
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points)
|
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points)
|
||||||
{
|
{
|
||||||
var hot = new PointCollection();
|
var hot = new PointCollection();
|
||||||
@@ -243,7 +369,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
return hot;
|
return hot;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string MapDrawingKindToShapeName(
|
internal static string MapDrawingKindToShapeName(
|
||||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind)
|
global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind)
|
||||||
{
|
{
|
||||||
switch (kind)
|
switch (kind)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RuntimeIdentifiers>win;win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<RootNamespace>Ink_Canvas</RootNamespace>
|
<RootNamespace>Ink_Canvas</RootNamespace>
|
||||||
<AssemblyName>InkCanvasForClass</AssemblyName>
|
<AssemblyName>InkCanvasForClass</AssemblyName>
|
||||||
<TargetFramework>net472</TargetFramework>
|
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
@@ -25,8 +27,15 @@
|
|||||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Platforms>AnyCPU;x86;x64;ARM64</Platforms>
|
<Platforms>AnyCPU;x86;x64</Platforms>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
|
<NoWarn>$(NoWarn);CA1416;NU1701;MSB3270;CS8012;NETSDK1138</NoWarn>
|
||||||
|
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||||
|
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||||
|
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
@@ -46,14 +55,12 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<Prefer32Bit>true</Prefer32Bit>
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<Prefer32Bit>true</Prefer32Bit>
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -71,14 +78,12 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
<PlatformTarget>ARM64</PlatformTarget>
|
<PlatformTarget>ARM64</PlatformTarget>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
<PlatformTarget>ARM64</PlatformTarget>
|
<PlatformTarget>ARM64</PlatformTarget>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -86,7 +91,6 @@
|
|||||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||||
<DebugType>none</DebugType>
|
<DebugType>none</DebugType>
|
||||||
<DebugSymbols>false</DebugSymbols>
|
<DebugSymbols>false</DebugSymbols>
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -94,41 +98,9 @@
|
|||||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||||
<DebugType>none</DebugType>
|
<DebugType>none</DebugType>
|
||||||
<DebugSymbols>false</DebugSymbols>
|
<DebugSymbols>false</DebugSymbols>
|
||||||
<LangVersion>7.3</LangVersion>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<Prefer32Bit>false</Prefer32Bit>
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<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" />
|
|
||||||
<Reference Include="System.Windows.Forms" />
|
|
||||||
<Reference Include="System.ComponentModel.Composition" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="System.Management" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xaml" />
|
|
||||||
<Reference Include="UIAutomationClient" />
|
|
||||||
<Reference Include="UIAutomationTypes" />
|
|
||||||
<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>
|
<ItemGroup>
|
||||||
<None Include="app.manifest" />
|
<None Include="app.manifest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -136,20 +108,23 @@
|
|||||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="gong-wpf-dragdrop" Version="4.0.0" />
|
||||||
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.0.131" />
|
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.0.131" />
|
||||||
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.10.2.1" />
|
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.10.2.1" />
|
||||||
<PackageReference Include="iNKORE.UI.WPF" Version="1.2.8" />
|
<PackageReference Include="iNKORE.UI.WPF" Version="1.2.8" />
|
||||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||||
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.2" />
|
|
||||||
<PackageReference Include="System.Runtime.WindowsRuntime" Version="4.7.0" />
|
|
||||||
<PackageReference Include="MicrosoftOfficeCore" Version="15.0.0" />
|
<PackageReference Include="MicrosoftOfficeCore" Version="15.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||||
<PackageReference Include="Microsoft.International.Converters.PinYinConverter" Version="1.0.0" />
|
<PackageReference Include="Microsoft.International.Converters.PinYinConverter" Version="1.0.0" />
|
||||||
<PackageReference Include="Sentry" Version="6.2.0" />
|
<PackageReference Include="Sentry" Version="6.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
|
||||||
|
<PackageReference Include="System.ComponentModel.Composition" Version="10.0.5" />
|
||||||
|
<PackageReference Include="System.Composition.AttributedModel" Version="10.0.5" />
|
||||||
|
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
|
||||||
|
<PackageReference Include="System.Management" Version="10.0.5" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="NHotkey.Wpf" Version="4.0.0" />
|
<PackageReference Include="NHotkey.Wpf" Version="4.0.0" />
|
||||||
<PackageReference Include="OSVersionExt" Version="4.1.0" />
|
<PackageReference Include="OSVersionExt" Version="4.1.0" />
|
||||||
@@ -157,7 +132,17 @@
|
|||||||
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
||||||
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
|
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
|
||||||
<PackageReference Include="AForge.Math" Version="2.2.5" />
|
<PackageReference Include="AForge.Math" Version="2.2.5" />
|
||||||
|
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||||
<PackageReference Include="WebDav.Client" Version="2.9.0" />
|
<PackageReference Include="WebDav.Client" Version="2.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" />
|
||||||
|
<ProjectReference Include="..\InkCanvas.Controls\InkCanvas.Controls.csproj" />
|
||||||
|
<ProjectReference Include="..\InkCanvas.IACoreHelper\InkCanvas.IACoreHelper.csproj">
|
||||||
|
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||||
|
<Private>false</Private>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
|
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
|
||||||
<COMReference Include="IWshRuntimeLibrary">
|
<COMReference Include="IWshRuntimeLibrary">
|
||||||
@@ -199,8 +184,6 @@
|
|||||||
<EmbeddedResource Include="Resources\IACore\IACore.dll" />
|
<EmbeddedResource Include="Resources\IACore\IACore.dll" />
|
||||||
<EmbeddedResource Include="Resources\IACore\IALoader.dll" />
|
<EmbeddedResource Include="Resources\IACore\IALoader.dll" />
|
||||||
<EmbeddedResource Include="Resources\IACore\IAWinFX.dll" />
|
<EmbeddedResource Include="Resources\IACore\IAWinFX.dll" />
|
||||||
<EmbeddedResource Include="UIAccessDLL_x64.dll" />
|
|
||||||
<EmbeddedResource Include="UIAccessDLL_x86.dll" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Resources\Cursors\Cursor.cur" />
|
<Resource Include="Resources\Cursors\Cursor.cur" />
|
||||||
@@ -631,6 +614,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Resources\Fonts\LXGWWenKaiTC-Regular.ttf" />
|
<Resource Include="Resources\Fonts\LXGWWenKaiTC-Regular.ttf" />
|
||||||
|
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Black.ttf" />
|
||||||
|
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Bold.ttf" />
|
||||||
|
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Light.ttf" />
|
||||||
|
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Medium.ttf" />
|
||||||
|
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Regular.ttf" />
|
||||||
|
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Thin.ttf" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="Resources\Icons-png\HiteAnnotation.png" />
|
<Resource Include="Resources\Icons-png\HiteAnnotation.png" />
|
||||||
@@ -675,4 +664,11 @@
|
|||||||
<Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')">
|
<Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')">
|
||||||
<Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" />
|
<Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<Target Name="CopyIACoreHelper" AfterTargets="Build">
|
||||||
|
<ItemGroup>
|
||||||
|
<IACoreHelperFiles Include="$(MSBuildProjectDirectory)\..\InkCanvas.IACoreHelper\bin\$(Configuration)\*.*" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Copy SourceFiles="@(IACoreHelperFiles)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" Condition="'@(IACoreHelperFiles)' != ''" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
+1150
-8556
File diff suppressed because it is too large
Load Diff
+318
-1689
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
@@ -64,7 +63,7 @@ namespace Ink_Canvas
|
|||||||
/// 处理折叠浮动栏的鼠标点击事件。
|
/// 处理折叠浮动栏的鼠标点击事件。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">事件发送者。</param>
|
/// <param name="sender">事件发送者。</param>
|
||||||
/// <param name="e">鼠标按钮事件参数。</param>
|
/// <param name="e">路由事件参数。</param>
|
||||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
await FoldFloatingBar(sender);
|
await FoldFloatingBar(sender);
|
||||||
@@ -91,19 +90,6 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public async Task FoldFloatingBar(object sender, bool isAutoFoldCommand = false)
|
public async Task FoldFloatingBar(object sender, bool isAutoFoldCommand = false)
|
||||||
{
|
{
|
||||||
var isShouldRejectAction = false;
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isShouldRejectAction)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloatingBarIcons_MouseUp_New(sender);
|
// FloatingBarIcons_MouseUp_New(sender);
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
@@ -338,7 +324,7 @@ namespace Ink_Canvas
|
|||||||
/// 处理展开浮动栏的鼠标点击事件。
|
/// 处理展开浮动栏的鼠标点击事件。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">事件发送者。</param>
|
/// <param name="sender">事件发送者。</param>
|
||||||
/// <param name="e">鼠标按钮事件参数。</param>
|
/// <param name="e">路由事件参数。</param>
|
||||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
await UnFoldFloatingBar(sender);
|
await UnFoldFloatingBar(sender);
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
using IWshRuntimeLibrary;
|
|
||||||
using System;
|
|
||||||
using System.Windows;
|
|
||||||
using Application = System.Windows.Forms.Application;
|
|
||||||
using File = System.IO.File;
|
|
||||||
|
|
||||||
namespace Ink_Canvas
|
|
||||||
{
|
|
||||||
public partial class MainWindow : Window
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 创建开机自启动快捷方式。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exeName">可执行文件名,用于命名快捷方式。</param>
|
|
||||||
/// <returns>创建成功返回true,失败返回false。</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// 操作包括:
|
|
||||||
/// 1. 创建Windows Shell对象
|
|
||||||
/// 2. 在启动文件夹中创建快捷方式
|
|
||||||
/// 3. 设置快捷方式的目标路径为当前可执行文件路径
|
|
||||||
/// 4. 设置工作目录为当前目录
|
|
||||||
/// 5. 设置窗口样式为普通窗口
|
|
||||||
/// 6. 设置快捷方式描述
|
|
||||||
/// 7. 保存快捷方式
|
|
||||||
/// 8. 捕获可能的异常,确保方法不会因异常而崩溃
|
|
||||||
/// </remarks>
|
|
||||||
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 = Application.ExecutablePath;
|
|
||||||
//应用程序的工作目录
|
|
||||||
//当用户没有指定一个具体的目录时,快捷方式的目标应用程序将使用该属性所指定的目录来装载或保存文件。
|
|
||||||
shortcut.WorkingDirectory = Environment.CurrentDirectory;
|
|
||||||
//目标应用程序窗口类型(1.Normal window普通窗口,3.Maximized最大化窗口,7.Minimized最小化)
|
|
||||||
shortcut.WindowStyle = 1;
|
|
||||||
//快捷方式的描述
|
|
||||||
shortcut.Description = exeName + "_Ink";
|
|
||||||
//设置快捷键(如果有必要的话.)
|
|
||||||
//shortcut.Hotkey = "CTRL+ALT+D";
|
|
||||||
shortcut.Save();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除开机自启动快捷方式。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="exeName">可执行文件名,用于定位要删除的快捷方式。</param>
|
|
||||||
/// <returns>删除成功返回true,失败返回false。</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// 操作包括:
|
|
||||||
/// 1. 在启动文件夹中删除指定名称的快捷方式
|
|
||||||
/// 2. 捕获可能的异常,确保方法不会因异常而崩溃
|
|
||||||
/// </remarks>
|
|
||||||
public static bool StartAutomaticallyDel(string exeName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName +
|
|
||||||
".lnk");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception) { }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Modern;
|
using iNKORE.UI.WPF.Modern;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System;
|
using System;
|
||||||
@@ -14,19 +15,18 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
/// <summary>
|
private const string ThemeLight = "Light";
|
||||||
/// 浮动栏前景色,根据当前主题动态更新。
|
private const string ThemeDark = "Dark";
|
||||||
/// </summary>
|
private const string LightThemePath = "Resources/Styles/Light.xaml";
|
||||||
|
private const string DarkThemePath = "Resources/Styles/Dark.xaml";
|
||||||
|
private const string DrawShapeImagePath = "Resources/DrawShapeImageDictionary.xaml";
|
||||||
|
private const string SeewoImagePath = "Resources/SeewoImageDictionary.xaml";
|
||||||
|
private const string IconImagePath = "Resources/IconImageDictionary.xaml";
|
||||||
|
|
||||||
private Color FloatBarForegroundColor;
|
private Color FloatBarForegroundColor;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用并切换到指定的主题("Light" 或 "Dark"),更新主题资源并刷新相关 UI 元素以反映主题变化。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="theme">主题标识,支持 "Light" 或 "Dark"(区分大小写)。</param>
|
|
||||||
/// <param name="autoSwitchIcon">若为 true,则根据主题自动切换并保存浮动工具栏的图标设置。</param>
|
|
||||||
private void SetTheme(string theme, bool autoSwitchIcon = false)
|
private void SetTheme(string theme, bool autoSwitchIcon = false)
|
||||||
{
|
{
|
||||||
// 清理现有的主题资源
|
|
||||||
var resourcesToRemove = new List<ResourceDictionary>();
|
var resourcesToRemove = new List<ResourceDictionary>();
|
||||||
foreach (var dict in Application.Current.Resources.MergedDictionaries)
|
foreach (var dict in Application.Current.Resources.MergedDictionaries)
|
||||||
{
|
{
|
||||||
@@ -43,195 +43,85 @@ namespace Ink_Canvas
|
|||||||
Application.Current.Resources.MergedDictionaries.Remove(dict);
|
Application.Current.Resources.MergedDictionaries.Remove(dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme == "Light")
|
var isLightTheme = theme == ThemeLight;
|
||||||
|
var themePath = isLightTheme ? LightThemePath : DarkThemePath;
|
||||||
|
var elementTheme = isLightTheme ? ElementTheme.Light : ElementTheme.Dark;
|
||||||
|
|
||||||
|
var rd1 = new ResourceDictionary { Source = new Uri(themePath, UriKind.Relative) };
|
||||||
|
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
// 先加载主题
|
await Task.Delay(100);
|
||||||
var rd1 = new ResourceDictionary
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
Source = new Uri("Resources/Styles/Light.xaml", UriKind.Relative)
|
LoadImageResourceDictionary(DrawShapeImagePath);
|
||||||
};
|
LoadImageResourceDictionary(SeewoImagePath);
|
||||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
LoadImageResourceDictionary(IconImagePath);
|
||||||
|
|
||||||
// 异步加载图形资源,避免阻塞启动
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
|
||||||
|
|
||||||
var rd4 = new ResourceDictionary
|
|
||||||
{
|
|
||||||
Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative)
|
|
||||||
};
|
|
||||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Light);
|
ThemeManager.SetRequestedTheme(window, elementTheme);
|
||||||
|
|
||||||
InitializeFloatBarForegroundColor();
|
InitializeFloatBarForegroundColor();
|
||||||
|
RefreshQuickPanelIcons();
|
||||||
|
RefreshStrokeSelectionIcons();
|
||||||
|
RefreshImageSelectionIcons();
|
||||||
|
RefreshGestureButtonIcon();
|
||||||
|
RefreshFloatingBarHighlightColors();
|
||||||
|
|
||||||
// 刷新快速面板图标
|
if (autoSwitchIcon)
|
||||||
RefreshQuickPanelIcons();
|
|
||||||
|
|
||||||
// 刷新墨迹选中栏图标
|
|
||||||
RefreshStrokeSelectionIcons();
|
|
||||||
|
|
||||||
// 刷新图片选中栏图标
|
|
||||||
RefreshImageSelectionIcons();
|
|
||||||
|
|
||||||
// 刷新手势按钮图标
|
|
||||||
RefreshGestureButtonIcon();
|
|
||||||
|
|
||||||
RefreshFloatingBarHighlightColors();
|
|
||||||
|
|
||||||
if (autoSwitchIcon)
|
|
||||||
{
|
|
||||||
AutoSwitchFloatingBarIconForTheme("Light");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 强制刷新UI
|
|
||||||
window.InvalidateVisual();
|
|
||||||
|
|
||||||
// 通知其他窗口刷新主题
|
|
||||||
RefreshOtherWindowsTheme();
|
|
||||||
}
|
|
||||||
else if (theme == "Dark")
|
|
||||||
{
|
{
|
||||||
// 先加载主题
|
AutoSwitchFloatingBarIconForTheme(theme);
|
||||||
var rd1 = new ResourceDictionary { Source = new Uri("Resources/Styles/Dark.xaml", UriKind.Relative) };
|
|
||||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
|
||||||
|
|
||||||
// 异步加载图形资源,避免阻塞启动
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
|
||||||
|
|
||||||
var rd4 = new ResourceDictionary
|
|
||||||
{
|
|
||||||
Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative)
|
|
||||||
};
|
|
||||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Dark);
|
|
||||||
|
|
||||||
InitializeFloatBarForegroundColor();
|
|
||||||
|
|
||||||
// 刷新快速面板图标
|
|
||||||
RefreshQuickPanelIcons();
|
|
||||||
|
|
||||||
// 刷新墨迹选中栏图标
|
|
||||||
RefreshStrokeSelectionIcons();
|
|
||||||
|
|
||||||
// 刷新图片选中栏图标
|
|
||||||
RefreshImageSelectionIcons();
|
|
||||||
|
|
||||||
// 刷新手势按钮图标
|
|
||||||
RefreshGestureButtonIcon();
|
|
||||||
|
|
||||||
RefreshFloatingBarHighlightColors();
|
|
||||||
|
|
||||||
if (autoSwitchIcon)
|
|
||||||
{
|
|
||||||
AutoSwitchFloatingBarIconForTheme("Dark");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 强制刷新UI
|
|
||||||
window.InvalidateVisual();
|
|
||||||
|
|
||||||
// 通知其他窗口刷新主题
|
|
||||||
RefreshOtherWindowsTheme();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.InvalidateVisual();
|
||||||
|
RefreshOtherWindowsTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadImageResourceDictionary(string path)
|
||||||
|
{
|
||||||
|
var rd = new ResourceDictionary { Source = new Uri(path, UriKind.Relative) };
|
||||||
|
Application.Current.Resources.MergedDictionaries.Add(rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化FloatBarForegroundColor,从当前主题资源中加载颜色
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeFloatBarForegroundColor()
|
private void InitializeFloatBarForegroundColor()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
|
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
|
||||||
|
|
||||||
// 强制刷新浮动工具栏按钮颜色
|
|
||||||
RefreshFloatingBarButtonColors();
|
RefreshFloatingBarButtonColors();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// 如果无法从资源中加载,使用默认颜色
|
|
||||||
FloatBarForegroundColor = Color.FromRgb(0, 0, 0);
|
FloatBarForegroundColor = Color.FromRgb(0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新快速面板图标
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshQuickPanelIcons()
|
private void RefreshQuickPanelIcons()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (LeftUnFoldButtonQuickPanel != null)
|
LeftUnFoldButtonQuickPanel?.InvalidateVisual();
|
||||||
{
|
RightUnFoldButtonQuickPanel?.InvalidateVisual();
|
||||||
LeftUnFoldButtonQuickPanel.InvalidateVisual();
|
LeftSidePanel?.InvalidateVisual();
|
||||||
}
|
RightSidePanel?.InvalidateVisual();
|
||||||
if (RightUnFoldButtonQuickPanel != null)
|
|
||||||
{
|
|
||||||
RightUnFoldButtonQuickPanel.InvalidateVisual();
|
|
||||||
}
|
|
||||||
if (LeftSidePanel != null)
|
|
||||||
{
|
|
||||||
LeftSidePanel.InvalidateVisual();
|
|
||||||
}
|
|
||||||
if (RightSidePanel != null)
|
|
||||||
{
|
|
||||||
RightSidePanel.InvalidateVisual();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新浮动栏高光条颜色
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshFloatingBarHighlightColors()
|
private void RefreshFloatingBarHighlightColors()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (FloatingbarSelectionBG != null && FloatingbarSelectionBG.Visibility == Visibility.Visible)
|
if (FloatingbarSelectionBG != null && FloatingbarSelectionBG.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
// 根据主题设置高光颜色
|
bool isDarkTheme = IsCurrentThemeDark();
|
||||||
|
|
||||||
Color highlightBackgroundColor;
|
Color highlightBackgroundColor;
|
||||||
Color highlightBarColor;
|
Color highlightBarColor;
|
||||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
|
||||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
|
||||||
|
|
||||||
if (isDarkTheme)
|
if (isDarkTheme)
|
||||||
{
|
{
|
||||||
@@ -244,7 +134,6 @@ namespace Ink_Canvas
|
|||||||
highlightBarColor = Color.FromRgb(37, 99, 235);
|
highlightBarColor = Color.FromRgb(37, 99, 235);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置高光背景颜色
|
|
||||||
FloatingbarSelectionBG.Background = new SolidColorBrush(highlightBackgroundColor);
|
FloatingbarSelectionBG.Background = new SolidColorBrush(highlightBackgroundColor);
|
||||||
if (FloatingbarSelectionBG.Child is System.Windows.Controls.Canvas canvas && canvas.Children.Count > 0)
|
if (FloatingbarSelectionBG.Child is System.Windows.Controls.Canvas canvas && canvas.Children.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -261,73 +150,67 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private bool IsCurrentThemeDark()
|
||||||
/// 刷新浮动工具栏按钮颜色
|
{
|
||||||
/// </summary>
|
return Settings.Appearance.Theme == 1 ||
|
||||||
|
(Settings.Appearance.Theme == 2 && !ThemeHelper.IsSystemThemeLight());
|
||||||
|
}
|
||||||
|
|
||||||
private void RefreshFloatingBarButtonColors()
|
private void RefreshFloatingBarButtonColors()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 根据主题选择高光颜色
|
SymbolIconDelete.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DeleteIcon);
|
||||||
Color selectedColor;
|
ShapeDrawFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ShapesIcon);
|
||||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
SymbolIconUndo.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.UndoIcon);
|
||||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
SymbolIconRedo.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.RedoIcon);
|
||||||
|
CursorWithDelFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.CursorWithDelFloatingBarBtnIcon);
|
||||||
|
WhiteboardFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.WhiteboardFloatingBarBtnIcon);
|
||||||
|
ToolsFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ToolsFloatingBarBtnIcon);
|
||||||
|
Fold_Icon.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.FoldIcon);
|
||||||
|
|
||||||
if (isDarkTheme)
|
TimerToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.TimerIconGeometry);
|
||||||
{
|
RandomDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.RandomDrawIconGeometry);
|
||||||
selectedColor = Color.FromRgb(102, 204, 255);
|
SingleDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SingleDrawIconGeometry);
|
||||||
}
|
SaveToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SaveIconGeometry);
|
||||||
else
|
OpenToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.OpenIconGeometry);
|
||||||
{
|
ReplayToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ReplayIconGeometry);
|
||||||
selectedColor = Color.FromRgb(30, 58, 138);
|
ScreenshotToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ScreenshotIconGeometry);
|
||||||
}
|
ManualToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ManualIconGeometry);
|
||||||
|
SettingsToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SettingsIconGeometry);
|
||||||
|
|
||||||
|
BoardTimerToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.TimerIconGeometry);
|
||||||
|
BoardRandomDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.RandomDrawIconGeometry);
|
||||||
|
BoardSingleDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SingleDrawIconGeometry);
|
||||||
|
BoardSaveToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SaveIconGeometry);
|
||||||
|
BoardOpenToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.OpenIconGeometry);
|
||||||
|
BoardReplayToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ReplayIconGeometry);
|
||||||
|
BoardScreenshotToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ScreenshotIconGeometry);
|
||||||
|
BoardManualToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ManualIconGeometry);
|
||||||
|
BoardSettingsToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SettingsIconGeometry);
|
||||||
|
|
||||||
|
bool isDarkTheme = IsCurrentThemeDark();
|
||||||
|
Color selectedColor = isDarkTheme ? Color.FromRgb(102, 204, 255) : Color.FromRgb(30, 58, 138);
|
||||||
|
|
||||||
|
SetAllFloatingBarButtonsToColor(FloatBarForegroundColor);
|
||||||
|
|
||||||
// 根据当前模式设置按钮颜色
|
|
||||||
switch (_currentToolMode)
|
switch (_currentToolMode)
|
||||||
{
|
{
|
||||||
case "cursor":
|
case "cursor":
|
||||||
CursorIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
Cursor_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
break;
|
break;
|
||||||
case "pen":
|
case "pen":
|
||||||
case "color":
|
case "color":
|
||||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
Pen_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||||
PenIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
|
||||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
break;
|
break;
|
||||||
case "eraser":
|
case "eraser":
|
||||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
Eraser_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
|
||||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
break;
|
break;
|
||||||
case "eraserByStrokes":
|
case "eraserByStrokes":
|
||||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
EraserByStrokes_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
|
||||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
break;
|
break;
|
||||||
case "select":
|
case "select":
|
||||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
SymbolIconSelect.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// 默认情况,所有按钮都使用主题颜色
|
|
||||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,79 +219,46 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
void SetAllFloatingBarButtonsToColor(Color color)
|
||||||
/// 处理系统主题偏好变化事件,根据当前设置更新应用主题。
|
{
|
||||||
/// </summary>
|
var brush = new SolidColorBrush(color);
|
||||||
/// <param name="sender">事件发送者。</param>
|
Cursor_Icon.Icon.Brush = brush;
|
||||||
/// <param name="e">用户偏好变化事件参数。</param>
|
Pen_Icon.Icon.Brush = brush;
|
||||||
/// <remarks>
|
EraserByStrokes_Icon.Icon.Brush = brush;
|
||||||
/// 操作包括:
|
Eraser_Icon.Icon.Brush = brush;
|
||||||
/// 1. 根据当前主题设置(Settings.Appearance.Theme)决定使用哪种主题
|
SymbolIconSelect.Icon.Brush = brush;
|
||||||
/// 2. 如果设置为0(浅色主题),则设置为Light主题
|
ShapeDrawFloatingBarBtn.Icon.Brush = brush;
|
||||||
/// 3. 如果设置为1(深色主题),则设置为Dark主题
|
SymbolIconUndo.Icon.Brush = brush;
|
||||||
/// 4. 如果设置为2(跟随系统主题),则根据系统主题设置应用相应的主题
|
SymbolIconRedo.Icon.Brush = brush;
|
||||||
/// </remarks>
|
CursorWithDelFloatingBarBtn.Icon.Brush = brush;
|
||||||
|
WhiteboardFloatingBarBtn.Icon.Brush = brush;
|
||||||
|
ToolsFloatingBarBtn.Icon.Brush = brush;
|
||||||
|
Fold_Icon.Icon.Brush = brush;
|
||||||
|
}
|
||||||
|
|
||||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||||
{
|
{
|
||||||
switch (Settings.Appearance.Theme)
|
switch (Settings.Appearance.Theme)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
SetTheme("Light");
|
SetTheme(ThemeLight);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
SetTheme("Dark");
|
SetTheme(ThemeDark);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (IsSystemThemeLight()) SetTheme("Light");
|
// 与 IsCurrentThemeDark / GetEffectiveTheme / 浮动栏一致,统一读 AppsUseLightTheme,
|
||||||
else SetTheme("Dark");
|
// 否则 SystemUsesLightTheme 与 AppsUseLightTheme 可独立取值时主题会混搭
|
||||||
|
SetTheme(ThemeHelper.IsSystemThemeLight() ? ThemeLight : ThemeDark);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查系统主题是否为浅色主题。
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>系统主题为浅色返回true,深色返回false。</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// 操作包括:
|
|
||||||
/// 1. 从注册表中读取系统主题设置
|
|
||||||
/// 2. 检查"SystemUsesLightTheme"键的值
|
|
||||||
/// 3. 如果值为1,则表示系统使用浅色主题
|
|
||||||
/// 4. 捕获可能的异常,确保方法不会因异常而崩溃
|
|
||||||
/// </remarks>
|
|
||||||
private bool IsSystemThemeLight()
|
|
||||||
{
|
|
||||||
var light = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var registryKey = Registry.CurrentUser;
|
|
||||||
var themeKey =
|
|
||||||
registryKey.OpenSubKey("software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
|
|
||||||
var keyValue = 0;
|
|
||||||
if (themeKey != null) keyValue = (int)themeKey.GetValue("SystemUsesLightTheme");
|
|
||||||
if (keyValue == 1) light = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
|
|
||||||
return light;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 根据主题自动切换浮动栏图标
|
|
||||||
/// </summary>
|
|
||||||
private void AutoSwitchFloatingBarIconForTheme(string theme)
|
private void AutoSwitchFloatingBarIconForTheme(string theme)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (theme == "Light")
|
Settings.Appearance.FloatingBarImg = theme == ThemeLight ? 0 : 3;
|
||||||
{
|
|
||||||
Settings.Appearance.FloatingBarImg = 0;
|
|
||||||
}
|
|
||||||
else if (theme == "Dark")
|
|
||||||
{
|
|
||||||
Settings.Appearance.FloatingBarImg = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateFloatingBarIcon();
|
UpdateFloatingBarIcon();
|
||||||
UpdateFloatingBarIconComboBox();
|
UpdateFloatingBarIconComboBox();
|
||||||
}
|
}
|
||||||
@@ -417,111 +267,49 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新设置界面中的浮动栏图标选择下拉框显示
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateFloatingBarIconComboBox()
|
private void UpdateFloatingBarIconComboBox()
|
||||||
{
|
{
|
||||||
try
|
|
||||||
{
|
|
||||||
if (ComboBoxFloatingBarImg != null)
|
|
||||||
{
|
|
||||||
ComboBoxFloatingBarImg.SelectedIndex = Settings.Appearance.FloatingBarImg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新墨迹选中栏图标
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshStrokeSelectionIcons()
|
private void RefreshStrokeSelectionIcons()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (BorderStrokeSelectionControl != null)
|
if (BorderStrokeSelectionControl != null)
|
||||||
{
|
{
|
||||||
// 强制刷新墨迹选中栏的视觉状态
|
|
||||||
BorderStrokeSelectionControl.InvalidateVisual();
|
BorderStrokeSelectionControl.InvalidateVisual();
|
||||||
|
|
||||||
// 刷新墨迹选中栏内的所有图标
|
|
||||||
var viewbox = BorderStrokeSelectionControl.Child as Viewbox;
|
var viewbox = BorderStrokeSelectionControl.Child as Viewbox;
|
||||||
if (viewbox?.Child is ui.SimpleStackPanel stackPanel)
|
if (viewbox?.Child is ui.SimpleStackPanel stackPanel)
|
||||||
{
|
{
|
||||||
RefreshStrokeSelectionIconsRecursive(stackPanel);
|
RefreshIconsRecursive(stackPanel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// 忽略异常,确保主题切换不会因为图标刷新失败而中断
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 递归刷新墨迹选中栏内的图标
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshStrokeSelectionIconsRecursive(System.Windows.Controls.Panel panel)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var child in panel.Children)
|
|
||||||
{
|
|
||||||
if (child is Image image)
|
|
||||||
{
|
|
||||||
// 强制刷新图像
|
|
||||||
image.InvalidateVisual();
|
|
||||||
}
|
|
||||||
else if (child is System.Windows.Controls.Panel childPanel)
|
|
||||||
{
|
|
||||||
// 递归处理子面板
|
|
||||||
RefreshStrokeSelectionIconsRecursive(childPanel);
|
|
||||||
}
|
|
||||||
else if (child is Border border && border.Child is System.Windows.Controls.Panel borderPanel)
|
|
||||||
{
|
|
||||||
// 处理Border内的面板
|
|
||||||
RefreshStrokeSelectionIconsRecursive(borderPanel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// 忽略异常
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新图片选中栏图标
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshImageSelectionIcons()
|
private void RefreshImageSelectionIcons()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (BorderImageSelectionControl != null)
|
if (BorderImageSelectionControl != null)
|
||||||
{
|
{
|
||||||
// 强制刷新图片选中栏的视觉状态
|
|
||||||
BorderImageSelectionControl.InvalidateVisual();
|
BorderImageSelectionControl.InvalidateVisual();
|
||||||
|
|
||||||
// 刷新图片选中栏内的所有图标
|
|
||||||
var viewbox = BorderImageSelectionControl.Child as Viewbox;
|
var viewbox = BorderImageSelectionControl.Child as Viewbox;
|
||||||
if (viewbox?.Child is ui.SimpleStackPanel stackPanel)
|
if (viewbox?.Child is ui.SimpleStackPanel stackPanel)
|
||||||
{
|
{
|
||||||
RefreshImageSelectionIconsRecursive(stackPanel);
|
RefreshIconsRecursive(stackPanel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// 忽略异常,确保主题切换不会因为图标刷新失败而中断
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void RefreshIconsRecursive(System.Windows.Controls.Panel panel)
|
||||||
/// 递归刷新图片选中栏内的图标
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshImageSelectionIconsRecursive(System.Windows.Controls.Panel panel)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -529,22 +317,18 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (child is Image image)
|
if (child is Image image)
|
||||||
{
|
{
|
||||||
// 强制刷新图像
|
|
||||||
image.InvalidateVisual();
|
image.InvalidateVisual();
|
||||||
}
|
}
|
||||||
else if (child is System.Windows.Controls.Panel childPanel)
|
else if (child is System.Windows.Controls.Panel childPanel)
|
||||||
{
|
{
|
||||||
// 递归处理子面板
|
RefreshIconsRecursive(childPanel);
|
||||||
RefreshImageSelectionIconsRecursive(childPanel);
|
|
||||||
}
|
}
|
||||||
else if (child is Border border && border.Child is System.Windows.Controls.Panel borderPanel)
|
else if (child is Border border && border.Child is System.Windows.Controls.Panel borderPanel)
|
||||||
{
|
{
|
||||||
// 处理Border内的面板
|
RefreshIconsRecursive(borderPanel);
|
||||||
RefreshImageSelectionIconsRecursive(borderPanel);
|
|
||||||
}
|
}
|
||||||
else if (child is Grid grid)
|
else if (child is Grid grid)
|
||||||
{
|
{
|
||||||
// 处理Grid内的子元素
|
|
||||||
foreach (var gridChild in grid.Children)
|
foreach (var gridChild in grid.Children)
|
||||||
{
|
{
|
||||||
if (gridChild is Image gridImage)
|
if (gridChild is Image gridImage)
|
||||||
@@ -557,18 +341,13 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// 忽略异常
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新手势按钮图标
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshGestureButtonIcon()
|
private void RefreshGestureButtonIcon()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 调用手势按钮颜色和图标更新方法,该方法会根据当前主题和手势状态设置正确的图标
|
|
||||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -576,14 +355,10 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新其他窗口的主题
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshOtherWindowsTheme()
|
private void RefreshOtherWindowsTheme()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 刷新所有打开的窗口
|
|
||||||
foreach (Window window in Application.Current.Windows)
|
foreach (Window window in Application.Current.Windows)
|
||||||
{
|
{
|
||||||
if (window is CountdownTimerWindow timerWindow)
|
if (window is CountdownTimerWindow timerWindow)
|
||||||
@@ -598,22 +373,18 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
operatingGuideWindow.RefreshTheme();
|
operatingGuideWindow.RefreshTheme();
|
||||||
}
|
}
|
||||||
|
else if (window is Windows.SettingsViews.SettingsWindow settingsWindow)
|
||||||
|
{
|
||||||
|
settingsWindow.RefreshTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新计时器控件
|
TimerControl?.RefreshTheme();
|
||||||
if (TimerControl != null)
|
MinimizedTimerControl?.RefreshTheme();
|
||||||
{
|
|
||||||
TimerControl.RefreshTheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MinimizedTimerControl != null)
|
|
||||||
{
|
|
||||||
MinimizedTimerControl.RefreshTheme();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
@@ -426,7 +427,10 @@ namespace Ink_Canvas
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。
|
/// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e)
|
private void BoardBtnWhiteBoardSwitchPrevious_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
=> BtnWhiteBoardSwitchPrevious_Click(sender, e);
|
||||||
|
|
||||||
|
private void BtnWhiteBoardSwitchPrevious_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (CurrentWhiteboardIndex <= 1) return;
|
if (CurrentWhiteboardIndex <= 1) return;
|
||||||
|
|
||||||
@@ -458,7 +462,10 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">触发事件的源对象(通常为按钮)。</param>
|
/// <param name="sender">触发事件的源对象(通常为按钮)。</param>
|
||||||
/// <param name="e">事件参数。</param>
|
/// <param name="e">事件参数。</param>
|
||||||
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e)
|
private void BoardBtnWhiteBoardSwitchNext_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
=> BtnWhiteBoardSwitchNext_Click(sender, e);
|
||||||
|
|
||||||
|
private void BtnWhiteBoardSwitchNext_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (CurrentWhiteboardIndex < WhiteboardTotalCount &&
|
if (CurrentWhiteboardIndex < WhiteboardTotalCount &&
|
||||||
Settings.Automation.IsAutoSaveStrokesAtClear &&
|
Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||||
@@ -505,7 +512,10 @@ namespace Ink_Canvas
|
|||||||
/// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。
|
/// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。
|
||||||
/// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。
|
/// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void BtnWhiteBoardAdd_Click(object sender, EventArgs e)
|
private void BoardBtnWhiteBoardAdd_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
=> BtnWhiteBoardAdd_Click(sender, e);
|
||||||
|
|
||||||
|
private void BtnWhiteBoardAdd_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (WhiteboardTotalCount >= 99) return;
|
if (WhiteboardTotalCount >= 99) return;
|
||||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||||
@@ -652,8 +662,8 @@ namespace Ink_Canvas
|
|||||||
bool isMaxPage = WhiteboardTotalCount >= 99;
|
bool isMaxPage = WhiteboardTotalCount >= 99;
|
||||||
|
|
||||||
// 设置按钮文本
|
// 设置按钮文本
|
||||||
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
BtnLeftWhiteBoardSwitchNext.LabelTextBlockControl.Text = isLastPage ? "新页面" : "下一页";
|
||||||
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
BtnRightWhiteBoardSwitchNext.LabelTextBlockControl.Text = isLastPage ? "新页面" : "下一页";
|
||||||
|
|
||||||
if (isLastPage)
|
if (isLastPage)
|
||||||
{
|
{
|
||||||
@@ -670,11 +680,11 @@ namespace Ink_Canvas
|
|||||||
// 设置下一页按钮颜色
|
// 设置下一页按钮颜色
|
||||||
if (iconForegroundBrush != null)
|
if (iconForegroundBrush != null)
|
||||||
{
|
{
|
||||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
BtnLeftWhiteBoardSwitchNext.IconGeometryDrawing.Brush = iconForegroundBrush;
|
||||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
BtnRightWhiteBoardSwitchNext.IconGeometryDrawing.Brush = iconForegroundBrush;
|
||||||
}
|
}
|
||||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
BtnLeftWhiteBoardSwitchNext.LabelTextBlockControl.Opacity = 1;
|
||||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
BtnRightWhiteBoardSwitchNext.LabelTextBlockControl.Opacity = 1;
|
||||||
|
|
||||||
BtnWhiteBoardSwitchPrevious.IsEnabled = true;
|
BtnWhiteBoardSwitchPrevious.IsEnabled = true;
|
||||||
|
|
||||||
@@ -684,21 +694,21 @@ namespace Ink_Canvas
|
|||||||
if (iconForegroundBrush != null)
|
if (iconForegroundBrush != null)
|
||||||
{
|
{
|
||||||
var disabledBrush = new SolidColorBrush(Color.FromArgb(127, iconForegroundBrush.Color.R, iconForegroundBrush.Color.G, iconForegroundBrush.Color.B));
|
var disabledBrush = new SolidColorBrush(Color.FromArgb(127, iconForegroundBrush.Color.R, iconForegroundBrush.Color.G, iconForegroundBrush.Color.B));
|
||||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = disabledBrush;
|
BtnLeftWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = disabledBrush;
|
||||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = disabledBrush;
|
BtnRightWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = disabledBrush;
|
||||||
}
|
}
|
||||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
BtnLeftWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 0.5;
|
||||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
BtnRightWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 0.5;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (iconForegroundBrush != null)
|
if (iconForegroundBrush != null)
|
||||||
{
|
{
|
||||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
BtnLeftWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = iconForegroundBrush;
|
||||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
BtnRightWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = iconForegroundBrush;
|
||||||
}
|
}
|
||||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
BtnLeftWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 1;
|
||||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
BtnRightWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1;
|
BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1;
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Ink;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
|
|
||||||
namespace Ink_Canvas
|
namespace Ink_Canvas
|
||||||
{
|
{
|
||||||
@@ -29,594 +27,159 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
// 创建背景选项面板(如果不存在)
|
if (BackgroundPalette.Visibility == Visibility.Visible)
|
||||||
if (BackgroundPalette == null)
|
|
||||||
{
|
{
|
||||||
CreateBackgroundPalette();
|
AnimationsHelper.HideWithSlideAndFade(BackgroundPalette);
|
||||||
}
|
|
||||||
|
|
||||||
// 显示或隐藏背景选项面板
|
|
||||||
if (BackgroundPalette != null)
|
|
||||||
{
|
|
||||||
if (BackgroundPalette.Visibility == Visibility.Visible)
|
|
||||||
{
|
|
||||||
// 如果面板已经显示,则隐藏它
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BackgroundPalette);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 隐藏其他可能显示的面板
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(EraserSizePanel);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(PenPalette);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardPenPalette);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderDrawShape);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
|
||||||
|
|
||||||
// 显示背景选项面板
|
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BackgroundPalette);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 原有的背景切换代码
|
|
||||||
Settings.Canvas.UsingWhiteboard = !Settings.Canvas.UsingWhiteboard;
|
|
||||||
SaveSettingsToFile();
|
|
||||||
if (Settings.Canvas.UsingWhiteboard)
|
|
||||||
{
|
|
||||||
if (inkColor == 5) lastBoardInkColor = 0;
|
|
||||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
|
||||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
// 设置为白板默认背景色
|
|
||||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
|
||||||
|
|
||||||
if (currentMode == 1) // 白板模式
|
|
||||||
{
|
|
||||||
// 设置背景为默认白板背景色
|
|
||||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
|
||||||
|
|
||||||
// 更新RGB滑块的值为默认白板背景色
|
|
||||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
|
||||||
{
|
|
||||||
UpdateRGBSliders(defaultWhiteboardColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新自定义背景色为默认白板背景色
|
|
||||||
CustomBackgroundColor = defaultWhiteboardColor;
|
|
||||||
|
|
||||||
// 保存到设置
|
|
||||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
|
||||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置墨迹颜色为黑色
|
|
||||||
CheckLastColor(0);
|
|
||||||
forceEraser = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (inkColor == 0) lastBoardInkColor = 5;
|
AnimationsHelper.HideWithSlideAndFade(EraserSizePanel);
|
||||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
AnimationsHelper.HidePopupWithSlideAndFade(BorderTools);
|
||||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
AnimationsHelper.HidePopupWithSlideAndFade(BoardBorderToolsPopup);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(PenPalette);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(BoardPenPalette);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(BoardBorderDrawShape);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||||
|
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||||
|
|
||||||
// 设置为黑板默认背景色
|
LoadCustomBackgroundColor();
|
||||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
UpdateBackgroundButtonsState();
|
||||||
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BackgroundPalette);
|
||||||
if (currentMode == 1) // 黑板模式
|
|
||||||
{
|
|
||||||
// 设置背景为默认黑板背景色
|
|
||||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
|
||||||
|
|
||||||
// 更新RGB滑块的值为默认黑板背景色
|
|
||||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
|
||||||
{
|
|
||||||
UpdateRGBSliders(defaultBlackboardColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新自定义背景色为默认黑板背景色
|
|
||||||
CustomBackgroundColor = defaultBlackboardColor;
|
|
||||||
|
|
||||||
// 保存到设置
|
|
||||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
|
||||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置墨迹颜色为白色
|
|
||||||
CheckLastColor(5);
|
|
||||||
forceEraser = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckColorTheme(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void WhiteboardModeBtn_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
/// 创建背景颜色选项面板
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// - 加载自定义背景色
|
|
||||||
/// - 创建背景选项面板UI
|
|
||||||
/// - 添加标题栏和关闭按钮
|
|
||||||
/// - 添加白板/黑板模式选择按钮
|
|
||||||
/// - 添加RGB颜色选择器
|
|
||||||
/// - 添加颜色预览和应用按钮
|
|
||||||
/// - 将面板添加到主网格
|
|
||||||
/// </remarks>
|
|
||||||
private void CreateBackgroundPalette()
|
|
||||||
{
|
{
|
||||||
// 确保加载自定义背景色
|
Settings.Canvas.UsingWhiteboard = true;
|
||||||
LoadCustomBackgroundColor();
|
SaveSettingsToFile();
|
||||||
|
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
||||||
|
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
// 创建一个类似于PenPalette的面板
|
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||||
BackgroundPalette = new Border
|
|
||||||
|
if (currentMode == 1)
|
||||||
{
|
{
|
||||||
Name = "BackgroundPalette",
|
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
||||||
Visibility = Visibility.Collapsed,
|
UpdateRGBSliders(defaultWhiteboardColor);
|
||||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBackground"),
|
CustomBackgroundColor = defaultWhiteboardColor;
|
||||||
Opacity = 1,
|
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
||||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
CornerRadius = new CornerRadius(8),
|
|
||||||
Width = 300,
|
|
||||||
MaxHeight = 400
|
|
||||||
};
|
|
||||||
|
|
||||||
// 确保面板显示在顶层
|
|
||||||
Panel.SetZIndex(BackgroundPalette, 1000);
|
|
||||||
|
|
||||||
// 创建面板内容
|
|
||||||
var stackPanel = new StackPanel();
|
|
||||||
|
|
||||||
// 创建标题栏
|
|
||||||
var titleBorder = new Border
|
|
||||||
{
|
|
||||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x1e, 0x3a, 0x8a)),
|
|
||||||
Height = 32,
|
|
||||||
BorderThickness = new Thickness(0, 0, 0, 1),
|
|
||||||
CornerRadius = new CornerRadius(8, 8, 0, 0),
|
|
||||||
Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
|
||||||
Margin = new Thickness(-1, -1, -1, 0),
|
|
||||||
Padding = new Thickness(1, 1, 1, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
var titleCanvas = new System.Windows.Controls.Canvas { Height = 24, ClipToBounds = true };
|
|
||||||
var titleText = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "背景设置",
|
|
||||||
Foreground = (SolidColorBrush)Application.Current.FindResource("FloatBarForeground"),
|
|
||||||
Padding = new Thickness(0, 5, 0, 0),
|
|
||||||
FontSize = 11,
|
|
||||||
FontWeight = FontWeights.Bold,
|
|
||||||
TextAlignment = TextAlignment.Center
|
|
||||||
};
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(titleText, 8);
|
|
||||||
titleCanvas.Children.Add(titleText);
|
|
||||||
|
|
||||||
// 关闭按钮
|
|
||||||
var closeImage = new Image
|
|
||||||
{
|
|
||||||
Source = new BitmapImage(new Uri("/Resources/new-icons/close-white.png", UriKind.Relative)),
|
|
||||||
Height = 16,
|
|
||||||
Width = 16
|
|
||||||
};
|
|
||||||
RenderOptions.SetBitmapScalingMode(closeImage, BitmapScalingMode.HighQuality);
|
|
||||||
closeImage.MouseUp += CloseBordertools_MouseUp;
|
|
||||||
System.Windows.Controls.Canvas.SetRight(closeImage, 8);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(closeImage, 4);
|
|
||||||
titleCanvas.Children.Add(closeImage);
|
|
||||||
|
|
||||||
titleBorder.Child = titleCanvas;
|
|
||||||
stackPanel.Children.Add(titleBorder);
|
|
||||||
|
|
||||||
// 创建背景选项内容区域
|
|
||||||
var contentPanel = new StackPanel { Margin = new Thickness(8) };
|
|
||||||
|
|
||||||
// 黑板/白板选择
|
|
||||||
var modeTitle = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "白板模式",
|
|
||||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
|
||||||
FontSize = 10,
|
|
||||||
FontWeight = FontWeights.Bold,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Margin = new Thickness(0, 4, 0, 8)
|
|
||||||
};
|
|
||||||
contentPanel.Children.Add(modeTitle);
|
|
||||||
|
|
||||||
var modePanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
|
||||||
|
|
||||||
// 白板按钮
|
|
||||||
var whiteboardButton = new Border
|
|
||||||
{
|
|
||||||
Width = 60,
|
|
||||||
Height = 30,
|
|
||||||
Background = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray),
|
|
||||||
CornerRadius = new CornerRadius(4),
|
|
||||||
Margin = new Thickness(0, 0, 8, 0)
|
|
||||||
};
|
|
||||||
var whiteboardText = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "白板",
|
|
||||||
Foreground = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
|
||||||
};
|
|
||||||
whiteboardButton.Child = whiteboardText;
|
|
||||||
whiteboardButton.MouseUp += (s, args) =>
|
|
||||||
{
|
|
||||||
Settings.Canvas.UsingWhiteboard = true;
|
|
||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
|
||||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
// 设置为白板默认背景色
|
|
||||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
|
||||||
|
|
||||||
if (currentMode == 1) // 白板模式
|
|
||||||
{
|
|
||||||
// 设置背景为默认白板背景色
|
|
||||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
|
||||||
|
|
||||||
// 更新RGB滑块的值为默认白板背景色
|
|
||||||
UpdateRGBSliders(defaultWhiteboardColor);
|
|
||||||
|
|
||||||
// 更新自定义背景色为默认白板背景色
|
|
||||||
CustomBackgroundColor = defaultWhiteboardColor;
|
|
||||||
|
|
||||||
// 保存到设置
|
|
||||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
|
||||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置墨迹颜色为黑色
|
|
||||||
CheckLastColor(0);
|
|
||||||
forceEraser = false;
|
|
||||||
|
|
||||||
CheckColorTheme(true);
|
|
||||||
UpdateBackgroundButtonsState();
|
|
||||||
};
|
|
||||||
modePanel.Children.Add(whiteboardButton);
|
|
||||||
|
|
||||||
// 黑板按钮
|
|
||||||
var blackboardButton = new Border
|
|
||||||
{
|
|
||||||
Width = 60,
|
|
||||||
Height = 30,
|
|
||||||
Background = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray),
|
|
||||||
CornerRadius = new CornerRadius(4)
|
|
||||||
};
|
|
||||||
var blackboardText = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "黑板",
|
|
||||||
Foreground = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
|
||||||
};
|
|
||||||
blackboardButton.Child = blackboardText;
|
|
||||||
blackboardButton.MouseUp += (s, args) =>
|
|
||||||
{
|
|
||||||
Settings.Canvas.UsingWhiteboard = false;
|
|
||||||
SaveSettingsToFile();
|
|
||||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
|
||||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
// 设置为黑板默认背景色
|
|
||||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
|
||||||
|
|
||||||
if (currentMode == 1) // 黑板模式
|
|
||||||
{
|
|
||||||
// 设置背景为默认黑板背景色
|
|
||||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
|
||||||
|
|
||||||
// 更新RGB滑块的值为默认黑板背景色
|
|
||||||
UpdateRGBSliders(defaultBlackboardColor);
|
|
||||||
|
|
||||||
// 更新自定义背景色为默认黑板背景色
|
|
||||||
CustomBackgroundColor = defaultBlackboardColor;
|
|
||||||
|
|
||||||
// 保存到设置
|
|
||||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
|
||||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置墨迹颜色为白色
|
|
||||||
CheckLastColor(5);
|
|
||||||
forceEraser = false;
|
|
||||||
|
|
||||||
CheckColorTheme(true);
|
|
||||||
UpdateBackgroundButtonsState();
|
|
||||||
};
|
|
||||||
modePanel.Children.Add(blackboardButton);
|
|
||||||
|
|
||||||
contentPanel.Children.Add(modePanel);
|
|
||||||
|
|
||||||
// 添加一条分隔线
|
|
||||||
var separator = new Border
|
|
||||||
{
|
|
||||||
Height = 1,
|
|
||||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
|
||||||
Margin = new Thickness(0, 12, 0, 12)
|
|
||||||
};
|
|
||||||
contentPanel.Children.Add(separator);
|
|
||||||
|
|
||||||
// 添加RGB颜色选择器部分
|
|
||||||
var colorTitle = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "背景颜色",
|
|
||||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
|
||||||
FontSize = 10,
|
|
||||||
FontWeight = FontWeights.Bold,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Margin = new Thickness(0, 4, 0, 8)
|
|
||||||
};
|
|
||||||
contentPanel.Children.Add(colorTitle);
|
|
||||||
|
|
||||||
// 创建颜色预览
|
|
||||||
Border colorPreview = new Border
|
|
||||||
{
|
|
||||||
Width = 100,
|
|
||||||
Height = 40,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
BorderBrush = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
|
||||||
Background = new SolidColorBrush(Colors.White),
|
|
||||||
CornerRadius = new CornerRadius(4),
|
|
||||||
Margin = new Thickness(0, 0, 0, 10),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center
|
|
||||||
};
|
|
||||||
contentPanel.Children.Add(colorPreview);
|
|
||||||
|
|
||||||
// 获取当前背景颜色
|
|
||||||
Color currentBackgroundColor;
|
|
||||||
if (currentMode == 1) // 白板或黑板模式
|
|
||||||
{
|
|
||||||
if (GridBackgroundCover.Background is SolidColorBrush brush)
|
|
||||||
{
|
|
||||||
currentBackgroundColor = brush.Color;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 默认颜色
|
|
||||||
currentBackgroundColor = Settings.Canvas.UsingWhiteboard ?
|
|
||||||
Color.FromRgb(234, 235, 237) : // 白板默认颜色
|
|
||||||
Color.FromRgb(22, 41, 36); // 黑板默认颜色
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 默认白色
|
|
||||||
currentBackgroundColor = Colors.White;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新颜色预览
|
CheckLastColor(0);
|
||||||
colorPreview.Background = new SolidColorBrush(currentBackgroundColor);
|
forceEraser = false;
|
||||||
|
CheckColorTheme(true);
|
||||||
|
UpdateBackgroundButtonsState();
|
||||||
|
}
|
||||||
|
|
||||||
// 先创建所有滑块控件
|
private void BlackboardModeBtn_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
// R滑块和文本框
|
{
|
||||||
var rPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
Settings.Canvas.UsingWhiteboard = false;
|
||||||
var rLabel = new TextBlock { Text = "R:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
SaveSettingsToFile();
|
||||||
var rSlider = new Slider
|
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
||||||
|
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
||||||
|
|
||||||
|
if (currentMode == 1)
|
||||||
{
|
{
|
||||||
Minimum = 0,
|
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
||||||
Maximum = 255,
|
UpdateRGBSliders(defaultBlackboardColor);
|
||||||
Value = currentBackgroundColor.R,
|
CustomBackgroundColor = defaultBlackboardColor;
|
||||||
Width = 150,
|
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
||||||
Margin = new Thickness(5, 0, 5, 0),
|
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
SaveSettingsToFile();
|
||||||
};
|
}
|
||||||
var rValueText = new TextBlock
|
|
||||||
|
CheckLastColor(5);
|
||||||
|
forceEraser = false;
|
||||||
|
CheckColorTheme(true);
|
||||||
|
UpdateBackgroundButtonsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackgroundRSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||||
|
{
|
||||||
|
if (BackgroundRValue != null)
|
||||||
{
|
{
|
||||||
Text = currentBackgroundColor.R.ToString(),
|
BackgroundRValue.Text = ((int)e.NewValue).ToString();
|
||||||
Width = 30,
|
UpdateColorPreviewFromSliders();
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
}
|
||||||
TextAlignment = TextAlignment.Right,
|
}
|
||||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
|
||||||
};
|
private void BackgroundGSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||||
|
{
|
||||||
// G滑块和文本框
|
if (BackgroundGValue != null)
|
||||||
var gPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
{
|
||||||
var gLabel = new TextBlock { Text = "G:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
BackgroundGValue.Text = ((int)e.NewValue).ToString();
|
||||||
var gSlider = new Slider
|
UpdateColorPreviewFromSliders();
|
||||||
{
|
}
|
||||||
Minimum = 0,
|
}
|
||||||
Maximum = 255,
|
|
||||||
Value = currentBackgroundColor.G,
|
private void BackgroundBSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||||
Width = 150,
|
{
|
||||||
Margin = new Thickness(5, 0, 5, 0),
|
if (BackgroundBValue != null)
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
{
|
||||||
};
|
BackgroundBValue.Text = ((int)e.NewValue).ToString();
|
||||||
var gValueText = new TextBlock
|
UpdateColorPreviewFromSliders();
|
||||||
{
|
}
|
||||||
Text = currentBackgroundColor.G.ToString(),
|
}
|
||||||
Width = 30,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
private void ApplyBackgroundColorBtn_Click(object sender, RoutedEventArgs e)
|
||||||
TextAlignment = TextAlignment.Right,
|
{
|
||||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
Color selectedColor = Color.FromRgb(
|
||||||
};
|
(byte)BackgroundRSlider.Value,
|
||||||
|
(byte)BackgroundGSlider.Value,
|
||||||
// B滑块和文本框
|
(byte)BackgroundBSlider.Value
|
||||||
var bPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
);
|
||||||
var bLabel = new TextBlock { Text = "B:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
ApplyCustomBackgroundColor(selectedColor);
|
||||||
var bSlider = new Slider
|
}
|
||||||
{
|
|
||||||
Minimum = 0,
|
private void UpdateColorPreviewFromSliders()
|
||||||
Maximum = 255,
|
{
|
||||||
Value = currentBackgroundColor.B,
|
if (BackgroundColorPreview != null)
|
||||||
Width = 150,
|
{
|
||||||
Margin = new Thickness(5, 0, 5, 0),
|
Color previewColor = Color.FromRgb(
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
(byte)BackgroundRSlider.Value,
|
||||||
};
|
(byte)BackgroundGSlider.Value,
|
||||||
var bValueText = new TextBlock
|
(byte)BackgroundBSlider.Value
|
||||||
{
|
);
|
||||||
Text = currentBackgroundColor.B.ToString(),
|
BackgroundColorPreview.Background = new SolidColorBrush(previewColor);
|
||||||
Width = 30,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
TextAlignment = TextAlignment.Right,
|
|
||||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
|
||||||
};
|
|
||||||
|
|
||||||
// 现在添加事件处理程序
|
|
||||||
rSlider.ValueChanged += (s, e) =>
|
|
||||||
{
|
|
||||||
int value = (int)e.NewValue;
|
|
||||||
rValueText.Text = value.ToString();
|
|
||||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
|
||||||
};
|
|
||||||
|
|
||||||
gSlider.ValueChanged += (s, e) =>
|
|
||||||
{
|
|
||||||
int value = (int)e.NewValue;
|
|
||||||
gValueText.Text = value.ToString();
|
|
||||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
|
||||||
};
|
|
||||||
|
|
||||||
bSlider.ValueChanged += (s, e) =>
|
|
||||||
{
|
|
||||||
int value = (int)e.NewValue;
|
|
||||||
bValueText.Text = value.ToString();
|
|
||||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加控件到面板
|
|
||||||
rPanel.Children.Add(rLabel);
|
|
||||||
rPanel.Children.Add(rSlider);
|
|
||||||
rPanel.Children.Add(rValueText);
|
|
||||||
contentPanel.Children.Add(rPanel);
|
|
||||||
|
|
||||||
gPanel.Children.Add(gLabel);
|
|
||||||
gPanel.Children.Add(gSlider);
|
|
||||||
gPanel.Children.Add(gValueText);
|
|
||||||
contentPanel.Children.Add(gPanel);
|
|
||||||
|
|
||||||
bPanel.Children.Add(bLabel);
|
|
||||||
bPanel.Children.Add(bSlider);
|
|
||||||
bPanel.Children.Add(bValueText);
|
|
||||||
contentPanel.Children.Add(bPanel);
|
|
||||||
|
|
||||||
// 应用按钮
|
|
||||||
var applyButton = new Button
|
|
||||||
{
|
|
||||||
Content = "应用颜色",
|
|
||||||
Margin = new Thickness(0, 10, 0, 0),
|
|
||||||
Padding = new Thickness(10, 5, 10, 5),
|
|
||||||
Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
|
||||||
Foreground = new SolidColorBrush(Colors.White),
|
|
||||||
BorderThickness = new Thickness(0),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center
|
|
||||||
};
|
|
||||||
|
|
||||||
applyButton.Click += (s, e) =>
|
|
||||||
{
|
|
||||||
Color selectedColor = Color.FromRgb(
|
|
||||||
(byte)rSlider.Value,
|
|
||||||
(byte)gSlider.Value,
|
|
||||||
(byte)bSlider.Value
|
|
||||||
);
|
|
||||||
ApplyCustomBackgroundColor(selectedColor);
|
|
||||||
};
|
|
||||||
|
|
||||||
contentPanel.Children.Add(applyButton);
|
|
||||||
|
|
||||||
stackPanel.Children.Add(contentPanel);
|
|
||||||
|
|
||||||
// 将面板添加到父容器
|
|
||||||
BackgroundPalette.Child = stackPanel;
|
|
||||||
|
|
||||||
// 获取主窗口中的根网格,确保面板添加到顶层
|
|
||||||
Grid mainGrid = FindName("Main_Grid") as Grid;
|
|
||||||
if (mainGrid != null)
|
|
||||||
{
|
|
||||||
// 删除可能已存在的BackgroundPalette
|
|
||||||
foreach (UIElement element in mainGrid.Children)
|
|
||||||
{
|
|
||||||
if (element is Border border && border.Name == "BackgroundPalette")
|
|
||||||
{
|
|
||||||
mainGrid.Children.Remove(border);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新定位面板
|
|
||||||
BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Center;
|
|
||||||
BackgroundPalette.VerticalAlignment = VerticalAlignment.Center;
|
|
||||||
BackgroundPalette.Margin = new Thickness(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// 添加到主网格
|
|
||||||
mainGrid.Children.Add(BackgroundPalette);
|
|
||||||
|
|
||||||
// 设置面板位置
|
|
||||||
var clickElement = FindName("BoardChangeBackgroundColorBtn") as FrameworkElement;
|
|
||||||
if (clickElement != null)
|
|
||||||
{
|
|
||||||
Point position = clickElement.TranslatePoint(new Point(0, 0), mainGrid);
|
|
||||||
BackgroundPalette.Margin = new Thickness(
|
|
||||||
position.X - 150,
|
|
||||||
position.Y + clickElement.ActualHeight + 5,
|
|
||||||
0, 0);
|
|
||||||
BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Left;
|
|
||||||
BackgroundPalette.VerticalAlignment = VerticalAlignment.Top;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新背景颜色选项面板中的按钮状态
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// - 更新白板和黑板按钮的背景和前景色
|
|
||||||
/// - 根据当前使用的模式设置按钮状态
|
|
||||||
/// </remarks>
|
|
||||||
private void UpdateBackgroundButtonsState()
|
private void UpdateBackgroundButtonsState()
|
||||||
{
|
{
|
||||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
if (WhiteboardModeBtn != null)
|
||||||
{
|
{
|
||||||
if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel)
|
WhiteboardModeBtn.Background = Settings.Canvas.UsingWhiteboard ?
|
||||||
|
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||||
|
new SolidColorBrush(Colors.LightGray);
|
||||||
|
if (WhiteboardModeBtn.Child is TextBlock whiteboardText)
|
||||||
{
|
{
|
||||||
if (contentPanel.Children.Count > 1 && contentPanel.Children[1] is StackPanel modePanel)
|
whiteboardText.Foreground = Settings.Canvas.UsingWhiteboard ?
|
||||||
{
|
new SolidColorBrush(Colors.White) :
|
||||||
if (modePanel.Children.Count > 1)
|
new SolidColorBrush(Colors.Black);
|
||||||
{
|
}
|
||||||
var whiteboardButton = modePanel.Children[0] as Border;
|
}
|
||||||
var blackboardButton = modePanel.Children[1] as Border;
|
|
||||||
|
|
||||||
if (whiteboardButton != null && whiteboardButton.Child is TextBlock whiteboardText)
|
if (BlackboardModeBtn != null)
|
||||||
{
|
{
|
||||||
whiteboardButton.Background = Settings.Canvas.UsingWhiteboard ?
|
BlackboardModeBtn.Background = !Settings.Canvas.UsingWhiteboard ?
|
||||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||||
new SolidColorBrush(Colors.LightGray);
|
new SolidColorBrush(Colors.LightGray);
|
||||||
whiteboardText.Foreground = Settings.Canvas.UsingWhiteboard ?
|
if (BlackboardModeBtn.Child is TextBlock blackboardText)
|
||||||
new SolidColorBrush(Colors.White) :
|
{
|
||||||
new SolidColorBrush(Colors.Black);
|
blackboardText.Foreground = !Settings.Canvas.UsingWhiteboard ?
|
||||||
}
|
new SolidColorBrush(Colors.White) :
|
||||||
|
new SolidColorBrush(Colors.Black);
|
||||||
if (blackboardButton != null && blackboardButton.Child is TextBlock blackboardText)
|
|
||||||
{
|
|
||||||
blackboardButton.Background = !Settings.Canvas.UsingWhiteboard ?
|
|
||||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
|
||||||
new SolidColorBrush(Colors.LightGray);
|
|
||||||
blackboardText.Foreground = !Settings.Canvas.UsingWhiteboard ?
|
|
||||||
new SolidColorBrush(Colors.White) :
|
|
||||||
new SolidColorBrush(Colors.Black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 背景颜色选项面板
|
|
||||||
/// </summary>
|
|
||||||
private Border BackgroundPalette { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前自定义背景色
|
/// 当前自定义背景色
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -767,41 +330,7 @@ namespace Ink_Canvas
|
|||||||
/// - 启用橡皮擦模式
|
/// - 启用橡皮擦模式
|
||||||
/// - 设置橡皮擦形状为圆形
|
/// - 设置橡皮擦形状为圆形
|
||||||
/// - 设置当前工具模式为按笔画擦除
|
/// - 设置当前工具模式为按笔画擦除
|
||||||
/// - 禁用形状绘制模式
|
|
||||||
/// - 重置钢笔类型和属性
|
|
||||||
/// - 触发编辑模式变更事件
|
|
||||||
/// - 取消单指拖动模式
|
|
||||||
/// - 隐藏子面板
|
|
||||||
/// </remarks>
|
|
||||||
private void BoardEraserIconByStrokes_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
//if (BoardEraserByStrokes.Background.ToString() == "#FF679CF4") {
|
|
||||||
// AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardDeleteIcon);
|
|
||||||
//}
|
|
||||||
//else {
|
|
||||||
// 禁用高级橡皮擦系统
|
|
||||||
DisableEraserOverlay();
|
|
||||||
|
|
||||||
forceEraser = true;
|
|
||||||
forcePointEraser = false;
|
|
||||||
|
|
||||||
inkCanvas.EraserShape = new EllipseStylusShape(5, 5);
|
|
||||||
// 使用集中化的工具模式切换方法
|
|
||||||
SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke);
|
|
||||||
drawingShapeMode = 0;
|
|
||||||
|
|
||||||
penType = 0;
|
|
||||||
drawingAttributes.IsHighlighter = false;
|
|
||||||
drawingAttributes.StylusTip = StylusTip.Ellipse;
|
|
||||||
|
|
||||||
inkCanvas_EditingModeChanged(inkCanvas, null);
|
|
||||||
CancelSingleFingerDragMode();
|
|
||||||
|
|
||||||
HideSubPanels("eraserByStrokes");
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理删除图标点击事件,清空画布内容
|
/// 处理删除图标点击事件,清空画布内容
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">事件发送者</param>
|
/// <param name="sender">事件发送者</param>
|
||||||
@@ -913,53 +442,9 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateRGBSliders(Color color)
|
private void UpdateRGBSliders(Color color)
|
||||||
{
|
{
|
||||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
if (BackgroundRSlider != null) BackgroundRSlider.Value = color.R;
|
||||||
{
|
if (BackgroundGSlider != null) BackgroundGSlider.Value = color.G;
|
||||||
if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel)
|
if (BackgroundBSlider != null) BackgroundBSlider.Value = color.B;
|
||||||
{
|
|
||||||
// 查找RGB滑块
|
|
||||||
Slider rSlider = null;
|
|
||||||
Slider gSlider = null;
|
|
||||||
Slider bSlider = null;
|
|
||||||
|
|
||||||
// 遍历面板查找RGB滑块
|
|
||||||
foreach (var child in contentPanel.Children)
|
|
||||||
{
|
|
||||||
if (child is StackPanel panel && panel.Orientation == Orientation.Horizontal)
|
|
||||||
{
|
|
||||||
foreach (var panelChild in panel.Children)
|
|
||||||
{
|
|
||||||
if (panelChild is Slider slider)
|
|
||||||
{
|
|
||||||
if (panel.Children.Count > 0 && panel.Children[0] is TextBlock label)
|
|
||||||
{
|
|
||||||
if (label.Text == "R:")
|
|
||||||
{
|
|
||||||
rSlider = slider;
|
|
||||||
}
|
|
||||||
else if (label.Text == "G:")
|
|
||||||
{
|
|
||||||
gSlider = slider;
|
|
||||||
}
|
|
||||||
else if (label.Text == "B:")
|
|
||||||
{
|
|
||||||
bSlider = slider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新滑块值
|
|
||||||
if (rSlider != null && gSlider != null && bSlider != null)
|
|
||||||
{
|
|
||||||
rSlider.Value = color.R;
|
|
||||||
gSlider.Value = color.G;
|
|
||||||
bSlider.Value = color.B;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,21 +272,21 @@ namespace Ink_Canvas
|
|||||||
/// - 显示通知
|
/// - 显示通知
|
||||||
/// - 包含异常处理
|
/// - 包含异常处理
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async Task PasteImageFromClipboard(Point? position = null)
|
private Task PasteImageFromClipboard(Point? position = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Clipboard.ContainsImage())
|
if (!Clipboard.ContainsImage())
|
||||||
{
|
{
|
||||||
ShowNotification("剪贴板中没有图片");
|
ShowNotification("剪贴板中没有图片");
|
||||||
return;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
var clipboardImage = Clipboard.GetImage();
|
var clipboardImage = Clipboard.GetImage();
|
||||||
if (clipboardImage == null)
|
if (clipboardImage == null)
|
||||||
{
|
{
|
||||||
ShowNotification("无法获取剪贴板图片");
|
ShowNotification("无法获取剪贴板图片");
|
||||||
return;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Image控件
|
// 创建Image控件
|
||||||
@@ -383,6 +383,7 @@ namespace Ink_Canvas
|
|||||||
ShowNotification($"粘贴图片失败: {ex.Message}");
|
ShowNotification($"粘贴图片失败: {ex.Message}");
|
||||||
LogHelper.WriteLogToFile($"粘贴图片失败: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"粘贴图片失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace Ink_Canvas
|
namespace Ink_Canvas
|
||||||
@@ -283,26 +281,26 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
// 亮系
|
// 亮系
|
||||||
// 亮色的红色
|
// 亮色的红色
|
||||||
BorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68));
|
BorderPenColorRed.Color = Color.FromRgb(239, 68, 68);
|
||||||
BoardBorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68));
|
BoardBorderPenColorRed.Color = Color.FromRgb(239, 68, 68);
|
||||||
// 亮色的绿色
|
// 亮色的绿色
|
||||||
BorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(34, 197, 94));
|
BorderPenColorGreen.Color = Color.FromRgb(34, 197, 94);
|
||||||
BoardBorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(34, 197, 94));
|
BoardBorderPenColorGreen.Color = Color.FromRgb(34, 197, 94);
|
||||||
// 亮色的蓝色
|
// 亮色的蓝色
|
||||||
BorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(59, 130, 246));
|
BorderPenColorBlue.Color = Color.FromRgb(59, 130, 246);
|
||||||
BoardBorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(59, 130, 246));
|
BoardBorderPenColorBlue.Color = Color.FromRgb(59, 130, 246);
|
||||||
// 亮色的黄色
|
// 亮色的黄色
|
||||||
BorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(250, 204, 21));
|
BorderPenColorYellow.Color = Color.FromRgb(250, 204, 21);
|
||||||
BoardBorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(250, 204, 21));
|
BoardBorderPenColorYellow.Color = Color.FromRgb(250, 204, 21);
|
||||||
// 亮色的粉色
|
// 亮色的粉色
|
||||||
BorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(236, 72, 153));
|
BorderPenColorPink.Color = Color.FromRgb(236, 72, 153);
|
||||||
BoardBorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(236, 72, 153));
|
BoardBorderPenColorPink.Color = Color.FromRgb(236, 72, 153);
|
||||||
// 亮色的Teal
|
// 亮色的Teal
|
||||||
BorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(20, 184, 166));
|
BorderPenColorTeal.Color = Color.FromRgb(20, 184, 166);
|
||||||
BoardBorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(20, 184, 166));
|
BoardBorderPenColorTeal.Color = Color.FromRgb(20, 184, 166);
|
||||||
// 亮色的Orange
|
// 亮色的Orange
|
||||||
BorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(249, 115, 22));
|
BorderPenColorOrange.Color = Color.FromRgb(249, 115, 22);
|
||||||
BoardBorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(249, 115, 22));
|
BoardBorderPenColorOrange.Color = Color.FromRgb(249, 115, 22);
|
||||||
|
|
||||||
var newImageSource = new BitmapImage();
|
var newImageSource = new BitmapImage();
|
||||||
newImageSource.BeginInit();
|
newImageSource.BeginInit();
|
||||||
@@ -319,26 +317,26 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
// 暗系
|
// 暗系
|
||||||
// 暗色的红色
|
// 暗色的红色
|
||||||
BorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(220, 38, 38));
|
BorderPenColorRed.Color = Color.FromRgb(220, 38, 38);
|
||||||
BoardBorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(220, 38, 38));
|
BoardBorderPenColorRed.Color = Color.FromRgb(220, 38, 38);
|
||||||
// 暗色的绿色
|
// 暗色的绿色
|
||||||
BorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(22, 163, 74));
|
BorderPenColorGreen.Color = Color.FromRgb(22, 163, 74);
|
||||||
BoardBorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(22, 163, 74));
|
BoardBorderPenColorGreen.Color = Color.FromRgb(22, 163, 74);
|
||||||
// 暗色的蓝色
|
// 暗色的蓝色
|
||||||
BorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
|
BorderPenColorBlue.Color = Color.FromRgb(37, 99, 235);
|
||||||
BoardBorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
|
BoardBorderPenColorBlue.Color = Color.FromRgb(37, 99, 235);
|
||||||
// 暗色的黄色
|
// 暗色的黄色
|
||||||
BorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(234, 179, 8));
|
BorderPenColorYellow.Color = Color.FromRgb(234, 179, 8);
|
||||||
BoardBorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(234, 179, 8));
|
BoardBorderPenColorYellow.Color = Color.FromRgb(234, 179, 8);
|
||||||
// 暗色的紫色对应亮色的粉色
|
// 暗色的紫色对应亮色的粉色
|
||||||
BorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(147, 51, 234));
|
BorderPenColorPink.Color = Color.FromRgb(147, 51, 234);
|
||||||
BoardBorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(147, 51, 234));
|
BoardBorderPenColorPink.Color = Color.FromRgb(147, 51, 234);
|
||||||
// 暗色的Teal
|
// 暗色的Teal
|
||||||
BorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(13, 148, 136));
|
BorderPenColorTeal.Color = Color.FromRgb(13, 148, 136);
|
||||||
BoardBorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(13, 148, 136));
|
BoardBorderPenColorTeal.Color = Color.FromRgb(13, 148, 136);
|
||||||
// 暗色的Orange
|
// 暗色的Orange
|
||||||
BorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(234, 88, 12));
|
BorderPenColorOrange.Color = Color.FromRgb(234, 88, 12);
|
||||||
BoardBorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(234, 88, 12));
|
BoardBorderPenColorOrange.Color = Color.FromRgb(234, 88, 12);
|
||||||
|
|
||||||
var newImageSource = new BitmapImage();
|
var newImageSource = new BitmapImage();
|
||||||
newImageSource.BeginInit();
|
newImageSource.BeginInit();
|
||||||
@@ -353,127 +351,129 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 改变选中提示
|
// 改变选中提示
|
||||||
ViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
BorderPenColorBlack.IsChecked = false;
|
||||||
ViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
BorderPenColorBlue.IsChecked = false;
|
||||||
ViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
BorderPenColorGreen.IsChecked = false;
|
||||||
ViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
BorderPenColorRed.IsChecked = false;
|
||||||
ViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
BorderPenColorYellow.IsChecked = false;
|
||||||
ViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
BorderPenColorWhite.IsChecked = false;
|
||||||
ViewboxBtnColorPinkContent.Visibility = Visibility.Collapsed;
|
BorderPenColorPink.IsChecked = false;
|
||||||
ViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
BorderPenColorTeal.IsChecked = false;
|
||||||
ViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
BorderPenColorOrange.IsChecked = false;
|
||||||
|
|
||||||
BoardViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorBlack.IsChecked = false;
|
||||||
BoardViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorBlue.IsChecked = false;
|
||||||
BoardViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorGreen.IsChecked = false;
|
||||||
BoardViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorRed.IsChecked = false;
|
||||||
BoardViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorYellow.IsChecked = false;
|
||||||
BoardViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorWhite.IsChecked = false;
|
||||||
BoardViewboxBtnColorPinkContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorPink.IsChecked = false;
|
||||||
BoardViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorTeal.IsChecked = false;
|
||||||
BoardViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
BoardBorderPenColorOrange.IsChecked = false;
|
||||||
|
|
||||||
HighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorBlack.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorBlue.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorGreen.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorOrange.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Collapsed;
|
HighlighterPenPenColorPurple.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorRed.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorTeal.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorWhite.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorYellow.IsChecked = false;
|
||||||
HighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Collapsed;
|
HighlighterPenColorZinc.IsChecked = false;
|
||||||
|
|
||||||
BoardHighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorBlack.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorBlue.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorGreen.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorOrange.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenPenColorPurple.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorRed.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorTeal.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorWhite.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorYellow.IsChecked = false;
|
||||||
BoardHighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Collapsed;
|
BoardHighlighterPenColorZinc.IsChecked = false;
|
||||||
|
|
||||||
switch (inkColor)
|
switch (inkColor)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
ViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
BorderPenColorBlack.IsChecked = true;
|
||||||
BoardViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorBlack.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
ViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
BorderPenColorRed.IsChecked = true;
|
||||||
BoardViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorRed.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
ViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
BorderPenColorGreen.IsChecked = true;
|
||||||
BoardViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorGreen.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
ViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
BorderPenColorBlue.IsChecked = true;
|
||||||
BoardViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorBlue.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
ViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
BorderPenColorYellow.IsChecked = true;
|
||||||
BoardViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorYellow.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
ViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
BorderPenColorWhite.IsChecked = true;
|
||||||
BoardViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorWhite.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
ViewboxBtnColorPinkContent.Visibility = Visibility.Visible;
|
BorderPenColorPink.IsChecked = true;
|
||||||
BoardViewboxBtnColorPinkContent.Visibility = Visibility.Visible;
|
BoardBorderPenColorPink.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
ViewboxBtnColorTealContent.Visibility = Visibility.Visible;
|
BorderPenColorTeal.IsChecked = true;
|
||||||
|
BoardBorderPenColorTeal.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
ViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
BorderPenColorOrange.IsChecked = true;
|
||||||
|
BoardBorderPenColorOrange.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (highlighterColor)
|
switch (highlighterColor)
|
||||||
{
|
{
|
||||||
case 100:
|
case 100:
|
||||||
HighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
HighlighterPenColorBlack.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorBlack.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 101:
|
case 101:
|
||||||
HighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
HighlighterPenColorWhite.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorWhite.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 102:
|
case 102:
|
||||||
HighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
HighlighterPenColorRed.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorRed.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 103:
|
case 103:
|
||||||
HighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
HighlighterPenColorYellow.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorYellow.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 104:
|
case 104:
|
||||||
HighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
HighlighterPenColorGreen.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorGreen.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 105:
|
case 105:
|
||||||
HighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Visible;
|
HighlighterPenColorZinc.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorZinc.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 106:
|
case 106:
|
||||||
HighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
HighlighterPenColorBlue.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorBlue.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 107:
|
case 107:
|
||||||
HighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Visible;
|
HighlighterPenPenColorPurple.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenPenColorPurple.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 108:
|
case 108:
|
||||||
HighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Visible;
|
HighlighterPenColorTeal.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorTeal.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
case 109:
|
case 109:
|
||||||
HighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
HighlighterPenColorOrange.IsChecked = true;
|
||||||
BoardHighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
BoardHighlighterPenColorOrange.IsChecked = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,13 +563,6 @@ namespace Ink_Canvas
|
|||||||
PenPalette.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 32);
|
PenPalette.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 32);
|
||||||
UpdatePenPalettePosition();
|
UpdatePenPalettePosition();
|
||||||
});
|
});
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
|
|
||||||
var currentMargin = BoardPenPaletteGrid.Margin;
|
|
||||||
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 50);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else if (penType == 1)
|
else if (penType == 1)
|
||||||
{
|
{
|
||||||
@@ -616,13 +609,6 @@ namespace Ink_Canvas
|
|||||||
PenPalette.Margin = new Thickness(currentMargin.Left, -157, currentMargin.Right, 32);
|
PenPalette.Margin = new Thickness(currentMargin.Left, -157, currentMargin.Right, 32);
|
||||||
UpdatePenPalettePosition();
|
UpdatePenPalettePosition();
|
||||||
});
|
});
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
|
|
||||||
var currentMargin = BoardPenPaletteGrid.Margin;
|
|
||||||
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -154, currentMargin.Right, 50);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果是图片元素,更新选择点位置
|
// 如果是图片元素,更新选择点位置
|
||||||
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && ImageSelectionOverlay?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果是图片元素,更新选择点位置
|
// 如果是图片元素,更新选择点位置
|
||||||
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && ImageSelectionOverlay?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
}
|
}
|
||||||
@@ -415,7 +415,7 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果是图片元素,更新选择点位置
|
// 如果是图片元素,更新选择点位置
|
||||||
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && ImageSelectionOverlay?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
}
|
}
|
||||||
@@ -510,11 +510,22 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (element.RenderTransform is TransformGroup transformGroup)
|
if (element.RenderTransform is TransformGroup transformGroup)
|
||||||
{
|
{
|
||||||
|
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||||
|
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||||
var rotateTransform = transformGroup.Children.OfType<RotateTransform>().FirstOrDefault();
|
var rotateTransform = transformGroup.Children.OfType<RotateTransform>().FirstOrDefault();
|
||||||
if (rotateTransform != null)
|
if (rotateTransform == null) return;
|
||||||
{
|
|
||||||
rotateTransform.Angle += angle;
|
var (ox, oy, visW, visH) = GetElementVisualBox(element);
|
||||||
}
|
double sX = scaleTransform?.ScaleX ?? 1;
|
||||||
|
double sY = scaleTransform?.ScaleY ?? 1;
|
||||||
|
double tx = translateTransform?.X ?? 0;
|
||||||
|
double ty = translateTransform?.Y ?? 0;
|
||||||
|
|
||||||
|
// Rotate runs last in the group, so its Center is in post-scale/translate space —
|
||||||
|
// i.e. the current visual center of the element.
|
||||||
|
rotateTransform.CenterX = tx + (ox + visW / 2) * sX;
|
||||||
|
rotateTransform.CenterY = ty + (oy + visH / 2) * sY;
|
||||||
|
rotateTransform.Angle += angle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2176,7 +2187,7 @@ namespace Ink_Canvas
|
|||||||
if (currentSelectedElement != null && IsBitmapLikeCanvasElement(currentSelectedElement))
|
if (currentSelectedElement != null && IsBitmapLikeCanvasElement(currentSelectedElement))
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
if (ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (ImageSelectionOverlay?.Visibility == Visibility.Visible)
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(currentSelectedElement));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(currentSelectedElement));
|
||||||
}
|
}
|
||||||
}), DispatcherPriority.Loaded);
|
}), DispatcherPriority.Loaded);
|
||||||
@@ -2379,240 +2390,361 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Image Resize Handles
|
#region Image Selection Overlay
|
||||||
|
|
||||||
// 图片缩放选择点相关变量
|
private bool _imageOverlayHooked;
|
||||||
private bool isResizingImage = false;
|
private FrameworkElement _overlayTrackedElement;
|
||||||
private Point imageResizeStartPoint;
|
|
||||||
private string activeResizeHandle = "";
|
private void EnsureImageOverlayHooks()
|
||||||
|
{
|
||||||
|
if (_imageOverlayHooked || ImageSelectionOverlay == null) return;
|
||||||
|
ImageSelectionOverlay.CoordinateSource = inkCanvas;
|
||||||
|
ImageSelectionOverlay.ResizeDelta += ImageSelectionOverlay_ResizeDelta;
|
||||||
|
ImageSelectionOverlay.MoveDelta += ImageSelectionOverlay_MoveDelta;
|
||||||
|
ImageSelectionOverlay.RotateDelta += ImageSelectionOverlay_RotateDelta;
|
||||||
|
_imageOverlayHooked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachOverlayTracking(FrameworkElement element)
|
||||||
|
{
|
||||||
|
if (_overlayTrackedElement == element) return;
|
||||||
|
DetachOverlayTracking();
|
||||||
|
_overlayTrackedElement = element;
|
||||||
|
if (element != null) element.LayoutUpdated += OverlayTrackedElement_LayoutUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachOverlayTracking()
|
||||||
|
{
|
||||||
|
if (_overlayTrackedElement != null)
|
||||||
|
{
|
||||||
|
_overlayTrackedElement.LayoutUpdated -= OverlayTrackedElement_LayoutUpdated;
|
||||||
|
_overlayTrackedElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OverlayTrackedElement_LayoutUpdated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (ImageSelectionOverlay?.Visibility != Visibility.Visible) return;
|
||||||
|
if (currentSelectedElement == null || _overlayTrackedElement == null) return;
|
||||||
|
if (!ReferenceEquals(currentSelectedElement, _overlayTrackedElement)) return;
|
||||||
|
UpdateImageResizeHandlesPosition(default);
|
||||||
|
}
|
||||||
|
|
||||||
// 显示图片缩放选择点
|
|
||||||
private void ShowImageResizeHandles(FrameworkElement element)
|
private void ShowImageResizeHandles(FrameworkElement element)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ImageResizeHandlesCanvas == null || element == null) return;
|
if (ImageSelectionOverlay == null || element == null) return;
|
||||||
|
EnsureImageOverlayHooks();
|
||||||
// 获取元素的实际边界
|
AttachOverlayTracking(element);
|
||||||
Rect elementBounds = GetElementActualBounds(element);
|
UpdateImageResizeHandlesPosition(default);
|
||||||
|
ImageSelectionOverlay.Visibility = Visibility.Visible;
|
||||||
// 设置选择点位置
|
|
||||||
UpdateImageResizeHandlesPosition(elementBounds);
|
|
||||||
|
|
||||||
// 显示选择点
|
|
||||||
ImageResizeHandlesCanvas.Visibility = Visibility.Visible;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"显示图片缩放选择点失败: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"显示图片选中框失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏图片缩放选择点
|
|
||||||
private void HideImageResizeHandles()
|
private void HideImageResizeHandles()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ImageResizeHandlesCanvas != null)
|
DetachOverlayTracking();
|
||||||
{
|
if (ImageSelectionOverlay != null)
|
||||||
ImageResizeHandlesCanvas.Visibility = Visibility.Collapsed;
|
ImageSelectionOverlay.Visibility = Visibility.Collapsed;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"隐藏图片缩放选择点失败: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"隐藏图片选中框失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新图片缩放选择点位置
|
// elementBounds parameter is ignored — overlay needs the UNROTATED bounds of the element,
|
||||||
|
// computed directly from the element's own state so rotation never distorts the frame.
|
||||||
private void UpdateImageResizeHandlesPosition(Rect elementBounds)
|
private void UpdateImageResizeHandlesPosition(Rect elementBounds)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ImageResizeHandlesCanvas == null) return;
|
if (ImageSelectionOverlay == null || currentSelectedElement == null) return;
|
||||||
|
|
||||||
ImageResizeHandlesCanvas.Margin = new Thickness(elementBounds.Left, elementBounds.Top, 0, 0);
|
var element = currentSelectedElement;
|
||||||
|
var (ox, oy, visW, visH) = GetElementVisualBox(element);
|
||||||
|
if (visW <= 0 || visH <= 0) return;
|
||||||
|
|
||||||
// 四个角控制点
|
double scaleX = 1, scaleY = 1, angle = 0;
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageTopLeftHandle, -4);
|
if (element.RenderTransform is TransformGroup tg)
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageTopLeftHandle, -4);
|
|
||||||
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageTopRightHandle, elementBounds.Width - 4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageTopRightHandle, -4);
|
|
||||||
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageBottomLeftHandle, -4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageBottomLeftHandle, elementBounds.Height - 4);
|
|
||||||
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageBottomRightHandle, elementBounds.Width - 4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageBottomRightHandle, elementBounds.Height - 4);
|
|
||||||
|
|
||||||
// 四个边控制点
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageTopHandle, elementBounds.Width / 2 - 4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageTopHandle, -4);
|
|
||||||
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageBottomHandle, elementBounds.Width / 2 - 4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageBottomHandle, elementBounds.Height - 4);
|
|
||||||
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageLeftHandle, -4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageLeftHandle, elementBounds.Height / 2 - 4);
|
|
||||||
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(ImageRightHandle, elementBounds.Width - 4);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(ImageRightHandle, elementBounds.Height / 2 - 4);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"更新图片缩放选择点位置失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片缩放选择点鼠标按下事件
|
|
||||||
private void ImageResizeHandle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse)
|
|
||||||
{
|
{
|
||||||
isResizingImage = true;
|
var st = tg.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||||
imageResizeStartPoint = e.GetPosition(inkCanvas);
|
var rt = tg.Children.OfType<RotateTransform>().FirstOrDefault();
|
||||||
|
if (st != null) { scaleX = st.ScaleX; scaleY = st.ScaleY; }
|
||||||
// 确定是哪个控制点
|
if (rt != null) angle = rt.Angle;
|
||||||
activeResizeHandle = ellipse.Name;
|
|
||||||
|
|
||||||
// 捕获鼠标
|
|
||||||
ellipse.CaptureMouse();
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"图片缩放选择点鼠标按下事件失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片缩放选择点鼠标释放事件
|
// Compute the visual center directly from the element's actual transform,
|
||||||
private void ImageResizeHandle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
// so we don't have to model the transform chain ourselves.
|
||||||
{
|
Point visualCenterLocal = new Point(ox + visW / 2, oy + visH / 2);
|
||||||
try
|
Point centerCanvas;
|
||||||
{
|
try
|
||||||
if (isResizingImage && sender is Ellipse ellipse)
|
|
||||||
{
|
{
|
||||||
isResizingImage = false;
|
var t = element.TransformToAncestor(inkCanvas);
|
||||||
ellipse.ReleaseMouseCapture();
|
centerCanvas = t.Transform(visualCenterLocal);
|
||||||
activeResizeHandle = "";
|
// Cancel out rotation: TransformToAncestor includes Rotate; we need pre-rotation
|
||||||
e.Handled = true;
|
// center in canvas coords for the overlay (overlay applies rotation itself).
|
||||||
|
// The visual center is invariant under rotation around itself, so this is the
|
||||||
|
// same point in canvas coords either way.
|
||||||
}
|
}
|
||||||
}
|
catch
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"图片缩放选择点鼠标释放事件失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 图片缩放选择点鼠标移动事件
|
|
||||||
private void ImageResizeHandle_MouseMove(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (isResizingImage && IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse)
|
|
||||||
{
|
{
|
||||||
var currentPoint = e.GetPosition(inkCanvas);
|
double left = InkCanvas.GetLeft(element); if (double.IsNaN(left)) left = 0;
|
||||||
ResizeImageByHandle(currentSelectedElement, imageResizeStartPoint, currentPoint, activeResizeHandle);
|
double top = InkCanvas.GetTop(element); if (double.IsNaN(top)) top = 0;
|
||||||
imageResizeStartPoint = currentPoint;
|
double tx = 0, ty = 0;
|
||||||
e.Handled = true;
|
if (element.RenderTransform is TransformGroup tg2)
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"图片缩放选择点鼠标移动事件失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据控制点缩放图片
|
|
||||||
private void ResizeImageByHandle(FrameworkElement element, Point startPoint, Point currentPoint, string handleName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (element.RenderTransform is TransformGroup transformGroup)
|
|
||||||
{
|
|
||||||
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
|
||||||
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
|
||||||
|
|
||||||
if (scaleTransform == null || translateTransform == null) return;
|
|
||||||
|
|
||||||
// 获取图片的当前边界
|
|
||||||
Rect currentBounds = GetElementActualBounds(element);
|
|
||||||
double deltaX = currentPoint.X - startPoint.X;
|
|
||||||
double deltaY = currentPoint.Y - startPoint.Y;
|
|
||||||
|
|
||||||
// 计算缩放比例
|
|
||||||
double scaleX = 1.0;
|
|
||||||
double scaleY = 1.0;
|
|
||||||
double translateX = 0;
|
|
||||||
double translateY = 0;
|
|
||||||
|
|
||||||
switch (handleName)
|
|
||||||
{
|
{
|
||||||
case "ImageTopLeftHandle":
|
var tt = tg2.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||||
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
|
if (tt != null) { tx = tt.X; ty = tt.Y; }
|
||||||
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
|
|
||||||
translateX = deltaX;
|
|
||||||
translateY = deltaY;
|
|
||||||
break;
|
|
||||||
case "ImageTopRightHandle":
|
|
||||||
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
|
|
||||||
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
|
|
||||||
translateY = deltaY;
|
|
||||||
break;
|
|
||||||
case "ImageBottomLeftHandle":
|
|
||||||
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
|
|
||||||
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
|
|
||||||
translateX = deltaX;
|
|
||||||
break;
|
|
||||||
case "ImageBottomRightHandle":
|
|
||||||
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
|
|
||||||
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
|
|
||||||
break;
|
|
||||||
case "ImageTopHandle":
|
|
||||||
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
|
|
||||||
translateY = deltaY;
|
|
||||||
break;
|
|
||||||
case "ImageBottomHandle":
|
|
||||||
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
|
|
||||||
break;
|
|
||||||
case "ImageLeftHandle":
|
|
||||||
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
|
|
||||||
translateX = deltaX;
|
|
||||||
break;
|
|
||||||
case "ImageRightHandle":
|
|
||||||
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
centerCanvas = new Point(left + tx + (ox + visW / 2) * scaleX,
|
||||||
// 限制缩放范围
|
top + ty + (oy + visH / 2) * scaleY);
|
||||||
scaleX = Math.Max(0.1, Math.Min(scaleX, 5.0));
|
|
||||||
scaleY = Math.Max(0.1, Math.Min(scaleY, 5.0));
|
|
||||||
|
|
||||||
// 应用缩放
|
|
||||||
scaleTransform.ScaleX *= scaleX;
|
|
||||||
scaleTransform.ScaleY *= scaleY;
|
|
||||||
|
|
||||||
// 应用平移
|
|
||||||
translateTransform.X += translateX;
|
|
||||||
translateTransform.Y += translateY;
|
|
||||||
|
|
||||||
// 更新选择点位置
|
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
|
||||||
|
|
||||||
if (BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
|
||||||
UpdateImageSelectionToolbarPosition(element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double scaledW = visW * scaleX;
|
||||||
|
double scaledH = visH * scaleY;
|
||||||
|
|
||||||
|
ImageSelectionOverlay.UpdateFrame(centerCanvas, scaledW, scaledH, angle);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"根据控制点缩放图片失败: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"更新图片选中框位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double GetElementRotationAngle(FrameworkElement element)
|
||||||
|
{
|
||||||
|
if (element?.RenderTransform is TransformGroup tg)
|
||||||
|
{
|
||||||
|
var rt = tg.Children.OfType<RotateTransform>().FirstOrDefault();
|
||||||
|
if (rt != null) return rt.Angle;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visual box of the rendered content inside element.ActualWidth/Height,
|
||||||
|
// accounting for Stretch=Uniform letterboxing on Image elements.
|
||||||
|
// Returns (offsetX, offsetY, visibleW, visibleH) in base (unscaled) coords.
|
||||||
|
private static (double ox, double oy, double w, double h) GetElementVisualBox(FrameworkElement element)
|
||||||
|
{
|
||||||
|
double boxW = element.ActualWidth > 0 ? element.ActualWidth : element.Width;
|
||||||
|
double boxH = element.ActualHeight > 0 ? element.ActualHeight : element.Height;
|
||||||
|
if (double.IsNaN(boxW) || double.IsNaN(boxH) || boxW <= 0 || boxH <= 0)
|
||||||
|
return (0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (element is Image img && img.Stretch == Stretch.Uniform && img.Source is BitmapSource bs
|
||||||
|
&& bs.PixelWidth > 0 && bs.PixelHeight > 0)
|
||||||
|
{
|
||||||
|
double srcAspect = (double)bs.PixelWidth / bs.PixelHeight;
|
||||||
|
double boxAspect = boxW / boxH;
|
||||||
|
double vW, vH;
|
||||||
|
if (srcAspect > boxAspect) { vW = boxW; vH = boxW / srcAspect; }
|
||||||
|
else { vH = boxH; vW = boxH * srcAspect; }
|
||||||
|
return ((boxW - vW) / 2, (boxH - vH) / 2, vW, vH);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, 0, boxW, boxH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate a canvas-space vector back into the element's local (unrotated) space.
|
||||||
|
private Vector CanvasVectorToLocal(Vector canvasDelta, double angleDegrees)
|
||||||
|
{
|
||||||
|
double rad = -angleDegrees * Math.PI / 180.0;
|
||||||
|
double cos = Math.Cos(rad);
|
||||||
|
double sin = Math.Sin(rad);
|
||||||
|
return new Vector(canvasDelta.X * cos - canvasDelta.Y * sin,
|
||||||
|
canvasDelta.X * sin + canvasDelta.Y * cos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImageSelectionOverlay_ResizeDelta(object sender, ImageResizeDeltaEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsBitmapLikeCanvasElement(currentSelectedElement)) return;
|
||||||
|
ResizeImageByCorner(currentSelectedElement, e.CanvasDelta, e.Corner, e.LockAspectRatio);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"图片缩放失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImageSelectionOverlay_MoveDelta(object sender, ImageMoveDeltaEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (currentSelectedElement == null) return;
|
||||||
|
if (currentSelectedElement.RenderTransform is TransformGroup tg)
|
||||||
|
{
|
||||||
|
var tt = tg.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||||
|
var rt = tg.Children.OfType<RotateTransform>().FirstOrDefault();
|
||||||
|
if (tt != null)
|
||||||
|
{
|
||||||
|
tt.X += e.CanvasDelta.X;
|
||||||
|
tt.Y += e.CanvasDelta.Y;
|
||||||
|
// Keep rotation center locked to the visual center so the element
|
||||||
|
// translates rigidly instead of swinging around an old pivot.
|
||||||
|
if (rt != null)
|
||||||
|
{
|
||||||
|
rt.CenterX += e.CanvasDelta.X;
|
||||||
|
rt.CenterY += e.CanvasDelta.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateImageResizeHandlesPosition(default);
|
||||||
|
if (BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"图片拖动失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImageSelectionOverlay_RotateDelta(object sender, ImageRotateDeltaEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (currentSelectedElement == null) return;
|
||||||
|
ApplyRotateTransform(currentSelectedElement, e.AngleDelta);
|
||||||
|
UpdateImageResizeHandlesPosition(default);
|
||||||
|
if (BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"图片旋转失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResizeImageByCorner(FrameworkElement element, Vector canvasDelta,
|
||||||
|
ImageResizeCorner corner, bool lockAspect)
|
||||||
|
{
|
||||||
|
if (!(element.RenderTransform is TransformGroup transformGroup)) return;
|
||||||
|
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||||
|
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||||
|
var rotateTransform = transformGroup.Children.OfType<RotateTransform>().FirstOrDefault();
|
||||||
|
if (scaleTransform == null || translateTransform == null) return;
|
||||||
|
|
||||||
|
double angle = rotateTransform?.Angle ?? 0;
|
||||||
|
|
||||||
|
var (ox, oy, visW, visH) = GetElementVisualBox(element);
|
||||||
|
if (visW <= 0 || visH <= 0) return;
|
||||||
|
|
||||||
|
double left = InkCanvas.GetLeft(element); if (double.IsNaN(left)) left = 0;
|
||||||
|
double top = InkCanvas.GetTop(element); if (double.IsNaN(top)) top = 0;
|
||||||
|
|
||||||
|
double curW = visW * scaleTransform.ScaleX;
|
||||||
|
double curH = visH * scaleTransform.ScaleY;
|
||||||
|
|
||||||
|
// Drag delta → local (unrotated) space so corner tracks cursor under any rotation.
|
||||||
|
Vector local = CanvasVectorToLocal(canvasDelta, angle);
|
||||||
|
|
||||||
|
double newW = curW, newH = curH;
|
||||||
|
double pivotFracX = 0, pivotFracY = 0; // opposite visual corner
|
||||||
|
switch (corner)
|
||||||
|
{
|
||||||
|
case ImageResizeCorner.TopLeft:
|
||||||
|
newW = curW - local.X; newH = curH - local.Y;
|
||||||
|
pivotFracX = 1; pivotFracY = 1;
|
||||||
|
break;
|
||||||
|
case ImageResizeCorner.TopRight:
|
||||||
|
newW = curW + local.X; newH = curH - local.Y;
|
||||||
|
pivotFracX = 0; pivotFracY = 1;
|
||||||
|
break;
|
||||||
|
case ImageResizeCorner.BottomLeft:
|
||||||
|
newW = curW - local.X; newH = curH + local.Y;
|
||||||
|
pivotFracX = 1; pivotFracY = 0;
|
||||||
|
break;
|
||||||
|
case ImageResizeCorner.BottomRight:
|
||||||
|
newW = curW + local.X; newH = curH + local.Y;
|
||||||
|
pivotFracX = 0; pivotFracY = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockAspect && curW > 0 && curH > 0)
|
||||||
|
{
|
||||||
|
double uniform = Math.Min(newW / curW, newH / curH);
|
||||||
|
newW = curW * uniform;
|
||||||
|
newH = curH * uniform;
|
||||||
|
}
|
||||||
|
|
||||||
|
double newScaleX = newW / visW;
|
||||||
|
double newScaleY = newH / visH;
|
||||||
|
newScaleX = Math.Max(0.1, Math.Min(newScaleX, 10.0));
|
||||||
|
newScaleY = Math.Max(0.1, Math.Min(newScaleY, 10.0));
|
||||||
|
newW = visW * newScaleX;
|
||||||
|
newH = visH * newScaleY;
|
||||||
|
|
||||||
|
double tx = translateTransform.X;
|
||||||
|
double ty = translateTransform.Y;
|
||||||
|
|
||||||
|
// Visual pivot canvas position BEFORE the change.
|
||||||
|
// Visual box origin (pre-scale) is (ox, oy); after scale its top-left in post-scale
|
||||||
|
// space (before rotation/translate) is (ox*sx, oy*sy), size (visW*sx, visH*sy).
|
||||||
|
// Pivot pre-rotation = (tx + (ox + pivotFrac*visW) * sx, ty + (oy + pivotFrac*visH) * sy).
|
||||||
|
// Rotation center is the element visual center, which is the SAME pre-rotation anchor
|
||||||
|
// we derive below — so pre-rotation coord == canvas coord relative to (left, top) up
|
||||||
|
// to a rotation around that center. Because we rotate around the center and both
|
||||||
|
// before/after share the same center (we'll update it to newCenter below after shift),
|
||||||
|
// we must match canvas positions WITH rotation applied.
|
||||||
|
Point pivotBefore = ApplyCanvasTransform(ox + pivotFracX * visW, oy + pivotFracY * visH,
|
||||||
|
ox + visW / 2, oy + visH / 2,
|
||||||
|
scaleTransform.ScaleX, scaleTransform.ScaleY,
|
||||||
|
tx, ty, angle, left, top);
|
||||||
|
|
||||||
|
Point pivotAfter = ApplyCanvasTransform(ox + pivotFracX * visW, oy + pivotFracY * visH,
|
||||||
|
ox + visW / 2, oy + visH / 2,
|
||||||
|
newScaleX, newScaleY,
|
||||||
|
tx, ty, angle, left, top);
|
||||||
|
|
||||||
|
Vector canvasDrift = pivotBefore - pivotAfter;
|
||||||
|
translateTransform.X += canvasDrift.X;
|
||||||
|
translateTransform.Y += canvasDrift.Y;
|
||||||
|
|
||||||
|
scaleTransform.ScaleX = newScaleX;
|
||||||
|
scaleTransform.ScaleY = newScaleY;
|
||||||
|
|
||||||
|
// Update rotation center to the new visual center so future rotations behave.
|
||||||
|
if (rotateTransform != null)
|
||||||
|
{
|
||||||
|
rotateTransform.CenterX = translateTransform.X + (ox + visW / 2) * newScaleX;
|
||||||
|
rotateTransform.CenterY = translateTransform.Y + (oy + visH / 2) * newScaleY;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateImageResizeHandlesPosition(default);
|
||||||
|
if (BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
|
UpdateImageSelectionToolbarPosition(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canvas position of element-local point (lx, ly) under the given transform.
|
||||||
|
// Model: P_canvas = (left, top) + Rotate_center(S * (lx,ly) + T)
|
||||||
|
// where center = S * (cx, cy) + T, and rotation angle is angleDeg.
|
||||||
|
private static Point ApplyCanvasTransform(double lx, double ly, double cx, double cy,
|
||||||
|
double sx, double sy, double tx, double ty,
|
||||||
|
double angleDeg, double left, double top)
|
||||||
|
{
|
||||||
|
double preX = sx * lx + tx;
|
||||||
|
double preY = sy * ly + ty;
|
||||||
|
double centerX = sx * cx + tx;
|
||||||
|
double centerY = sy * cy + ty;
|
||||||
|
double rad = angleDeg * Math.PI / 180.0;
|
||||||
|
double cos = Math.Cos(rad);
|
||||||
|
double sin = Math.Sin(rad);
|
||||||
|
double relX = preX - centerX;
|
||||||
|
double relY = preY - centerY;
|
||||||
|
double rotX = relX * cos - relY * sin + centerX;
|
||||||
|
double rotY = relX * sin + relY * cos + centerY;
|
||||||
|
return new Point(left + rotX, top + rotY);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Modern.Controls;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace Ink_Canvas
|
namespace Ink_Canvas
|
||||||
@@ -10,25 +8,14 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
#region 悬浮窗拦截功能
|
#region 悬浮窗拦截功能
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化悬浮窗拦截管理器
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeFloatingWindowInterceptor()
|
private void InitializeFloatingWindowInterceptor()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_floatingWindowInterceptorManager = new FloatingWindowInterceptorManager();
|
_floatingWindowInterceptorManager = new FloatingWindowInterceptorManager();
|
||||||
|
|
||||||
// 订阅事件
|
|
||||||
_floatingWindowInterceptorManager.WindowIntercepted += OnFloatingWindowIntercepted;
|
_floatingWindowInterceptorManager.WindowIntercepted += OnFloatingWindowIntercepted;
|
||||||
_floatingWindowInterceptorManager.WindowRestored += OnFloatingWindowRestored;
|
_floatingWindowInterceptorManager.WindowRestored += OnFloatingWindowRestored;
|
||||||
|
|
||||||
// 初始化拦截器
|
|
||||||
_floatingWindowInterceptorManager.Initialize(Settings.Automation.FloatingWindowInterceptor);
|
_floatingWindowInterceptorManager.Initialize(Settings.Automation.FloatingWindowInterceptor);
|
||||||
|
|
||||||
// 加载UI状态
|
|
||||||
LoadFloatingWindowInterceptorUI();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("悬浮窗拦截管理器初始化完成", LogHelper.LogType.Event);
|
LogHelper.WriteLogToFile("悬浮窗拦截管理器初始化完成", LogHelper.LogType.Event);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -37,83 +24,11 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载悬浮窗拦截UI状态
|
|
||||||
/// </summary>
|
|
||||||
private void LoadFloatingWindowInterceptorUI()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!isLoaded) return;
|
|
||||||
|
|
||||||
// 设置主开关状态
|
|
||||||
ToggleSwitchFloatingWindowInterceptorEnabled.IsOn = Settings.Automation.FloatingWindowInterceptor.IsEnabled;
|
|
||||||
|
|
||||||
// 设置各个拦截规则的状态
|
|
||||||
foreach (var kvp in Settings.Automation.FloatingWindowInterceptor.InterceptRules)
|
|
||||||
{
|
|
||||||
var toggleName = $"ToggleSwitch{kvp.Key}";
|
|
||||||
var toggle = FindName(toggleName) as ToggleSwitch;
|
|
||||||
if (toggle != null)
|
|
||||||
{
|
|
||||||
toggle.IsOn = kvp.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新UI可见性
|
|
||||||
UpdateFloatingWindowInterceptorUI();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"加载悬浮窗拦截UI状态失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新悬浮窗拦截UI
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateFloatingWindowInterceptorUI()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var isEnabled = Settings.Automation.FloatingWindowInterceptor.IsEnabled;
|
|
||||||
FloatingWindowInterceptorGrid.Visibility = isEnabled ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
|
|
||||||
// 计算启用的规则数量
|
|
||||||
var enabledRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Where(kvp => kvp.Value).Count();
|
|
||||||
var totalRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Count;
|
|
||||||
|
|
||||||
// 更新状态文本
|
|
||||||
if (_floatingWindowInterceptorManager != null)
|
|
||||||
{
|
|
||||||
var stats = _floatingWindowInterceptorManager.GetStatistics();
|
|
||||||
TextBlockFloatingWindowInterceptorStatus.Text = stats.IsRunning
|
|
||||||
? $"拦截器运行中 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则"
|
|
||||||
: $"拦截器未启动 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TextBlockFloatingWindowInterceptorStatus.Text = $"拦截器未初始化 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"更新悬浮窗拦截UI失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口被拦截事件处理
|
|
||||||
/// </summary>
|
|
||||||
private void OnFloatingWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e)
|
private void OnFloatingWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 在UI线程中更新状态
|
Dispatcher.BeginInvoke(new Action(() => { }));
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
UpdateFloatingWindowInterceptorUI();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -121,18 +36,11 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口被恢复事件处理
|
|
||||||
/// </summary>
|
|
||||||
private void OnFloatingWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
|
private void OnFloatingWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 在UI线程中更新状态
|
Dispatcher.BeginInvoke(new Action(() => { }));
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
UpdateFloatingWindowInterceptorUI();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -144,30 +52,24 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
#region 悬浮窗拦截事件处理
|
#region 悬浮窗拦截事件处理
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 主开关切换事件
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchFloatingWindowInterceptorEnabled_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchFloatingWindowInterceptorEnabled_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Settings.Automation.FloatingWindowInterceptor.IsEnabled = ToggleSwitchFloatingWindowInterceptorEnabled.IsOn;
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null)
|
||||||
|
Settings.Automation.FloatingWindowInterceptor.IsEnabled = toggle.IsOn;
|
||||||
|
|
||||||
if (_floatingWindowInterceptorManager != null)
|
if (_floatingWindowInterceptorManager != null)
|
||||||
{
|
{
|
||||||
if (Settings.Automation.FloatingWindowInterceptor.IsEnabled)
|
if (Settings.Automation.FloatingWindowInterceptor.IsEnabled)
|
||||||
{
|
|
||||||
_floatingWindowInterceptorManager.Start();
|
_floatingWindowInterceptorManager.Start();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
_floatingWindowInterceptorManager.Stop();
|
_floatingWindowInterceptorManager.Stop();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFloatingWindowInterceptorUI();
|
|
||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -176,129 +78,98 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃白板3拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoWhiteboard3Floating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoWhiteboard3Floating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, ToggleSwitchSeewoWhiteboard3Floating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃白板5拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoWhiteboard5Floating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoWhiteboard5Floating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, ToggleSwitchSeewoWhiteboard5Floating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃白板5C拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoWhiteboard5CFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoWhiteboard5CFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, ToggleSwitchSeewoWhiteboard5CFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃品课侧栏拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoPincoSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoPincoSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, ToggleSwitchSeewoPincoSideBarFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃品课画笔拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoPincoDrawingFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoPincoDrawingFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, ToggleSwitchSeewoPincoDrawingFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃PPT小工具拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoPPTFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoPPTFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, ToggleSwitchSeewoPPTFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// AiClass拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchAiClassFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchAiClassFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, ToggleSwitchAiClassFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 鸿合屏幕书写拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchHiteAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchHiteAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, ToggleSwitchHiteAnnotationFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 畅言智慧课堂拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchChangYanFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchChangYanFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, ToggleSwitchChangYanFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 畅言PPT拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchChangYanPptFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchChangYanPptFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, ToggleSwitchChangYanPptFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 天喻教育云拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchIntelligentClassFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchIntelligentClassFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, ToggleSwitchIntelligentClassFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃桌面画笔拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoDesktopAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoDesktopAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, ToggleSwitchSeewoDesktopAnnotationFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 希沃桌面侧栏拦截开关
|
|
||||||
/// </summary>
|
|
||||||
private void ToggleSwitchSeewoDesktopSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchSeewoDesktopSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, ToggleSwitchSeewoDesktopSideBarFloating.IsOn);
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, toggle.IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
||||||
/// 设置拦截规则
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">拦截类型</param>
|
|
||||||
/// <param name="enabled">是否启用拦截</param>
|
|
||||||
private void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -307,18 +178,15 @@ namespace Ink_Canvas
|
|||||||
_floatingWindowInterceptorManager.SetInterceptRule(type, enabled);
|
_floatingWindowInterceptorManager.SetInterceptRule(type, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新设置
|
|
||||||
var ruleName = type.ToString();
|
var ruleName = type.ToString();
|
||||||
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(ruleName))
|
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(ruleName))
|
||||||
{
|
{
|
||||||
Settings.Automation.FloatingWindowInterceptor.InterceptRules[ruleName] = enabled;
|
Settings.Automation.FloatingWindowInterceptor.InterceptRules[ruleName] = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取规则信息以处理父子关系
|
|
||||||
var rule = _floatingWindowInterceptorManager?.GetInterceptRule(type);
|
var rule = _floatingWindowInterceptorManager?.GetInterceptRule(type);
|
||||||
if (rule != null)
|
if (rule != null)
|
||||||
{
|
{
|
||||||
// 如果是父规则,更新所有子规则的设置
|
|
||||||
if (rule.ChildTypes.Count > 0)
|
if (rule.ChildTypes.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var childType in rule.ChildTypes)
|
foreach (var childType in rule.ChildTypes)
|
||||||
@@ -330,7 +198,6 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果是子规则,更新父规则的设置
|
|
||||||
else if (rule.ParentType.HasValue)
|
else if (rule.ParentType.HasValue)
|
||||||
{
|
{
|
||||||
var parentRule = _floatingWindowInterceptorManager?.GetInterceptRule(rule.ParentType.Value);
|
var parentRule = _floatingWindowInterceptorManager?.GetInterceptRule(rule.ParentType.Value);
|
||||||
@@ -345,9 +212,6 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新UI显示
|
|
||||||
UpdateFloatingWindowInterceptorUI();
|
|
||||||
|
|
||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -357,4 +221,4 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,8 +104,12 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">发送者</param>
|
/// <param name="sender">发送者</param>
|
||||||
/// <param name="e">执行路由事件参数</param>
|
/// <param name="e">执行路由事件参数</param>
|
||||||
private void KeyChangeToDrawTool(object sender, ExecutedRoutedEventArgs e)
|
private async void KeyChangeToDrawTool(object sender, ExecutedRoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (isFloatingBarFolded)
|
||||||
|
{
|
||||||
|
await UnFoldFloatingBar(new object());
|
||||||
|
}
|
||||||
PenIcon_Click(lastBorderMouseDownObject, null);
|
PenIcon_Click(lastBorderMouseDownObject, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,15 +154,17 @@ namespace Ink_Canvas
|
|||||||
/// <param name="sender">发送者</param>
|
/// <param name="sender">发送者</param>
|
||||||
/// <param name="e">执行路由事件参数</param>
|
/// <param name="e">执行路由事件参数</param>
|
||||||
/// <remarks>仅当画布控件面板可见时生效,根据当前橡皮擦状态选择相应的橡皮擦模式</remarks>
|
/// <remarks>仅当画布控件面板可见时生效,根据当前橡皮擦状态选择相应的橡皮擦模式</remarks>
|
||||||
private void KeyChangeToEraser(object sender, ExecutedRoutedEventArgs e)
|
private async void KeyChangeToEraser(object sender, ExecutedRoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (StackPanelCanvasControls.Visibility == Visibility.Visible)
|
if (isFloatingBarFolded)
|
||||||
{
|
{
|
||||||
if (Eraser_Icon.Background != null)
|
await UnFoldFloatingBar(new object());
|
||||||
EraserIconByStrokes_Click(lastBorderMouseDownObject, null);
|
|
||||||
else
|
|
||||||
EraserIcon_Click(lastBorderMouseDownObject, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Eraser_Icon.Background != null)
|
||||||
|
EraserIconByStrokes_Click(lastBorderMouseDownObject, null);
|
||||||
|
else
|
||||||
|
EraserIcon_Click(lastBorderMouseDownObject, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -176,5 +176,65 @@ namespace Ink_Canvas
|
|||||||
/// <remarks>老版浮动栏中用于表示套索选择工具的图标</remarks>
|
/// <remarks>老版浮动栏中用于表示套索选择工具的图标</remarks>
|
||||||
public static string LegacySolidLassoSelectIcon =
|
public static string LegacySolidLassoSelectIcon =
|
||||||
"F1 M24,24z M0,0z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83888,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.77649,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9178 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z";
|
"F1 M24,24z M0,0z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83888,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.77649,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9178 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z";
|
||||||
|
public static string FoldIcon =
|
||||||
|
"F1 M24,24z M0,0z M19.1634,15.0537C18.9999,15.0537 18.8278,15.0021 18.6902,14.8902 18.3632,14.6321 18.3116,14.1589 18.5783,13.832 19.0343,13.2642 19.4387,12.6447 19.7656,11.9909 19.172,10.8208 16.763,6.72556 11.9967,6.72556 11.6612,6.72556 11.3256,6.75137 10.9987,6.78579 10.5943,6.83741 10.2072,6.54489 10.1556,6.13193 10.1039,5.71896 10.3965,5.34041 10.8094,5.28879 11.1966,5.23717 11.5923,5.21136 11.9967,5.21136 18.4407,5.21136 21.1766,11.423 21.297,11.6897 21.383,11.8876 21.3744,12.1113 21.2884,12.3006 20.8754,13.1781 20.3592,14.0041 19.757,14.7612 19.6107,14.9418 19.387,15.0451 19.172,15.0451L19.1634,15.0537z M10.689,15.1483C11.0847,15.3118,11.5063,15.3892,11.9365,15.3892L11.9451,15.4064 12.0053,15.4064C12.4613,15.3978 12.8915,15.2946 13.3044,15.1139 13.4763,15.0387 13.6392,14.9486 13.7931,14.8455L15.4197,16.472 16.533,17.5854 20.0753,21.1277C20.2216,21.2826,20.4195,21.3514,20.6087,21.3514L20.6001,21.36C20.7894,21.36 20.9873,21.2826 21.1335,21.1363 21.4261,20.8438 21.4261,20.362 21.1335,20.0695L3.92668,2.86262C3.63416,2.5615 3.16958,2.57011 2.86846,2.86262 2.56734,3.15514 2.56734,3.62833 2.86846,3.92085L6.21371,7.2661C4.72738,8.44444 3.5311,9.95577 2.71359,11.6725 2.62756,11.8704 2.61896,12.0941 2.70499,12.292 2.81684,12.5587 5.55273,18.7704 12.0053,18.7704L12.0311,18.7704C13.6161,18.7704,15.1606,18.3654,16.533,17.5854L15.4197,16.472C14.3708,16.9906,13.2144,17.2648,12.0311,17.2648L12.0053,17.2648C7.239,17.2648 4.83004,13.1695 4.2364,11.9995 4.96897,10.5725 6.01289,9.32147 7.29011,8.3425L9.15767,10.2101C9.05523,10.3645 8.96629,10.5279 8.89086,10.7003 8.71018,11.1133 8.60694,11.5521 8.60694,11.9995 8.59834,12.4468 8.67577,12.8856 8.84784,13.3072 9.0113,13.7288 9.26081,14.0987 9.57913,14.417 9.89746,14.7354 10.2674,14.9763 10.689,15.1483z";
|
||||||
|
public static string DeleteIcon =
|
||||||
|
"F1 M24,24z M0,0z M8.53333,2.37333L7.92,4.21333 2.56,4.21333C2.4,4.21333 2.26667,4.27556 2.16,4.4 2.05333,4.50667 2,4.63111 2,4.77333L2,5.89333C2,6.03556 2.05333,6.16889 2.16,6.29333 2.26667,6.4 2.4,6.45333 2.56,6.45333L21.44,6.45333C21.6,6.45333 21.7333,6.4 21.84,6.29333 21.9467,6.16889 22,6.03556 22,5.89333L22,4.77333C22,4.63111 21.9467,4.50667 21.84,4.4 21.7333,4.27556 21.6,4.21333 21.44,4.21333L16.08,4.21333 15.4667,2.37333C15.4311,2.26667 15.36,2.17778 15.2533,2.10667 15.1644,2.03556 15.0578,2 14.9333,2L9.06667,2C8.94222,2 8.82667,2.03556 8.72,2.10667 8.63111,2.17778 8.56889,2.26667 8.53333,2.37333z M19.7867,8.10667L18.7467,20.9867C18.7111,21.2711 18.5867,21.5111 18.3733,21.7067 18.1778,21.9022 17.9378,22 17.6533,22L6.37333,22C6.08889,22 5.84,21.9022 5.62667,21.7067 5.41333,21.5111 5.28889,21.2711 5.25333,20.9867L4.21333,8.10667 19.7867,8.10667z M8.66667,12.56L8.66667,16.9867C8.66667,17.1467 8.72,17.28 8.82667,17.3867 8.93333,17.4933 9.06667,17.5467 9.22667,17.5467L9.78667,17.5467C9.92889,17.5467 10.0533,17.4933 10.16,17.3867 10.2844,17.28 10.3467,17.1467 10.3467,16.9867L10.3467,12.56C10.3467,12.4 10.2844,12.2667 10.16,12.16 10.0533,12.0533 9.92889,12 9.78667,12L9.22667,12C9.06667,12 8.93333,12.0533 8.82667,12.16 8.72,12.2667 8.66667,12.4 8.66667,12.56z M14.2133,12C14.0711,12 13.9467,12.0533 13.84,12.16 13.7333,12.2667 13.68,12.4 13.68,12.56L13.68,16.9867C13.68,17.1467 13.7333,17.28 13.84,17.3867 13.9467,17.4933 14.0711,17.5467 14.2133,17.5467L14.7733,17.5467C14.9333,17.5467 15.0667,17.4933 15.1733,17.3867 15.28,17.28 15.3333,17.1467 15.3333,16.9867L15.3333,12.56C15.3333,12.4 15.28,12.2667 15.1733,12.16 15.0667,12.0533 14.9333,12 14.7733,12L14.2133,12z";
|
||||||
|
public static string ShapesIcon =
|
||||||
|
"F1 M24,24z M0,0z M8.7269,2.9067C8.84872,2.70875 9.00852,2.57931 9.20647,2.5184 9.41966,2.44227 9.62541,2.43465 9.82337,2.49556 10.0365,2.55647 10.2039,2.67829 10.3257,2.86102L13.9118,8.13731C12.9068,8.47231 12.0084,8.99766 11.2165,9.71334 10.4247,10.4138 9.8157,11.2513 9.38933,12.2259 8.93251,13.2156 8.70413,14.2587 8.70413,15.3551L8.7269,15.9946 2.46851,15.9946C2.22487,15.9946 2.01158,15.9185 1.82885,15.7662 1.66135,15.614 1.55481,15.4236 1.50913,15.1952 1.47868,14.9668 1.5244,14.746 1.64622,14.5328L8.7269,2.9067z M16.3329,21.545C15.2061,21.545 14.1632,21.2633 13.2038,20.6999 12.275,20.1517 11.5288,19.4056 10.9654,18.4615 10.4172,17.5022 10.1431,16.4667 10.1431,15.3551 10.1431,14.2283 10.4172,13.1928 10.9654,12.2487 11.5288,11.3046 12.2825,10.5585 13.2266,10.0103 14.1707,9.44687 15.1986,9.16516 16.3102,9.16516 17.437,9.16516 18.4724,9.44687 19.4165,10.0103 20.3606,10.5585 21.1068,11.3046 21.6549,12.2487 22.2184,13.1928 22.5,14.2283 22.5,15.3551 22.5,16.4667 22.2184,17.5022 21.6549,18.4615 21.1068,19.4056 20.3683,20.1517 19.4395,20.6999 18.4801,21.2633 17.4445,21.545 16.3329,21.545z";
|
||||||
|
public static string UndoIcon =
|
||||||
|
"F1 M24,24z M0,0z M10.3344,5.02734L10.3605,8.62849C12.2431,8.87029 14.0305,9.44026 15.7232,10.3384 17.4676,11.2538 18.9187,12.4196 20.0759,13.8359 21.3367,15.3731 22.1398,17.0657 22.4852,18.9138 22.5197,19.1211 22.4938,19.3197 22.4075,19.5097 22.3211,19.6997 22.2003,19.8292 22.0448,19.8983 21.8894,19.9501 21.7423,19.9069 21.6042,19.7687 20.7579,18.8361 19.6268,17.9984 18.2105,17.2557 16.9324,16.5821 15.5591,16.0553 14.091,15.6754 12.7093,15.3126 11.4659,15.1313 10.3605,15.1313L10.3344,18.862C10.3344,19.2938 10.2395,19.6047 10.0495,19.7947 9.8941,19.9501 9.69547,20.0106 9.45367,19.976 9.21187,19.9242 8.99586,19.8119 8.80587,19.6392 8.14955,18.9829 6.90606,17.7739 5.07526,16.0121 3.46899,14.475 2.41544,13.4559 1.91456,12.9551 1.63821,12.6787 1.5,12.3678 1.5,12.0224 1.51727,11.6769 1.67272,11.3574 1.96634,11.0638 4.02168,9.00847 6.3103,6.73724 8.83197,4.25011 9.00468,4.07739 9.20331,3.99967 9.42784,4.01694 9.66965,4.01694 9.87683,4.11194 10.0495,4.30193 10.2395,4.47464 10.3344,4.71645 10.3344,5.02734z";
|
||||||
|
public static string RedoIcon =
|
||||||
|
"F1 M24,24z M0,0z M13.6656,5.02734L13.6395,8.62849C11.7569,8.87029 9.96948,9.44026 8.27685,10.3384 6.5324,11.2538 5.08134,12.4196 3.92413,13.8359 2.6633,15.3731 1.86023,17.0657 1.5148,18.9138 1.48026,19.1211 1.50619,19.3197 1.59255,19.5097 1.6789,19.6997 1.79974,19.8292 1.95518,19.8983 2.11063,19.9501 2.25766,19.9069 2.39583,19.7687 3.24215,18.8361 4.37323,17.9984 5.78951,17.2557 7.06761,16.5821 8.44089,16.0553 9.90899,15.6754 11.2907,15.3126 12.5341,15.1313 13.6395,15.1313L13.6656,18.862C13.6656,19.2938 13.7605,19.6047 13.9505,19.7947 14.1059,19.9501 14.3045,20.0106 14.5463,19.976 14.7881,19.9242 15.0041,19.8119 15.1941,19.6392 15.8505,18.9829 17.0939,17.7739 18.9247,16.0121 20.531,14.475 21.5846,13.4559 22.0854,12.9551 22.3618,12.6787 22.5,12.3678 22.5,12.0224 22.4827,11.6769 22.3273,11.3574 22.0337,11.0638 19.9783,9.00847 17.6897,6.73724 15.168,4.25011 14.9953,4.07739 14.7967,3.99967 14.5722,4.01694 14.3304,4.01694 14.1232,4.11194 13.9505,4.30193 13.7605,4.47464 13.6656,4.71645 13.6656,5.02734z";
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠并清
|
||||||
|
/// </summary>
|
||||||
|
public static string CursorWithDelFloatingBarBtnIcon =
|
||||||
|
"F0 M24,24z M0,0z M11.5007,11.0214C11.5007,10.674,11.7823,10.3923,12.1298,10.3923L14.2967,10.3923 14.2967,9.90299C14.2967,9.41878 14.5325,8.98723 14.8305,8.68924 15.1285,8.39124 15.56,8.15547 16.0442,8.15547L18.281,8.15547C18.7652,8.15547 19.1968,8.39124 19.4948,8.68924 19.7928,8.98723 20.0286,9.41878 20.0286,9.90299L20.0286,10.3923 22.1955,10.3923C22.5429,10.3923 22.8246,10.674 22.8246,11.0214 22.8246,11.3688 22.5429,11.6505 22.1955,11.6505L21.7062,11.6505 21.7062,18.8503C21.7062,19.3345 21.4704,19.766 21.1724,20.064 20.8744,20.362 20.4429,20.5978 19.9587,20.5978L14.3666,20.5978C13.8824,20.5978 13.4508,20.362 13.1528,20.064 12.8549,19.766 12.6191,19.3345 12.6191,18.8503L12.6191,11.6505 12.1298,11.6505C11.7823,11.6505,11.5007,11.3688,11.5007,11.0214z M14.0425,19.1743C13.9211,19.0529,13.8773,18.9253,13.8773,18.8503L13.8773,11.6505 20.448,11.6505 20.448,18.8503C20.448,18.9253 20.4041,19.0529 20.2827,19.1743 20.1613,19.2958 20.0337,19.3396 19.9587,19.3396L14.3666,19.3396C14.2916,19.3396,14.1639,19.2958,14.0425,19.1743z M15.5549,9.90299C15.5549,9.82799 15.5987,9.70034 15.7202,9.57893 15.8416,9.45752 15.9692,9.41368 16.0442,9.41368L18.281,9.41368C18.356,9.41368 18.4837,9.45752 18.6051,9.57893 18.7265,9.70034 18.7703,9.82799 18.7703,9.90299L18.7703,10.3923 15.5549,10.3923 15.5549,9.90299z M18.9101,13.8174C18.9101,13.47 18.6285,13.1883 18.281,13.1883 17.9336,13.1883 17.6519,13.47 17.6519,13.8174L17.6519,17.1727C17.6519,17.5201 17.9336,17.8018 18.281,17.8018 18.6285,17.8018 18.9101,17.5201 18.9101,17.1727L18.9101,13.8174z M16.6733,13.8174C16.6733,13.47 16.3917,13.1883 16.0442,13.1883 15.6968,13.1883 15.4151,13.47 15.4151,13.8174L15.4151,17.1727C15.4151,17.5201 15.6968,17.8018 16.0442,17.8018 16.3917,17.8018 16.6733,17.5201 16.6733,17.1727L16.6733,13.8174z M1.32567,3.55246C1.47265,3.40549,1.69379,3.36174,1.88566,3.44167L11.8342,7.58639C12.0311,7.66845 12.1567,7.86389 12.1495,8.07716 12.1424,8.29043 12.0039,8.47698 11.8018,8.54561L8.25047,9.75183 11.4309,12.9323C11.6313,13.1326 11.6313,13.4574 11.4309,13.6577 11.2306,13.8581 10.9058,13.8581 10.7055,13.6577L7.52503,10.4773 6.31881,14.0286C6.25019,14.2307 6.06364,14.3692 5.85037,14.3763 5.6371,14.3835 5.44166,14.2579 5.3596,14.0609L1.21488,4.11245C1.13494,3.92058,1.1787,3.69944,1.32567,3.55246z";
|
||||||
|
/// <summary>
|
||||||
|
/// 白板
|
||||||
|
/// </summary>
|
||||||
|
public static string WhiteboardFloatingBarBtnIcon =
|
||||||
|
"F0 M24,24z M0,0z M5,5.875C4.70163,5.875 4.41548,5.99353 4.2045,6.2045 3.99353,6.41548 3.875,6.70163 3.875,7L3.875,17C3.875,17.2984 3.99353,17.5845 4.2045,17.7955 4.41548,18.0065 4.70163,18.125 5,18.125L8,18.125C8.48325,18.125 8.875,18.5168 8.875,19 8.875,19.4832 8.48325,19.875 8,19.875L5,19.875C4.2375,19.875 3.50623,19.5721 2.96707,19.0329 2.4279,18.4938 2.125,17.7625 2.125,17L2.125,7C2.125,6.2375 2.4279,5.50624 2.96707,4.96707 3.50624,4.4279 4.2375,4.125 5,4.125L19,4.125C19.7625,4.125 20.4938,4.4279 21.0329,4.96707 21.5721,5.50623 21.875,6.2375 21.875,7L21.875,18C21.875,18.4973 21.6775,18.9742 21.3258,19.3258 20.9742,19.6775 20.4973,19.875 20,19.875 19.5168,19.875 19.125,19.4832 19.125,19 19.125,18.5168 19.5168,18.125 20,18.125 20.0332,18.125 20.0649,18.1118 20.0884,18.0884 20.1118,18.0649 20.125,18.0332 20.125,18L20.125,7C20.125,6.70163 20.0065,6.41548 19.7955,6.2045 19.5845,5.99353 19.2984,5.875 19,5.875L5,5.875z M10.6742,15.6742C11.0258,15.3225,11.5027,15.125,12,15.125L16,15.125C16.4973,15.125 16.9742,15.3225 17.3258,15.6742 17.6775,16.0258 17.875,16.5027 17.875,17L17.875,18C17.875,18.4973 17.6775,18.9742 17.3258,19.3258 16.9742,19.6775 16.4973,19.875 16,19.875L12,19.875C11.5027,19.875 11.0258,19.6775 10.6742,19.3258 10.3225,18.9742 10.125,18.4973 10.125,18L10.125,17C10.125,16.5027,10.3225,16.0258,10.6742,15.6742z";
|
||||||
|
public static string ToolsFloatingBarBtnIcon =
|
||||||
|
"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";
|
||||||
|
|
||||||
|
public static string ClipGeometry24x24 = "M0,0 V24 H24 V0 H0 Z";
|
||||||
|
|
||||||
|
public static string ClearInkIconGeometry = "F0 M24,24z M0,0z M10.1573,10.0421C10.7298,10.0421,11.1938,10.5062,11.1938,11.0787L11.1938,16.6067C11.1938,17.1792,10.7298,17.6433,10.1573,17.6433,9.58485,17.6433,9.12079,17.1792,9.12079,16.6067L9.12079,11.0787C9.12079,10.5062,9.58485,10.0421,10.1573,10.0421z M13.8427,10.0421C14.4151,10.0421,14.8792,10.5062,14.8792,11.0787L14.8792,16.6067C14.8792,17.1792,14.4151,17.6433,13.8427,17.6433,13.2702,17.6433,12.8062,17.1792,12.8062,16.6067L12.8062,11.0787C12.8062,10.5062,13.2702,10.0421,13.8427,10.0421z M3.70787,5.43539C3.13541,5.43539,2.67135,5.89946,2.67135,6.47191,2.67135,7.04436,3.13541,7.50843,3.70787,7.50843L4.51405,7.50843,4.51405,19.3708C4.51405,20.1686,4.9025,20.8796,5.39348,21.3706,5.88445,21.8615,6.59548,22.25,7.39326,22.25L16.6067,22.25C17.4045,22.25,18.1155,21.8615,18.6065,21.3706,19.0975,20.8796,19.486,20.1686,19.486,19.3708L19.486,7.50843,20.2921,7.50843C20.8646,7.50843,21.3287,7.04436,21.3287,6.47191,21.3287,5.89946,20.8646,5.43539,20.2921,5.43539L16.7219,5.43539,16.7219,4.62921C16.7219,3.83143,16.3335,3.12041,15.8425,2.62943,15.3515,2.13845,14.6405,1.75,13.8427,1.75L10.1573,1.75C9.35952,1.75,8.6485,2.13845,8.15752,2.62943,7.66654,3.12041,7.27809,3.83143,7.27809,4.62921L7.27809,5.43539,3.70787,5.43539z M6.58708,19.3708C6.58708,19.4944,6.6593,19.7047,6.85933,19.9047,7.05937,20.1047,7.26969,20.177,7.39326,20.177L16.6067,20.177C16.7303,20.177,16.9406,20.1047,17.1407,19.9047,17.3407,19.7047,17.4129,19.4944,17.4129,19.3708L17.4129,7.50843,6.58708,7.50843,6.58708,19.3708z M9.62338,4.09529C9.42334,4.29532,9.35112,4.50565,9.35112,4.62921L9.35112,5.43539,14.6489,5.43539,14.6489,4.62921C14.6489,4.50565,14.5767,4.29532,14.3766,4.09529,14.1766,3.89525,13.9663,3.82303,13.8427,3.82303L10.1573,3.82303C10.0337,3.82303,9.82341,3.89525,9.62338,4.09529z";
|
||||||
|
|
||||||
|
public static string TimerIconGeometry = "F0 M24,24z M0,0z M12,3C11.7613,3,11.5324,3.09482,11.3636,3.2636,11.1948,3.43239,11.1,3.6613,11.1,3.9L11.1,6.6C11.1,7.09705,11.5029,7.5,12,7.5,12.4971,7.5,12.9,7.09705,12.9,6.6L12.9,4.85646C14.3196,5.0353,15.6613,5.63436,16.7472,6.58675,18.0606,7.73848,18.9104,9.32838,19.1384,11.0602,19.3664,12.792,18.957,14.5477,17.9865,16.0001,17.0162,17.4525,15.5508,18.5026,13.8635,18.9547,12.1762,19.4067,10.3822,19.2301,8.81552,18.4575,7.24888,17.6849,6.01653,16.3691,5.34806,14.7554,4.67961,13.1415,4.62062,11.3397,5.1821,9.68563,5.49617,8.76044,5.99235,7.91676,6.63344,7.2,6.96481,6.8295,6.93309,6.26054,6.5626,5.92917,6.19212,5.59781,5.62315,5.62952,5.29179,6.00001,4.49049,6.8959,3.87021,7.95054,3.47763,9.10704,2.77577,11.1746,2.84951,13.4269,3.68509,15.4441,4.52067,17.4614,6.0611,19.1061,8.0194,20.0718,9.97769,21.0375,12.2203,21.2585,14.3294,20.6933,16.4384,20.1282,18.2701,18.8156,19.4832,17.0001,20.6963,15.1847,21.208,12.9901,20.923,10.8252,20.638,8.66047,19.5758,6.67311,17.9341,5.23344,16.2925,3.79377,14.1835,3,12,3z M9.0364,7.7636C8.68492,7.41213,8.11508,7.41213,7.7636,7.7636,7.41213,8.11508,7.41213,8.68492,7.7636,9.0364L10.2609,11.5338C10.2212,11.6825,10.2,11.8387,10.2,12,10.2,12.9941,11.0059,13.8,12,13.8,12.9941,13.8,13.8,12.9941,13.8,12,13.8,11.0059,12.9941,10.2,12,10.2,11.8387,10.2,11.6825,10.2212,11.5338,10.2609L9.0364,7.7636z";
|
||||||
|
|
||||||
|
public static string RandomDrawIconGeometry = "F0 M24,24z M0,0z M15.9475,4.75965C15.7358,5.18305,15.9074,5.69791,16.3308,5.90961L16.9034,6.19589C14.0673,6.82039,11.7039,8.89176,10.7582,11.729,9.8511,14.4502,7.3045,16.2857,4.43608,16.2857L3.85713,16.2857C3.38376,16.2857,3,16.6694,3,17.1428,3,17.6162,3.38376,17.9999,3.85713,17.9999L4.43608,17.9999C8.04237,17.9999,11.2441,15.6923,12.3845,12.271,13.2915,9.54985,15.8381,7.71436,18.7066,7.71436L20.124,7.71436C20.1661,7.71524,20.208,7.71301,20.2494,7.70777,20.4903,7.67729,20.7143,7.54515,20.8554,7.33338,20.9116,7.24952,20.9533,7.15519,20.9771,7.05396,21.001,6.95301,21.0059,6.85024,20.9932,6.75036,20.9627,6.50938,20.8304,6.28523,20.6184,6.14418,20.5838,6.12101,20.5474,6.1003,20.5094,6.08229L17.0974,4.37632C16.674,4.16462,16.1592,4.33624,15.9475,4.75965z M20.8557,16.667C20.9116,16.7507,20.9533,16.8447,20.977,16.9456,21.001,17.0467,21.0059,17.1497,20.9932,17.2498,20.9626,17.4908,20.8303,17.7148,20.6185,17.8558,20.5838,17.8791,20.5474,17.8997,20.5094,17.9177L17.0975,19.6237C16.674,19.8354,16.1592,19.6638,15.9475,19.2404,15.7358,18.817,15.9074,18.3022,16.3308,18.0904L17.0491,17.7313C15.7728,17.4079,14.5941,16.8029,13.5952,15.9737,13.231,15.6713,13.1809,15.1308,13.4833,14.7667,13.7857,14.4025,14.3261,14.3523,14.6903,14.6547,15.9326,15.6862,17.5188,16.2857,19.2067,16.2857L20.1241,16.2857C20.1662,16.2848,20.2081,16.287,20.2495,16.2923,20.4905,16.3228,20.7146,16.455,20.8557,16.667z M3,6.85722C3,6.38384,3.38376,6.00009,3.85713,6.00009L4.36455,6.00009C6.25231,6.00009,8.03834,6.60414,9.50119,7.6588,9.88517,7.93565,9.972,8.47136,9.69519,8.85534,9.41835,9.23934,8.88264,9.32619,8.49866,9.04935,7.32072,8.2001,5.88391,7.71436,4.36455,7.71436L3.85713,7.71436C3.38376,7.71436,3,7.3306,3,6.85722z";
|
||||||
|
|
||||||
|
public static string SingleDrawIconGeometry = "F0 M24,24z M0,0z M3.9,9.3C4.39706,9.3,4.8,8.89706,4.8,8.4L4.8,4.8,8.4,4.8C8.89705,4.8,9.3,4.39706,9.3,3.9,9.3,3.40295,8.89705,3,8.4,3L3.9,3C3.6613,3,3.43239,3.09482,3.2636,3.2636,3.09482,3.43239,3,3.6613,3,3.9L3,8.4C3,8.89706,3.40295,9.3,3.9,9.3z M3.9,14.7C4.39706,14.7,4.8,15.1029,4.8,15.6L4.8,19.2,8.4,19.2C8.89705,19.2,9.3,19.6029,9.3,20.1,9.3,20.5971,8.89705,21,8.4,21L3.9,21C3.6613,21,3.43239,20.9051,3.2636,20.7364,3.09482,20.5676,3,20.3387,3,20.1L3,15.6C3,15.1029,3.40295,14.7,3.9,14.7z M20.1,9.3C19.6029,9.3,19.2,8.89706,19.2,8.4L19.2,4.8,15.6,4.8C15.1029,4.8,14.7,4.39706,14.7,3.9,14.7,3.40295,15.1029,3,15.6,3L20.1,3C20.3387,3,20.5676,3.09482,20.7364,3.2636,20.9051,3.43239,21,3.6613,21,3.9L21,8.4C21,8.89706,20.5971,9.3,20.1,9.3z M20.1,14.7C19.6029,14.7,19.2,15.1029,19.2,15.6L19.2,19.2,15.6,19.2C15.1029,19.2,14.7,19.6029,14.7,20.1,14.7,20.5971,15.1029,21,15.6,21L20.1,21C20.3387,21,20.5676,20.9051,20.7364,20.7364,20.9051,20.5676,21,20.3387,21,20.1L21,15.6C21,15.1029,20.5971,14.7,20.1,14.7z M8.4,10.2C8.4,8.21177,10.0118,6.6,12,6.6,13.9882,6.6,15.6,8.21177,15.6,10.2,15.6,12.1882,13.9882,13.8,12,13.8,10.0118,13.8,8.4,12.1882,8.4,10.2z M12,8.4C11.0059,8.4,10.2,9.20589,10.2,10.2,10.2,11.1941,11.0059,12,12,12,12.9941,12,13.8,11.1941,13.8,10.2,13.8,9.20589,12.9941,8.4,12,8.4z M7.5,16.5C7.5,16.0029,7.90295,15.6,8.4,15.6L15.6,15.6C16.0971,15.6,16.5,16.0029,16.5,16.5,16.5,16.9971,16.0971,17.4,15.6,17.4L8.4,17.4C7.90295,17.4,7.5,16.9971,7.5,16.5z";
|
||||||
|
|
||||||
|
public static string SaveIconGeometry = "F0 M24,24z M0,0z M6,5C5.73478,5,5.48043,5.10536,5.29289,5.29289,5.10536,5.48043,5,5.73478,5,6L5,18C5,18.2652,5.10536,18.5196,5.29289,18.7071,5.48043,18.8946,5.73478,19,6,19L18,19C18.2652,19,18.5196,18.8946,18.7071,18.7071,18.8946,18.5196,19,18.2652,19,18L19,8.41421,15.5858,5,15,5,15,8C15,8.55228,14.5523,9,14,9L8,9C7.44772,9,7,8.55228,7,8L7,5,6,5z M8,3L6,3C5.20435,3,4.44129,3.31607,3.87868,3.87868,3.31607,4.44129,3,5.20435,3,6L3,18C3,18.7956,3.31607,19.5587,3.87868,20.1213,4.44129,20.6839,5.20435,21,6,21L18,21C18.7957,21,19.5587,20.6839,20.1213,20.1213,20.6839,19.5587,21,18.7957,21,18L21,8C21,7.73478,20.8946,7.48043,20.7071,7.29289L16.7071,3.29289C16.5196,3.10536,16.2652,3,16,3L14,3,8,3z M9,5L9,7,13,7,13,5,9,5z M9.87868,11.8787C10.4413,11.3161,11.2043,11,12,11,12.7957,11,13.5587,11.3161,14.1213,11.8787,14.6839,12.4413,15,13.2043,15,14,15,14.7957,14.6839,15.5587,14.1213,16.1213,13.5587,16.6839,12.7957,17,12,17,11.2043,17,10.4413,16.6839,9.87868,16.1213,9.31607,15.5587,9,14.7957,9,14,9,13.2043,9.31607,12.4413,9.87868,11.8787z M12,13C11.7348,13,11.4804,13.1054,11.2929,13.2929,11.1054,13.4804,11,13.7348,11,14,11,14.2652,11.1054,14.5196,11.2929,14.7071,11.4804,14.8946,11.7348,15,12,15,12.2652,15,12.5196,14.8946,12.7071,14.7071,12.8946,14.5196,13,14.2652,13,14,13,13.7348,12.8946,13.4804,12.7071,13.2929,12.5196,13.1054,12.2652,13,12,13z";
|
||||||
|
|
||||||
|
public static string OpenIconGeometry = "F0 M24,24z M0,0z M5,5C4.73478,5,4.48043,5.10536,4.29289,5.29289,4.10536,5.48043,4,5.73478,4,6L4,17C4,17.2652,4.10536,17.5196,4.29289,17.7071,4.32236,17.7366,4.35349,17.764,4.38604,17.7893L6.82062,11.298C6.82065,11.2979,6.8206,11.2981,6.82062,11.298,6.96351,10.9169,7.21932,10.5883,7.55377,10.3564,7.88827,10.1245,8.28558,10.0002,8.69262,10L20,10,20,9C20,8.73478,19.8946,8.48043,19.7071,8.29289,19.5196,8.10536,19.2652,8,19,8L12,8C11.7348,8,11.4804,7.89464,11.2929,7.70711L8.58579,5,5,5z M20.9992,12L8.69338,12,6.44307,18,19.0257,18C19.0258,18,19.0256,18,19.0257,18,19.2583,17.9999,19.4838,17.9187,19.663,17.7705,19.8422,17.6222,19.9641,17.416,20.0077,17.1875L20.9992,12z M22,10.2682C22.1988,10.383,22.3767,10.5315,22.5256,10.7073,22.7133,10.9288,22.8504,11.1885,22.9276,11.4684,23.0048,11.7483,23.0201,12.0416,22.9725,12.328L22.9682,12.3517,21.9723,17.5625C21.8414,18.248,21.4756,18.8665,20.9379,19.3114,20.4002,19.7563,19.7242,19.9998,19.0263,20L5,20C4.20435,20,3.44129,19.6839,2.87868,19.1213,2.31607,18.5587,2,17.7956,2,17L2,6C2,5.20435,2.31607,4.44129,2.87868,3.87868,3.44129,3.31607,4.20435,3,5,3L9,3C9.26522,3,9.51957,3.10536,9.70711,3.29289L12.4142,6,19,6C19.7957,6,20.5587,6.31607,21.1213,6.87868,21.6839,7.44129,22,8.20435,22,9L22,10.2682z";
|
||||||
|
|
||||||
|
public static string ReplayIconGeometry = "F0 M24,24z M0,0z M11,4.99999C11,4.60761,10.7705,4.25149,10.4132,4.08935,10.0559,3.92722,9.63679,3.98903,9.3415,4.24741L1.3415,11.2474C1.12448,11.4373,1,11.7116,1,12,1,12.2883,1.12448,12.5627,1.3415,12.7526L9.3415,19.7526C9.63679,20.0109,10.0559,20.0728,10.4132,19.9106,10.7705,19.7485,11,19.3924,11,19L11,4.99999z M9,16.7962L3.51859,12,9,7.20375,9,16.7962z M22,4.99999C22,4.60761,21.7705,4.25149,21.4132,4.08935,21.0559,3.92722,20.6368,3.98903,20.3415,4.24741L12.3415,11.2474C12.1245,11.4373,12,11.7116,12,12,12,12.2883,12.1245,12.5627,12.3415,12.7526L20.3415,19.7526C20.6368,20.0109,21.0559,20.0728,21.4132,19.9106,21.7705,19.7485,22,19.3924,22,19L22,4.99999z M20,16.7962L14.5186,12,20,7.20375,20,16.7962z";
|
||||||
|
|
||||||
|
public static string ScreenshotIconGeometry = "F0 M24,24z M0,0z M9,3C8.46957,3,7.96086,3.21071,7.58579,3.58579,7.21071,3.96086,7,4.46957,7,5,7,5.26522,6.89464,5.51957,6.70711,5.70711,6.51957,5.89464,6.26522,6,6,6L5,6C4.20435,6,3.44129,6.31607,2.87868,6.87868,2.31607,7.44129,2,8.20435,2,9L2,18C2,18.7956,2.31607,19.5587,2.87868,20.1213,3.44129,20.6839,4.20435,21,5,21L19,21C19.7957,21,20.5587,20.6839,21.1213,20.1213,21.6839,19.5587,22,18.7957,22,18L22,9C22,8.20435,21.6839,7.44129,21.1213,6.87868,20.5587,6.31607,19.7957,6,19,6L18,6C17.7348,6,17.4804,5.89464,17.2929,5.70711,17.1054,5.51957,17,5.26522,17,5,17,4.46957,16.7893,3.96086,16.4142,3.58579,16.0391,3.21071,15.5304,3,15,3L9,3z M9,5L15,5C15,5.79565,15.3161,6.55871,15.8787,7.12132,16.4413,7.68393,17.2044,8,18,8L19,8C19.2652,8,19.5196,8.10536,19.7071,8.29289,19.8946,8.48043,20,8.73478,20,9L20,18C20,18.2652,19.8946,18.5196,19.7071,18.7071,19.5196,18.8946,19.2652,19,19,19L5,19C4.73478,19,4.48043,18.8946,4.29289,18.7071,4.10536,18.5196,4,18.2652,4,18L4,9C4,8.73478,4.10536,8.48043,4.29289,8.29289,4.48043,8.10536,4.73478,8,5,8L6,8C6.79565,8,7.55871,7.68393,8.12132,7.12132,8.68393,6.55871,9,5.79565,9,5z M12,9C10.9391,9,9.92172,9.42143,9.17157,10.1716,8.42143,10.9217,8,11.9391,8,13,8,14.0609,8.42143,15.0783,9.17157,15.8284,9.92172,16.5786,10.9391,17,12,17,13.0609,17,14.0783,16.5786,14.8284,15.8284,15.5786,15.0783,16,14.0609,16,13,16,11.9391,15.5786,10.9217,14.8284,10.1716,14.0783,9.42143,13.0609,9,12,9z M10.5858,11.5858C10.9609,11.2107,11.4696,11,12,11,12.5304,11,13.0391,11.2107,13.4142,11.5858,13.7893,11.9609,14,12.4696,14,13,14,13.5304,13.7893,14.0391,13.4142,14.4142,13.0391,14.7893,12.5304,15,12,15,11.4696,15,10.9609,14.7893,10.5858,14.4142,10.2107,14.0391,10,13.5304,10,13,10,12.4696,10.2107,11.9609,10.5858,11.5858z";
|
||||||
|
|
||||||
|
public static string ManualIconGeometry = "F0 M24,24z M0,0z M8.17317,2.7612C9.38642,2.25866 10.6868,2 12,2 13.3132,2 14.6136,2.25866 15.8268,2.7612 17.0401,3.26375 18.1425,4.00035 19.0711,4.92893 19.9997,5.85752 20.7362,6.95991 21.2388,8.17317 21.7413,9.38642 22,10.6868 22,12 22,13.3132 21.7413,14.6136 21.2388,15.8268 20.7362,17.0401 19.9997,18.1425 19.0711,19.0711 18.1425,19.9997 17.0401,20.7362 15.8268,21.2388 14.6136,21.7413 13.3132,22 12,22 10.6868,22 9.38642,21.7413 8.17317,21.2388 6.95991,20.7362 5.85752,19.9997 4.92893,19.0711 4.00035,18.1425 3.26375,17.0401 2.7612,15.8268 2.25866,14.6136 2,13.3132 2,12 2,10.6868 2.25866,9.38642 2.7612,8.17317 3.26375,6.95991 4.00035,5.85752 4.92893,4.92893 5.85752,4.00035 6.95991,3.26375 8.17317,2.7612z M12,4C10.9494,4 9.90914,4.20693 8.93853,4.60896 7.96793,5.011 7.08601,5.60028 6.34315,6.34315 5.60028,7.08601 5.011,7.96793 4.60896,8.93853 4.20693,9.90914 4,10.9494 4,12 4,13.0506 4.20693,14.0909 4.60896,15.0615 5.011,16.0321 5.60028,16.914 6.34315,17.6569 7.08601,18.3997 7.96793,18.989 8.93853,19.391 9.90914,19.7931 10.9494,20 12,20 13.0506,20 14.0909,19.7931 15.0615,19.391 16.0321,18.989 16.914,18.3997 17.6569,17.6569 18.3997,16.914 18.989,16.0321 19.391,15.0615 19.7931,14.0909 20,13.0506 20,12 20,10.9494 19.7931,9.90914 19.391,8.93853 18.989,7.96793 18.3997,7.08602 17.6569,6.34315 16.914,5.60028 16.0321,5.011 15.0615,4.60896 14.0909,4.20693 13.0506,4 12,4z M12,16C12.5523,16,13,16.4477,13,17L13,17.01C13,17.5623 12.5523,18.01 12,18.01 11.4477,18.01 11,17.5623 11,17.01L11,17C11,16.4477,11.4477,16,12,16z M12.0813,5.97153C11.5241,5.96998 10.9742,6.09779 10.4748,6.3449 9.97535,6.59201 9.54013,6.95167 9.20334,7.39557 8.86952,7.83555 8.95559,8.46284 9.39557,8.79666 9.83555,9.13047 10.4628,9.04441 10.7967,8.60443 10.9463,8.40714 11.1398,8.24729 11.3617,8.13746 11.5837,8.02764 11.8281,7.97083 12.0758,7.97152 12.3234,7.97221 12.5675,8.03037 12.7888,8.14142 13.0102,8.25248 13.2027,8.4134 13.3513,8.61151 13.4999,8.80963 13.6005,9.03953 13.6451,9.28311 13.6897,9.5267 13.6772,9.77732 13.6086,10.0152 13.5399,10.2532 13.4169,10.4719 13.2493,10.6542 13.084,10.8341 12.8798,10.9736 12.6524,11.0623 12.1479,11.2435 11.7149,11.5821 11.4175,12.0283 11.1169,12.4792 10.9709,13.0156 11.0016,13.5566 11.0329,14.108 11.5052,14.5297 12.0566,14.4984 12.608,14.4671 13.0297,13.9948 12.9984,13.4434 12.9923,13.3352 13.0214,13.2279 13.0816,13.1377 13.1417,13.0475 13.2295,12.9793 13.3317,12.9434 13.3403,12.9404 13.3487,12.9373 13.3572,12.934 13.8776,12.735 14.3448,12.4179 14.7218,12.0077 15.0989,11.5974 15.3756,11.1053 15.5301,10.5699 15.6846,10.0346 15.7128,9.47068 15.6124,8.92261 15.5119,8.37454 15.2856,7.85727 14.9513,7.41151 14.617,6.96576 14.1838,6.60369 13.6857,6.35381 13.1877,6.10393 12.6385,5.97307 12.0813,5.97153z";
|
||||||
|
|
||||||
|
public static string SettingsIconGeometry = "F0 M24,24z M0,0z M4.66591,7.13141L11.4017,3.15976C11.5957,3.0552,11.8126,3.00041,12.033,3.00041,12.2534,3.00041,12.4704,3.0552,12.6643,3.15977L19.2182,7.02415C19.2676,7.06721,19.3219,7.10586,19.3806,7.13926,19.5699,7.24687,19.727,7.40296,19.8358,7.59146,19.9447,7.77997,20.0014,7.99407,20,8.21175L20,8.21175,20,8.218,20,15.502C20,15.943,19.7585,16.3548,19.3603,16.5737,19.3424,16.5835,19.3247,16.5939,19.3074,16.6049L12.5874,20.8559C12.4062,20.9506,12.2047,21.0001,12,21.0001,11.7953,21.0001,11.5938,20.9506,11.4126,20.8559L4.69261,16.6049C4.6746,16.5935,4.65624,16.5827,4.63755,16.5725,4.44494,16.4672,4.28416,16.3122,4.172,16.1235,4.05999,15.9351,4.00059,15.72,4,15.5008L4,8.217C4,7.77653,4.24107,7.36544,4.63968,7.14635,4.6485,7.1415,4.65724,7.13652,4.66591,7.13141z M20.4159,5.40859C20.4791,5.44583,20.5369,5.4892,20.589,5.53759,20.9895,5.81003,21.3244,6.16988,21.5678,6.59125,21.8538,7.08656,22.003,7.649,22,8.22093L22,15.502C22,16.6678,21.3677,17.7387,20.353,18.31L13.6266,22.5651C13.6092,22.5761,13.5914,22.5866,13.5733,22.5966,13.0911,22.8613,12.55,23.0001,12,23.0001,11.45,23.0001,10.9089,22.8613,10.4267,22.5966,10.4086,22.5866,10.3908,22.5761,10.3734,22.5651L3.64791,18.3106C3.15439,18.0339,2.74214,17.6322,2.45282,17.1455,2.15755,16.6488,2.00116,16.0818,2,15.504L2,15.502,2,8.217C2,7.04497,2.63892,5.97063,3.6619,5.40163L10.4001,1.42859C10.4084,1.4237,10.4167,1.41894,10.4252,1.41429,10.9176,1.1428,11.4707,1.00041,12.033,1.00041,12.5953,1.00041,13.1484,1.1428,13.6408,1.41429,13.6493,1.41894,13.6576,1.4237,13.6659,1.42859L20.4159,5.40859z M12,8C10.9391,8,9.92172,8.42143,9.17157,9.17157,8.42143,9.92172,8,10.9391,8,12,8,13.0609,8.42143,14.0783,9.17157,14.8284,9.92172,15.5786,10.9391,16,12,16,13.0609,16,14.0783,15.5786,14.8284,14.8284,15.5786,14.0783,16,13.0609,16,12,16,10.9391,15.5786,9.92172,14.8284,9.17157,14.0783,8.42143,13.0609,8,12,8z M10.5858,10.5858C10.9609,10.2107,11.4696,10,12,10,12.5304,10,13.0391,10.2107,13.4142,10.5858,13.7893,10.9609,14,11.4696,14,12,14,12.5304,13.7893,13.0391,13.4142,13.4142,13.0391,13.7893,12.5304,14,12,14,11.4696,14,10.9609,13.7893,10.5858,13.4142,10.2107,13.0391,10,12.5304,10,12,10,11.4696,10.2107,10.9609,10.5858,10.5858z";
|
||||||
|
|
||||||
|
#region 导航栏图标
|
||||||
|
public static string NavStartupIconGeometry = "F0 M24,24z M0,0z M6.63415,3.34528C6.87134,3.21274,7.16167,3.21885,7.39307,3.36126L20.3931,11.3613C20.6149,11.4978 20.75,11.7396 20.75,12 20.75,12.2604 20.6149,12.5022 20.3931,12.6387L7.39307,20.6387C7.16167,20.7811 6.87134,20.7873 6.63415,20.6547 6.39696,20.5222 6.25,20.2717 6.25,20L6.25,4C6.25,3.72829,6.39696,3.47783,6.63415,3.34528z M7.75,5.34217L7.75,18.6578 18.569,12 7.75,5.34217z";
|
||||||
|
public static string NavCanvasIconGeometry = "F0 M24,24z M0,0z M4.11612,6.11612C4.35054,5.8817,4.66848,5.75,5,5.75L19,5.75C19.3315,5.75 19.6495,5.8817 19.8839,6.11612 20.1183,6.35054 20.25,6.66848 20.25,7L20.25,18C20.25,18.0663 20.2237,18.1299 20.1768,18.1768 20.1299,18.2237 20.0663,18.25 20,18.25 19.5858,18.25 19.25,18.5858 19.25,19 19.25,19.4142 19.5858,19.75 20,19.75 20.4641,19.75 20.9092,19.5656 21.2374,19.2374 21.5656,18.9092 21.75,18.4641 21.75,18L21.75,7C21.75,6.27065 21.4603,5.57118 20.9445,5.05546 20.4288,4.53973 19.7293,4.25 19,4.25L5,4.25C4.27065,4.25 3.57118,4.53973 3.05546,5.05546 2.53973,5.57118 2.25,6.27065 2.25,7L2.25,17C2.25,17.7293 2.53973,18.4288 3.05546,18.9445 3.57118,19.4603 4.27065,19.75 5,19.75L8,19.75C8.41421,19.75 8.75,19.4142 8.75,19 8.75,18.5858 8.41421,18.25 8,18.25L5,18.25C4.66848,18.25 4.35054,18.1183 4.11612,17.8839 3.8817,17.6495 3.75,17.3315 3.75,17L3.75,7C3.75,6.66848,3.8817,6.35054,4.11612,6.11612z M11.8232,16.8232C11.8701,16.7763,11.9337,16.75,12,16.75L16,16.75C16.0663,16.75 16.1299,16.7763 16.1768,16.8232 16.2237,16.8701 16.25,16.9337 16.25,17L16.25,18C16.25,18.0663 16.2237,18.1299 16.1768,18.1768 16.1299,18.2237 16.0663,18.25 16,18.25L12,18.25C11.9337,18.25 11.8701,18.2237 11.8232,18.1768 11.7763,18.1299 11.75,18.0663 11.75,18L11.75,17C11.75,16.9337,11.7763,16.8701,11.8232,16.8232z M12,15.25C11.5359,15.25 11.0908,15.4344 10.7626,15.7626 10.4344,16.0908 10.25,16.5359 10.25,17L10.25,18C10.25,18.4641 10.4344,18.9092 10.7626,19.2374 11.0908,19.5656 11.5359,19.75 12,19.75L16,19.75C16.4641,19.75 16.9092,19.5656 17.2374,19.2374 17.5656,18.9092 17.75,18.4641 17.75,18L17.75,17C17.75,16.5359 17.5656,16.0908 17.2374,15.7626 16.9092,15.4344 16.4641,15.25 16,15.25L12,15.25z";
|
||||||
|
public static string NavInkRecognitionIconGeometry = "F0 M24,24z M0,0z M15.1306,4.19378C15.5647,4.01395 16.0301,3.92139 16.5,3.92139 16.9699,3.92139 17.4353,4.01395 17.8694,4.19378 18.3036,4.37361 18.698,4.6372 19.0303,4.96948 19.3626,5.30177 19.6262,5.69625 19.806,6.13041 19.9859,6.56457 20.0784,7.02989 20.0784,7.49981 20.0784,7.96974 19.9859,8.43506 19.806,8.86922 19.6262,9.30337 19.3626,9.69786 19.0303,10.0301L18.0403,11.0202 18.0303,11.0303 18.0202,11.0403 8.53033,20.5301C8.38968,20.6708,8.19891,20.7498,8,20.7498L4,20.7498C3.58579,20.7498,3.25,20.414,3.25,19.9998L3.25,15.9998C3.25,15.8009,3.32902,15.6101,3.46967,15.4695L13.9697,4.96948C14.302,4.6372,14.6964,4.37361,15.1306,4.19378z M17.9697,8.96948L17.4999,9.43925 14.5606,6.49991 15.0303,6.03014C15.2233,5.83714 15.4525,5.68405 15.7046,5.5796 15.9568,5.47515 16.2271,5.42139 16.5,5.42139 16.7729,5.42139 17.0432,5.47515 17.2954,5.5796 17.5475,5.68405 17.7767,5.83714 17.9697,6.03014 18.1627,6.22314 18.3158,6.45227 18.4202,6.70443 18.5247,6.9566 18.5784,7.22687 18.5784,7.49981 18.5784,7.77276 18.5247,8.04303 18.4202,8.29519 18.3158,8.54736 18.1627,8.77648 17.9697,8.96948z M4.75,16.3105L13.4999,7.56057 16.4392,10.4999 7.68934,19.2498 4.75,19.2498 4.75,16.3105z M21.5303,17.5303C21.8232,17.2374 21.8232,16.7626 21.5303,16.4697 21.2374,16.1768 20.7626,16.1768 20.4697,16.4697L17,19.9393 15.5303,18.4697C15.2374,18.1768 14.7626,18.1768 14.4697,18.4697 14.1768,18.7626 14.1768,19.2374 14.4697,19.5303L16.4697,21.5303C16.7626,21.8232,17.2374,21.8232,17.5303,21.5303L21.5303,17.5303z";
|
||||||
|
public static string NavThemeIconGeometry = "F0 M24,24z M0,0z M9.43853,3.39157C9.63411,3.53254 9.75,3.75892 9.75,4.00001 9.75,4.59674 9.98705,5.16904 10.409,5.591 10.831,6.01295 11.4033,6.25001 12,6.25001 12.5967,6.25001 13.169,6.01295 13.591,5.591 14.0129,5.16904 14.25,4.59674 14.25,4.00001 14.25,3.75892 14.3659,3.53254 14.5615,3.39157 14.757,3.25061 15.0085,3.21226 15.2372,3.28849L21.2372,5.28849C21.5434,5.39058,21.75,5.67718,21.75,6.00001L21.75,11C21.75,11.4142,21.4142,11.75,21,11.75L18.75,11.75 18.75,19C18.75,19.4641 18.5656,19.9093 18.2374,20.2374 17.9092,20.5656 17.4641,20.75 17,20.75L7,20.75C6.53587,20.75 6.09075,20.5656 5.76256,20.2374 5.43437,19.9093 5.25,19.4641 5.25,19L5.25,11.75 3,11.75C2.58579,11.75,2.25,11.4142,2.25,11L2.25,6.00001C2.25,5.67718,2.45657,5.39058,2.76283,5.28849L8.76283,3.28849C8.99154,3.21226,9.24296,3.25061,9.43853,3.39157z M3.75,6.54058L3.75,10.25 6,10.25C6.41421,10.25,6.75,10.5858,6.75,11L6.75,19C6.75,19.0663 6.77634,19.1299 6.82322,19.1768 6.87011,19.2237 6.93369,19.25 7,19.25L17,19.25C17.0663,19.25 17.1299,19.2237 17.1768,19.1768 17.2237,19.1299 17.25,19.0663 17.25,19L17.25,11C17.25,10.5858,17.5858,10.25,18,10.25L20.25,10.25 20.25,6.54058 15.6154,4.99571C15.4444,5.61643 15.1149,6.1884 14.6517,6.65166 13.9484,7.35492 12.9946,7.75001 12,7.75001 11.0054,7.75001 10.0516,7.35492 9.34835,6.65166 8.8851,6.1884 8.55557,5.61643 8.3846,4.99571L3.75,6.54058z";
|
||||||
|
public static string NavPPTIconGeometry = "F0 M24,24z M0,0z M12.6955,2.31464C12.8562,2.2432,13.037,2.23053,13.2061,2.27886L20.2061,4.27886C20.5281,4.37085,20.7501,4.66514,20.7501,5L20.7501,18C20.7501,18.3,20.5713,18.5712,20.2955,18.6894L13.2955,21.6894C13.1267,21.7617,12.9372,21.7696,12.7629,21.7115L3.7629,18.7115C3.41393,18.5952 3.20083,18.243 3.25975,17.8799 3.31867,17.5168 3.63223,17.25 4.00007,17.25L12.2501,17.25 12.2501,7.10778 8.75007,8.50778 8.75007,13C8.75007,13.2841,8.58957,13.5438,8.33548,13.6708L4.33548,15.6708C4.10299,15.7871 3.82688,15.7746 3.60577,15.638 3.38466,15.5013 3.25007,15.2599 3.25007,15L3.25007,7C3.25007,6.70361,3.42462,6.43502,3.69546,6.31464L12.6955,2.31464z M13.0558,3.79595L4.75007,7.48741 4.75007,13.7865 7.25007,12.5365 7.25007,8C7.25007,7.69332,7.43678,7.41754,7.72153,7.30364L12.7215,5.30364C12.9526,5.21122 13.2145,5.23943 13.4205,5.37895 13.6266,5.51847 13.7501,5.75113 13.7501,6L13.7501,18C13.7501,18.4142,13.4143,18.75,13.0001,18.75L8.62178,18.75 12.9667,20.1983 19.2501,17.5055 19.2501,5.56573 13.0558,3.79595z";
|
||||||
|
public static string NavAdvancedIconGeometry = "F0 M24,24z M0,0z M7.8679,3.80724L10.5302,6.46952C10.6708,6.61018,10.7499,6.80094,10.7499,6.99985L10.7499,9.99985C10.7499,10.4141,10.4141,10.7499,9.99985,10.7499L6.99985,10.7499C6.80094,10.7499,6.61018,10.6708,6.46952,10.5302L3.80724,7.8679C3.64358,8.5543 3.61907,9.27047 3.73968,9.97343 3.92327,11.0435 4.43407,12.0303 5.20176,12.798 5.96944,13.5656 6.95625,14.0764 8.02628,14.26 9.09632,14.4436 10.197,14.291 11.1766,13.8231 11.4634,13.6861 11.8054,13.7448 12.0302,13.9695L18.0302,19.9695C18.2874,20.2267 18.6362,20.3712 18.9999,20.3712 19.3636,20.3712 19.7124,20.2267 19.9695,19.9695 20.2267,19.7124 20.3712,19.3636 20.3712,18.9999 20.3712,18.6362 20.2267,18.2874 19.9695,18.0302L13.9695,12.0302C13.7448,11.8054 13.6861,11.4634 13.8231,11.1766 14.291,10.197 14.4436,9.09632 14.26,8.02628 14.0764,6.95625 13.5656,5.96944 12.798,5.20176 12.0303,4.43407 11.0435,3.92327 9.97343,3.73968 9.27047,3.61907 8.5543,3.64358 7.8679,3.80724z M6.17663,2.82308C7.43621,2.22151 8.85132,2.02523 10.2271,2.26128 11.6028,2.49732 12.8716,3.15407 13.8586,4.1411 14.8456,5.12812 15.5024,6.39687 15.7384,7.77263 15.944,8.97097 15.8216,10.1992 15.3891,11.3284L21.0302,16.9695C21.5687,17.508 21.8712,18.2383 21.8712,18.9999 21.8712,19.7614 21.5687,20.4917 21.0302,21.0302 20.4917,21.5687 19.7614,21.8712 18.9999,21.8712 18.2383,21.8712 17.508,21.5687 16.9695,21.0302L11.3284,15.3891C10.1992,15.8216 8.97097,15.944 7.77263,15.7384 6.39687,15.5024 5.12812,14.8456 4.1411,13.8586 3.15407,12.8716 2.49732,11.6028 2.26128,10.2271 2.02523,8.85132 2.22151,7.43621 2.82308,6.17663 2.92801,5.95693 3.13306,5.80183 3.37303,5.76066 3.613,5.71948 3.85802,5.79736 4.03018,5.96952L7.31051,9.24985 9.24985,9.24985 9.24985,7.31051 5.96952,4.03018C5.79736,3.85802 5.71948,3.613 5.76066,3.37303 5.80183,3.13306 5.95693,2.92801 6.17663,2.82308z";
|
||||||
|
public static string NavAutomationIconGeometry = "F0 M24,24z M0,0z M7.81828,2.27257C8.22012,2.17211,8.62732,2.41643,8.72778,2.81828L9.08572,4.25 14.9146,4.25 15.2726,2.81828C15.373,2.41643 15.7802,2.17211 16.1821,2.27257 16.5839,2.37303 16.8282,2.78023 16.7278,3.18208L16.4608,4.25 18,4.25C18.7293,4.25 19.4288,4.53973 19.9445,5.05546 20.4603,5.57118 20.75,6.27065 20.75,7L20.75,19C20.75,19.7293 20.4603,20.4288 19.9445,20.9445 19.4288,21.4603 18.7293,21.75 18,21.75L6,21.75C5.27065,21.75 4.57118,21.4603 4.05546,20.9445 3.53973,20.4288 3.25,19.7293 3.25,19L3.25,7C3.25,6.27065 3.53973,5.57118 4.05546,5.05546 4.57118,4.53973 5.27065,4.25 6,4.25L7.53955,4.25 7.27257,3.18208C7.17211,2.78023,7.41643,2.37303,7.81828,2.27257z M14.5396,5.75L14.2726,6.81828C14.1721,7.22012 14.4164,7.62732 14.8183,7.72778 15.2201,7.82825 15.6273,7.58393 15.7278,7.18208L16.0858,5.75 18,5.75C18.3315,5.75 18.6495,5.8817 18.8839,6.11612 19.1183,6.35054 19.25,6.66848 19.25,7L19.25,19C19.25,19.3315 19.1183,19.6495 18.8839,19.8839 18.6495,20.1183 18.3315,20.25 18,20.25L6,20.25C5.66848,20.25 5.35054,20.1183 5.11612,19.8839 4.8817,19.6495 4.75,19.3315 4.75,19L4.75,7C4.75,6.66848 4.8817,6.35054 5.11612,6.11612 5.35054,5.8817 5.66848,5.75 6,5.75L7.91455,5.75 8.27257,7.18208C8.37303,7.58393 8.78023,7.82825 9.18208,7.72778 9.58393,7.62732 9.82825,7.22012 9.72778,6.81828L9.46072,5.75 14.5396,5.75z M9.41625,15.3761C9.07166,15.1463 8.60598,15.2393 8.37614,15.5839 8.14629,15.9285 8.23932,16.3942 8.58391,16.624 9.6821,17.3565 10.8245,17.7501 12.0001,17.7501 13.1757,17.7501 14.3181,17.3565 15.4162,16.624 15.7608,16.3942 15.8539,15.9285 15.624,15.5839 15.3942,15.2393 14.9285,15.1463 14.5839,15.3761 13.6821,15.9776 12.8245,16.2501 12.0001,16.2501 11.1757,16.2501 10.3181,15.9776 9.41625,15.3761z M9,10.25C9.41421,10.25,9.75,10.5858,9.75,11L9.75,12C9.75,12.4142 9.41421,12.75 9,12.75 8.58579,12.75 8.25,12.4142 8.25,12L8.25,11C8.25,10.5858,8.58579,10.25,9,10.25z M15.75,11C15.75,10.5858 15.4626,10.25 15.0484,10.25 14.6342,10.25 14.25,10.5858 14.25,11L14.25,12C14.25,12.4142 14.5374,12.75 14.9516,12.75 15.3658,12.75 15.75,12.4142 15.75,12L15.75,11z";
|
||||||
|
public static string NavRandomWindowIconGeometry = "F0 M24,24z M0,0z M18.5303,3.46967C18.2374,3.17678 17.7626,3.17678 17.4697,3.46967 17.1768,3.76256 17.1768,4.23744 17.4697,4.53033L19.1893,6.25 16,6.25C14.475,6.25 13.0125,6.8558 11.9341,7.93414 11.5659,8.30239 11.2527,8.71546 11,9.16052 10.7473,8.71546 10.4341,8.30239 10.0659,7.93414 8.98753,6.8558 7.52499,6.25 6,6.25L3,6.25C2.58579,6.25 2.25,6.58579 2.25,7 2.25,7.41421 2.58579,7.75 3,7.75L6,7.75C7.12717,7.75 8.20817,8.19777 9.0052,8.9948 9.80223,9.79183 10.25,10.8728 10.25,12 10.25,13.1272 9.80223,14.2082 9.0052,15.0052 8.20817,15.8022 7.12717,16.25 6,16.25L3,16.25C2.58579,16.25 2.25,16.5858 2.25,17 2.25,17.4142 2.58579,17.75 3,17.75L6,17.75C7.52499,17.75 8.98753,17.1442 10.0659,16.0659 10.4341,15.6976 10.7473,15.2845 11,14.8395 11.2527,15.2845 11.5659,15.6976 11.9341,16.0659 13.0125,17.1442 14.475,17.75 16,17.75L19.1893,17.75 17.4697,19.4697C17.1768,19.7626 17.1768,20.2374 17.4697,20.5303 17.7626,20.8232 18.2374,20.8232 18.5303,20.5303L21.5303,17.5303C21.6022,17.4584 21.6565,17.3755 21.6931,17.2871 21.7298,17.1987 21.75,17.1017 21.75,17 21.75,16.8081 21.6768,16.6161 21.5303,16.4697L18.5303,13.4697C18.2374,13.1768 17.7626,13.1768 17.4697,13.4697 17.1768,13.7626 17.1768,14.2374 17.4697,14.5303L19.1893,16.25 16,16.25C14.8728,16.25 13.7918,15.8022 12.9948,15.0052 12.1978,14.2082 11.75,13.1272 11.75,12 11.75,10.8728 12.1978,9.79183 12.9948,8.9948 13.7918,8.19777 14.8728,7.75 16,7.75L19.1893,7.75 17.4697,9.46967C17.1768,9.76256 17.1768,10.2374 17.4697,10.5303 17.7626,10.8232 18.2374,10.8232 18.5303,10.5303L21.5303,7.53033C21.6768,7.38388 21.75,7.19194 21.75,7 21.75,6.89831 21.7298,6.80134 21.6931,6.71291 21.6565,6.62445 21.6022,6.54158 21.5303,6.46967L18.5303,3.46967z";
|
||||||
|
public static string NavAboutIconGeometry = "F0 M24,24z M0,0z M5.10571,5.10571C6.93419,3.27723 9.41414,2.25 12,2.25 14.5859,2.25 17.0658,3.27723 18.8943,5.10571 20.7228,6.93419 21.75,9.41414 21.75,12 21.75,13.2804 21.4978,14.5482 21.0078,15.7312 20.5178,16.9141 19.7997,17.9889 18.8943,18.8943 17.9889,19.7997 16.9141,20.5178 15.7312,21.0078 14.5482,21.4978 13.2804,21.75 12,21.75 10.7196,21.75 9.45176,21.4978 8.26884,21.0078 7.08591,20.5178 6.01108,19.7997 5.10571,18.8943 4.20034,17.9889 3.48216,16.9141 2.99217,15.7312 2.50219,14.5482 2.25,13.2804 2.25,12 2.25,9.41414 3.27723,6.93419 5.10571,5.10571z M12,3.75C9.81196,3.75 7.71354,4.61919 6.16637,6.16637 4.61919,7.71354 3.75,9.81196 3.75,12 3.75,13.0834 3.96339,14.1562 4.37799,15.1571 4.79259,16.1581 5.40029,17.0675 6.16637,17.8336 6.93245,18.5997 7.84193,19.2074 8.84286,19.622 9.8438,20.0366 10.9166,20.25 12,20.25 13.0834,20.25 14.1562,20.0366 15.1571,19.622 16.1581,19.2074 17.0675,18.5997 17.8336,17.8336 18.5997,17.0675 19.2074,16.1581 19.622,15.1571 20.0366,14.1562 20.25,13.0834 20.25,12 20.25,9.81196 19.3808,7.71354 17.8336,6.16637 16.2865,4.61919 14.188,3.75 12,3.75z M11.25,9C11.25,8.58579,11.5858,8.25,12,8.25L12.01,8.25C12.4242,8.25 12.76,8.58579 12.76,9 12.76,9.41421 12.4242,9.75 12.01,9.75L12,9.75C11.5858,9.75,11.25,9.41421,11.25,9z M11,11.25C10.5858,11.25 10.25,11.5858 10.25,12 10.25,12.4142 10.5858,12.75 11,12.75L11.25,12.75 11.25,16C11.25,16.4142,11.5858,16.75,12,16.75L13,16.75C13.4142,16.75 13.75,16.4142 13.75,16 13.75,15.5858 13.4142,15.25 13,15.25L12.75,15.25 12.75,12C12.75,11.5858,12.4142,11.25,12,11.25L11,11.25z";
|
||||||
|
public static string NavShortcutsIconGeometry = "F0 M24,24z M0,0z M18.5303,3.46967C18.2374,3.17678 17.7626,3.17678 17.4697,3.46967 17.1768,3.76256 17.1768,4.23744 17.4697,4.53033L19.1893,6.25 16,6.25C14.475,6.25 13.0125,6.8558 11.9341,7.93414 11.5659,8.30239 11.2527,8.71546 11,9.16052 10.7473,8.71546 10.4341,8.30239 10.0659,7.93414 8.98753,6.8558 7.52499,6.25 6,6.25L3,6.25C2.58579,6.25 2.25,6.58579 2.25,7 2.25,7.41421 2.58579,7.75 3,7.75L6,7.75C7.12717,7.75 8.20817,8.19777 9.0052,8.9948 9.80223,9.79183 10.25,10.8728 10.25,12 10.25,13.1272 9.80223,14.2082 9.0052,15.0052 8.20817,15.8022 7.12717,16.25 6,16.25L3,16.25C2.58579,16.25 2.25,16.5858 2.25,17 2.25,17.4142 2.58579,17.75 3,17.75L6,17.75C7.52499,17.75 8.98753,17.1442 10.0659,16.0659 10.4341,15.6976 10.7473,15.2845 11,14.8395 11.2527,15.2845 11.5659,15.6976 11.9341,16.0659 13.0125,17.1442 14.475,17.75 16,17.75L19.1893,17.75 17.4697,19.4697C17.1768,19.7626 17.1768,20.2374 17.4697,20.5303 17.7626,20.8232 18.2374,20.8232 18.5303,20.5303L21.5303,17.5303C21.6022,17.4584 21.6565,17.3755 21.6931,17.2871 21.7298,17.1987 21.75,17.1017 21.75,17 21.75,16.8081 21.6768,16.6161 21.5303,16.4697L18.5303,13.4697C18.2374,13.1768 17.7626,13.1768 17.4697,13.4697 17.1768,13.7626 17.1768,14.2374 17.4697,14.5303L19.1893,16.25 16,16.25C14.8728,16.25 13.7918,15.8022 12.9948,15.0052 12.1978,14.2082 11.75,13.1272 11.75,12 11.75,10.8728 12.1978,9.79183 12.9948,8.9948 13.7918,8.19777 14.8728,7.75 16,7.75L19.1893,7.75 17.4697,9.46967C17.1768,9.76256 17.1768,10.2374 17.4697,10.5303 17.7626,10.8232 18.2374,10.8232 18.5303,10.5303L21.5303,7.53033C21.6768,7.38388 21.75,7.19194 21.75,7 21.75,6.89831 21.7298,6.80134 21.6931,6.71291 21.6565,6.62445 21.6022,6.54158 21.5303,6.46967L18.5303,3.46967z";
|
||||||
|
public static string NavCollapseSidebarIconGeometry = "F0 M24,24z M0,0z M5.10571,5.10571C6.93419,3.27723 9.41414,2.25 12,2.25 14.5859,2.25 17.0658,3.27723 18.8943,5.10571 20.7228,6.93419 21.75,9.41414 21.75,12 21.75,13.2804 21.4978,14.5482 21.0078,15.7312 20.5178,16.9141 19.7997,17.9889 18.8943,18.8943 17.9889,19.7997 16.9141,20.5178 15.7312,21.0078 14.5482,21.4978 13.2804,21.75 12,21.75 10.7196,21.75 9.45176,21.4978 8.26884,21.0078 7.08591,20.5178 6.01108,19.7997 5.10571,18.8943 4.20034,17.9889 3.48216,16.9141 2.99217,15.7312 2.50219,14.5482 2.25,13.2804 2.25,12 2.25,9.41414 3.27723,6.93419 5.10571,5.10571z M12,3.75C9.81196,3.75 7.71354,4.61919 6.16637,6.16637 4.61919,7.71354 3.75,9.81196 3.75,12 3.75,13.0834 3.96339,14.1562 4.37799,15.1571 4.79259,16.1581 5.40029,17.0675 6.16637,17.8336 6.93245,18.5997 7.84193,19.2074 8.84286,19.622 9.8438,20.0366 10.9166,20.25 12,20.25 13.0834,20.25 14.1562,20.0366 15.1571,19.622 16.1581,19.2074 17.0675,18.5997 17.8336,17.8336 18.5997,17.0675 19.2074,16.1581 19.622,15.1571 20.0366,14.1562 20.25,13.0834 20.25,12 20.25,9.81196 19.3808,7.71354 17.8336,6.16637 16.2865,4.61919 14.188,3.75 12,3.75z M11.25,9C11.25,8.58579,11.5858,8.25,12,8.25L12.01,8.25C12.4242,8.25 12.76,8.58579 12.76,9 12.76,9.41421 12.4242,9.75 12.01,9.75L12,9.75C11.5858,9.75,11.25,9.41421,11.25,9z M11,11.25C10.5858,11.25 10.25,11.5858 10.25,12 10.25,12.4142 10.5858,12.75 11,12.75L11.25,12.75 11.25,16C11.25,16.4142,11.5858,16.75,12,16.75L13,16.75C13.4142,16.75 13.75,16.4142 13.75,16 13.75,15.5858 13.4142,15.25 13,15.25L12.75,15.25 12.75,12C12.75,11.5858,12.4142,11.25,12,11.25L11,11.25z";
|
||||||
|
public static string NavShowSidebarIconGeometry = "M20,5H4A2,2 0 0,0 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7A2,2 0 0,0 20,5M20,17H4V7H20V17M5,8H7V10H5V8M8,8H10V10H8V8M11,8H13V10H11V8M14,8H16V10H14V8M17,8H19V10H17V8M5,11H7V13H5V11M8,11H10V13H8V11M11,11H13V13H11V11M14,11H16V13H14V11M17,11H19V13H17V11M8,14H16V16H8V14Z";
|
||||||
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,7 +496,7 @@ namespace Ink_Canvas
|
|||||||
/// 10. 提交历史记录
|
/// 10. 提交历史记录
|
||||||
/// 11. 插入图片后切换到选择模式并刷新浮动栏高光显示
|
/// 11. 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async Task InsertScreenshotToCanvas(Bitmap bitmap)
|
private Task InsertScreenshotToCanvas(Bitmap bitmap)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -504,7 +504,7 @@ namespace Ink_Canvas
|
|||||||
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
|
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
|
||||||
{
|
{
|
||||||
ShowNotification("无效的截图");
|
ShowNotification("无效的截图");
|
||||||
return;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将Bitmap转换为WPF BitmapSource
|
// 将Bitmap转换为WPF BitmapSource
|
||||||
@@ -513,7 +513,7 @@ namespace Ink_Canvas
|
|||||||
if (bitmapSource == null)
|
if (bitmapSource == null)
|
||||||
{
|
{
|
||||||
ShowNotification("转换截图失败");
|
ShowNotification("转换截图失败");
|
||||||
return;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建WPF Image控件
|
// 创建WPF Image控件
|
||||||
@@ -572,6 +572,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
bitmap?.Dispose();
|
bitmap?.Dispose();
|
||||||
}
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -591,7 +592,7 @@ namespace Ink_Canvas
|
|||||||
/// 8. 提交历史记录
|
/// 8. 提交历史记录
|
||||||
/// 9. 插入图片后切换到选择模式并刷新浮动栏高光显示
|
/// 9. 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async Task InsertBitmapSourceToCanvas(BitmapSource bitmapSource, string successMessage = "截图已插入到画布", string failureMessagePrefix = "插入截图失败")
|
private Task InsertBitmapSourceToCanvas(BitmapSource bitmapSource, string successMessage = "截图已插入到画布", string failureMessagePrefix = "插入截图失败")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -647,6 +648,7 @@ namespace Ink_Canvas
|
|||||||
ShowNotification($"{failureMessagePrefix}: {ex.Message}");
|
ShowNotification($"{failureMessagePrefix}: {ex.Message}");
|
||||||
LogHelper.WriteLogToFile($"插入摄像头截图失败: {ex.Message}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"插入摄像头截图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
+387
-304
@@ -14,6 +14,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using Application = System.Windows.Application;
|
using Application = System.Windows.Application;
|
||||||
using File = System.IO.File;
|
using File = System.IO.File;
|
||||||
@@ -180,6 +181,7 @@ namespace Ink_Canvas
|
|||||||
/// PowerPoint 全屏放映顶层窗口类名(与编辑态 PPTFrameClass 区分)。
|
/// PowerPoint 全屏放映顶层窗口类名(与编辑态 PPTFrameClass 区分)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string PowerPointSlideShowWindowClassName = "screenClass";
|
private const string PowerPointSlideShowWindowClassName = "screenClass";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PPT Managers
|
#region PPT Managers
|
||||||
@@ -205,6 +207,7 @@ namespace Ink_Canvas
|
|||||||
/// 提供对内部PPT链接管理器的公共访问,用于外部代码与PowerPoint进行交互。
|
/// 提供对内部PPT链接管理器的公共访问,用于外部代码与PowerPoint进行交互。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public IPPTLinkManager PPTManager => _pptManager;
|
public IPPTLinkManager PPTManager => _pptManager;
|
||||||
|
public PPTUIManager PPTUIManager => _pptUIManager;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PPT Manager Initialization
|
#region PPT Manager Initialization
|
||||||
@@ -214,12 +217,13 @@ namespace Ink_Canvas
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 清理并释放现有的 PPT 管理器与 COM/Interop 状态,创建并配置新的 PPT 管理器(ROT 或 COM 实现,取决于设置)、单一的 PPT 墨迹管理器及其自动保存行为,以及 PPT UI 管理器与其显示/按钮位置选项。方法内部会订阅必要的 PPT 事件并记录初始化过程中的错误或警告。同时初始化长按页翻页定时器以支持长按翻页功能。
|
/// 清理并释放现有的 PPT 管理器与 COM/Interop 状态,创建并配置新的 PPT 管理器(ROT 或 COM 实现,取决于设置)、单一的 PPT 墨迹管理器及其自动保存行为,以及 PPT UI 管理器与其显示/按钮位置选项。方法内部会订阅必要的 PPT 事件并记录初始化过程中的错误或警告。同时初始化长按页翻页定时器以支持长按翻页功能。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InitializePPTManagers()
|
public void InitializePPTManagers()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 初始化长按定时器
|
// 初始化长按定时器
|
||||||
InitializeLongPressTimer();
|
InitializeLongPressTimer();
|
||||||
|
WirePptNavBars();
|
||||||
|
|
||||||
// 完全清理旧模式
|
// 完全清理旧模式
|
||||||
try
|
try
|
||||||
@@ -298,7 +302,7 @@ namespace Ink_Canvas
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 只有当Settings.PowerPointSettings.PowerPointSupport为true时才会启动监控,并记录启动事件日志。
|
/// 只有当Settings.PowerPointSettings.PowerPointSupport为true时才会启动监控,并记录启动事件日志。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void StartPPTMonitoring()
|
public void StartPPTMonitoring()
|
||||||
{
|
{
|
||||||
if (Settings.PowerPointSettings.PowerPointSupport)
|
if (Settings.PowerPointSettings.PowerPointSupport)
|
||||||
{
|
{
|
||||||
@@ -310,7 +314,7 @@ namespace Ink_Canvas
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 停止 PowerPoint 相关的监控:停止并清除用于延迟退出 PPT 模式的定时器,并停止 PPT 管理器的监控,同时记录事件日志。
|
/// 停止 PowerPoint 相关的监控:停止并清除用于延迟退出 PPT 模式的定时器,并停止 PPT 管理器的监控,同时记录事件日志。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StopPPTMonitoring()
|
public void StopPPTMonitoring()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -328,13 +332,12 @@ namespace Ink_Canvas
|
|||||||
#region PowerPoint Application Management
|
#region PowerPoint Application Management
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动PowerPoint应用程序守护
|
/// 启动PowerPoint应用程序守护
|
||||||
/// <summary>
|
|
||||||
/// 启动对本地 PowerPoint 应用实例的守护监控并在需要时创建应用程序实例。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// 启动对本地 PowerPoint 应用实例的守护监控并在需要时创建应用程序实例。
|
||||||
/// 仅在 PowerPoint 增强功能已启用且未使用 ROT 链接时生效;方法将创建 PowerPoint 应用(若不存在)并启动用于定期检查应用状态的定时器。
|
/// 仅在 PowerPoint 增强功能已启用且未使用 ROT 链接时生效;方法将创建 PowerPoint 应用(若不存在)并启动用于定期检查应用状态的定时器。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void StartPowerPointProcessMonitoring()
|
public void StartPowerPointProcessMonitoring()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -364,7 +367,7 @@ namespace Ink_Canvas
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 停止PowerPoint应用程序守护
|
/// 停止PowerPoint应用程序守护
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StopPowerPointProcessMonitoring()
|
public void StopPowerPointProcessMonitoring()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1370,6 +1373,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await Application.Current.Dispatcher.InvokeAsync(() => CollapseAllPptNavBarPreviews());
|
||||||
|
|
||||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
|
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
|
||||||
{
|
{
|
||||||
FoldFloatingBar_MouseUp(new object(), null);
|
FoldFloatingBar_MouseUp(new object(), null);
|
||||||
@@ -1652,7 +1657,7 @@ namespace Ink_Canvas
|
|||||||
if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0)
|
if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0)
|
||||||
{
|
{
|
||||||
_lastPlaybackPage = page;
|
_lastPlaybackPage = page;
|
||||||
new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () =>
|
var yesNoWindow = new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1674,7 +1679,17 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"跳转到第{page}页失败: {ex}", LogHelper.LogType.Error);
|
LogHelper.WriteLogToFile($"跳转到第{page}页失败: {ex}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
}).ShowDialog();
|
});
|
||||||
|
yesNoWindow.Owner = this;
|
||||||
|
PauseTopmostMaintenance();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
yesNoWindow.ShowDialog();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ResumeTopmostMaintenance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1716,7 +1731,7 @@ namespace Ink_Canvas
|
|||||||
if (hasHiddenSlides && !IsShowingRestoreHiddenSlidesWindow)
|
if (hasHiddenSlides && !IsShowingRestoreHiddenSlidesWindow)
|
||||||
{
|
{
|
||||||
IsShowingRestoreHiddenSlidesWindow = true;
|
IsShowingRestoreHiddenSlidesWindow = true;
|
||||||
new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?",
|
var yesNoWindow = new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?",
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1740,7 +1755,17 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => { IsShowingRestoreHiddenSlidesWindow = false; },
|
() => { IsShowingRestoreHiddenSlidesWindow = false; },
|
||||||
() => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog();
|
() => { IsShowingRestoreHiddenSlidesWindow = false; });
|
||||||
|
yesNoWindow.Owner = this;
|
||||||
|
PauseTopmostMaintenance();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
yesNoWindow.ShowDialog();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ResumeTopmostMaintenance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1786,7 +1811,7 @@ namespace Ink_Canvas
|
|||||||
if (hasSlideTimings && !IsShowingAutoplaySlidesWindow)
|
if (hasSlideTimings && !IsShowingAutoplaySlidesWindow)
|
||||||
{
|
{
|
||||||
IsShowingAutoplaySlidesWindow = true;
|
IsShowingAutoplaySlidesWindow = true;
|
||||||
new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?",
|
var yesNoWindow = new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?",
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1806,7 +1831,17 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => { IsShowingAutoplaySlidesWindow = false; },
|
() => { IsShowingAutoplaySlidesWindow = false; },
|
||||||
() => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog();
|
() => { IsShowingAutoplaySlidesWindow = false; });
|
||||||
|
yesNoWindow.Owner = this;
|
||||||
|
PauseTopmostMaintenance();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
yesNoWindow.ShowDialog();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ResumeTopmostMaintenance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1991,14 +2026,14 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
Settings.PowerPointSettings.EnablePowerPointEnhancement = ToggleSwitchPowerPointEnhancement.IsOn;
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null)
|
||||||
|
Settings.PowerPointSettings.EnablePowerPointEnhancement = toggle.IsOn;
|
||||||
|
|
||||||
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
||||||
{
|
{
|
||||||
Settings.PowerPointSettings.IsSupportWPS = false;
|
Settings.PowerPointSettings.IsSupportWPS = false;
|
||||||
ToggleSwitchSupportWPS.IsOn = false;
|
|
||||||
|
|
||||||
// 更新PPT管理器的WPS支持设置
|
|
||||||
if (_pptManager != null)
|
if (_pptManager != null)
|
||||||
{
|
{
|
||||||
_pptManager.IsSupportWPS = false;
|
_pptManager.IsSupportWPS = false;
|
||||||
@@ -2007,7 +2042,6 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
|
|
||||||
// 启动或停止PowerPoint进程守护
|
|
||||||
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
||||||
{
|
{
|
||||||
StartPowerPointProcessMonitoring();
|
StartPowerPointProcessMonitoring();
|
||||||
@@ -2036,16 +2070,16 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn;
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null)
|
||||||
|
Settings.PowerPointSettings.IsSupportWPS = toggle.IsOn;
|
||||||
|
|
||||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||||
{
|
{
|
||||||
if (!Settings.PowerPointSettings.PowerPointSupport)
|
if (!Settings.PowerPointSettings.PowerPointSupport)
|
||||||
{
|
{
|
||||||
Settings.PowerPointSettings.PowerPointSupport = true;
|
Settings.PowerPointSettings.PowerPointSupport = true;
|
||||||
ToggleSwitchSupportPowerPoint.IsOn = true;
|
|
||||||
|
|
||||||
// 启动PPT监控
|
|
||||||
if (_pptManager == null)
|
if (_pptManager == null)
|
||||||
{
|
{
|
||||||
InitializePPTManagers();
|
InitializePPTManagers();
|
||||||
@@ -2056,12 +2090,10 @@ namespace Ink_Canvas
|
|||||||
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
||||||
{
|
{
|
||||||
Settings.PowerPointSettings.EnablePowerPointEnhancement = false;
|
Settings.PowerPointSettings.EnablePowerPointEnhancement = false;
|
||||||
ToggleSwitchPowerPointEnhancement.IsOn = false;
|
|
||||||
StopPowerPointProcessMonitoring();
|
StopPowerPointProcessMonitoring();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新PPT管理器的WPS支持设置与翻页跳过动画设置
|
|
||||||
if (_pptManager != null)
|
if (_pptManager != null)
|
||||||
{
|
{
|
||||||
_pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS;
|
_pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS;
|
||||||
@@ -2075,7 +2107,9 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
Settings.PowerPointSettings.SkipAnimationsWhenGoNext = ToggleSwitchSkipAnimationsWhenGoNext.IsOn;
|
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||||
|
if (toggle != null)
|
||||||
|
Settings.PowerPointSettings.SkipAnimationsWhenGoNext = toggle.IsOn;
|
||||||
|
|
||||||
if (_pptManager != null)
|
if (_pptManager != null)
|
||||||
{
|
{
|
||||||
@@ -2213,100 +2247,14 @@ namespace Ink_Canvas
|
|||||||
/// 2. 检查是否启用了PPT按钮页码点击功能
|
/// 2. 检查是否启用了PPT按钮页码点击功能
|
||||||
/// 3. 根据按下的按钮设置相应的反馈边框透明度
|
/// 3. 根据按下的按钮设置相应的反馈边框透明度
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e)
|
private void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e) { }
|
||||||
|
private void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e) { }
|
||||||
|
private void PPTNavigationBtn_MouseUp(object sender, MouseButtonEventArgs e) { }
|
||||||
|
|
||||||
|
/// <summary>由 PptNavBar 控件 PageClick 事件触发的页码点击逻辑。</summary>
|
||||||
|
private async Task OnPptNavBarPageClickAsync(Controls.PptNavBar bar)
|
||||||
{
|
{
|
||||||
lastBorderMouseDownObject = sender;
|
|
||||||
if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return;
|
if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return;
|
||||||
if (sender == PPTLSPageButton)
|
|
||||||
{
|
|
||||||
PPTLSPageButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSPageButton)
|
|
||||||
{
|
|
||||||
PPTRSPageButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBPageButton)
|
|
||||||
{
|
|
||||||
PPTLBPageButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBPageButton)
|
|
||||||
{
|
|
||||||
PPTRBPageButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT导航按钮的鼠标离开事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户鼠标离开PPT导航按钮时执行以下操作:
|
|
||||||
/// 1. 重置按下的按钮对象为null
|
|
||||||
/// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
|
||||||
/// </remarks>
|
|
||||||
private void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
lastBorderMouseDownObject = null;
|
|
||||||
if (sender == PPTLSPageButton)
|
|
||||||
{
|
|
||||||
PPTLSPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSPageButton)
|
|
||||||
{
|
|
||||||
PPTRSPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBPageButton)
|
|
||||||
{
|
|
||||||
PPTLBPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBPageButton)
|
|
||||||
{
|
|
||||||
PPTRBPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT导航按钮的鼠标释放事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标按钮事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户释放PPT导航按钮时执行以下操作:
|
|
||||||
/// 1. 检查释放的按钮是否与按下的按钮一致
|
|
||||||
/// 2. 隐藏按钮的反馈效果
|
|
||||||
/// 3. 检查是否启用了PPT按钮页码点击功能
|
|
||||||
/// 4. 检查PPT是否已连接且在放映状态
|
|
||||||
/// 5. 设置背景透明度和颜色
|
|
||||||
/// 6. 切换到光标模式
|
|
||||||
/// 7. 尝试显示PPT幻灯片导航
|
|
||||||
/// 8. 如果浮动栏未折叠,则调整其位置
|
|
||||||
/// 9. 捕获并记录可能的异常
|
|
||||||
/// </remarks>
|
|
||||||
private async void PPTNavigationBtn_MouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (lastBorderMouseDownObject != sender) return;
|
|
||||||
|
|
||||||
if (sender == PPTLSPageButton)
|
|
||||||
{
|
|
||||||
PPTLSPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSPageButton)
|
|
||||||
{
|
|
||||||
PPTRSPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBPageButton)
|
|
||||||
{
|
|
||||||
PPTLBPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBPageButton)
|
|
||||||
{
|
|
||||||
PPTRBPageButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return;
|
|
||||||
|
|
||||||
// 使用新的PPT管理器检查连接状态
|
|
||||||
if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true)
|
if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("PPT未连接或未在放映状态,无法执行页码点击操作", LogHelper.LogType.Warning);
|
LogHelper.WriteLogToFile("PPT未连接或未在放映状态,无法执行页码点击操作", LogHelper.LogType.Warning);
|
||||||
@@ -2319,22 +2267,58 @@ namespace Ink_Canvas
|
|||||||
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
|
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
|
||||||
CursorIcon_Click(null, null);
|
CursorIcon_Click(null, null);
|
||||||
|
|
||||||
// 使用新的PPT管理器显示导航
|
if (Settings.PowerPointSettings.EnablePPTButtonEnhancedPreview && bar != null)
|
||||||
if (_pptManager.TryShowSlideNavigation())
|
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace);
|
// 侧边条点击时,把增强预览重定向到同侧的底部条上展开
|
||||||
// 若启用了“翻页时跳过PPT动画”,显示导航后把焦点拉回本窗口
|
var targetBar = ResolvePreviewTargetBar(bar);
|
||||||
if (Settings.PowerPointSettings.SkipAnimationsWhenGoNext)
|
if (targetBar == null)
|
||||||
{
|
{
|
||||||
try { this.Activate(); } catch { }
|
_pptManager.TryShowSlideNavigation();
|
||||||
|
}
|
||||||
|
else if (targetBar.IsPreviewExpanded)
|
||||||
|
{
|
||||||
|
targetBar.IsPreviewExpanded = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var slides = await RunOnStaAsync(BuildPptPreviewItems);
|
||||||
|
if (slides == null || slides.Count == 0)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("PPT增强预览未生成可用缩略图,改用默认导航", LogHelper.LogType.Warning);
|
||||||
|
_pptManager.TryShowSlideNavigation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var items = new List<Controls.PptNavBar.PreviewItem>(slides.Count);
|
||||||
|
foreach (var s in slides)
|
||||||
|
{
|
||||||
|
items.Add(new Controls.PptNavBar.PreviewItem
|
||||||
|
{
|
||||||
|
SlideNumber = s.SlideNumber,
|
||||||
|
Thumbnail = s.Thumbnail
|
||||||
|
});
|
||||||
|
}
|
||||||
|
targetBar.PreviewItems = items;
|
||||||
|
targetBar.CurrentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||||
|
targetBar.IsPreviewExpanded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning);
|
if (_pptManager.TryShowSlideNavigation())
|
||||||
|
{
|
||||||
|
if (Settings.PowerPointSettings.SkipAnimationsWhenGoNext)
|
||||||
|
{
|
||||||
|
try { this.Activate(); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 控制居中
|
|
||||||
if (!isFloatingBarFolded)
|
if (!isFloatingBarFolded)
|
||||||
{
|
{
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
@@ -2347,6 +2331,282 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPptNavBarSlideSelected(Controls.PptNavBar bar, int slideNumber)
|
||||||
|
{
|
||||||
|
try { _pptManager?.TryNavigateToSlide(slideNumber); }
|
||||||
|
catch (Exception ex) { LogHelper.WriteLogToFile($"PPT增强预览跳转异常: {ex}", LogHelper.LogType.Error); }
|
||||||
|
finally { if (bar != null) bar.IsPreviewExpanded = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选择承载增强预览的底部条:
|
||||||
|
/// - 来自侧边条的点击重定向到同侧底部条;
|
||||||
|
/// - 若同侧底部条不可用,退化到任意可用的底部条;
|
||||||
|
/// - 来自底部条的点击保持原行为。
|
||||||
|
/// </summary>
|
||||||
|
private Controls.PptNavBar ResolvePreviewTargetBar(Controls.PptNavBar source)
|
||||||
|
{
|
||||||
|
if (source == null) return null;
|
||||||
|
switch (source.Direction)
|
||||||
|
{
|
||||||
|
case Controls.PptNavBar.NavDirection.LeftSide:
|
||||||
|
return PickVisibleBar(LeftBottomPanelForPPTNavigation, RightBottomPanelForPPTNavigation) ?? source;
|
||||||
|
case Controls.PptNavBar.NavDirection.RightSide:
|
||||||
|
return PickVisibleBar(RightBottomPanelForPPTNavigation, LeftBottomPanelForPPTNavigation) ?? source;
|
||||||
|
default:
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Controls.PptNavBar PickVisibleBar(params Controls.PptNavBar[] candidates)
|
||||||
|
{
|
||||||
|
foreach (var c in candidates)
|
||||||
|
{
|
||||||
|
if (c != null && c.Visibility == Visibility.Visible) return c;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class PptEnhancedPreviewItem
|
||||||
|
{
|
||||||
|
public int SlideNumber { get; set; }
|
||||||
|
public BitmapImage Thumbnail { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CollapseAllPptNavBarPreviews()
|
||||||
|
{
|
||||||
|
var bars = new[]
|
||||||
|
{
|
||||||
|
LeftBottomPanelForPPTNavigation,
|
||||||
|
RightBottomPanelForPPTNavigation,
|
||||||
|
LeftSidePanelForPPTNavigation,
|
||||||
|
RightSidePanelForPPTNavigation,
|
||||||
|
};
|
||||||
|
foreach (var bar in bars)
|
||||||
|
{
|
||||||
|
if (bar == null) continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bar.IsPreviewExpanded = false;
|
||||||
|
bar.PreviewItems = null;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>在 MainWindow 加载完成后调用,把 4 个 PptNavBar 的事件接到本类。</summary>
|
||||||
|
private bool _pptNavBarsWired;
|
||||||
|
|
||||||
|
private void WirePptNavBars()
|
||||||
|
{
|
||||||
|
// InitializePPTManagers 可能被多次调用(切换 COM/ROT、设置变更等)。
|
||||||
|
// PptNavBar 事件若在同一控件上重复订阅,会导致翻页、长按、预览展开等逻辑成倍触发。
|
||||||
|
if (_pptNavBarsWired) return;
|
||||||
|
|
||||||
|
var bars = new[]
|
||||||
|
{
|
||||||
|
LeftBottomPanelForPPTNavigation,
|
||||||
|
RightBottomPanelForPPTNavigation,
|
||||||
|
LeftSidePanelForPPTNavigation,
|
||||||
|
RightSidePanelForPPTNavigation,
|
||||||
|
};
|
||||||
|
foreach (var bar in bars)
|
||||||
|
{
|
||||||
|
if (bar == null) continue;
|
||||||
|
bar.PreviousClick += (s, e) => BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
|
||||||
|
bar.NextClick += (s, e) => BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
|
||||||
|
bar.PreviousPressedDown += (s, e) =>
|
||||||
|
{
|
||||||
|
if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn)
|
||||||
|
StartLongPressDetection(s, false);
|
||||||
|
};
|
||||||
|
bar.NextPressedDown += (s, e) =>
|
||||||
|
{
|
||||||
|
if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn)
|
||||||
|
StartLongPressDetection(s, true);
|
||||||
|
};
|
||||||
|
bar.PressEnded += (s, e) => StopLongPressDetection();
|
||||||
|
var captured = bar;
|
||||||
|
bar.PageClick += async (s, e) => await OnPptNavBarPageClickAsync(captured);
|
||||||
|
bar.SlideSelected += (s, slideNumber) => OnPptNavBarSlideSelected(captured, slideNumber);
|
||||||
|
bar.PreviewExpandedChanged += (s, expanded) => OnPptNavBarPreviewExpandedChanged(captured, expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pptNavBarsWired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _suppressPreviewExpandedSync;
|
||||||
|
|
||||||
|
private void OnPptNavBarPreviewExpandedChanged(Controls.PptNavBar bar, bool expanded)
|
||||||
|
{
|
||||||
|
if (_suppressPreviewExpandedSync) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_suppressPreviewExpandedSync = true;
|
||||||
|
|
||||||
|
if (expanded)
|
||||||
|
{
|
||||||
|
// 仅允许同时一侧展开
|
||||||
|
foreach (var other in new[]
|
||||||
|
{
|
||||||
|
LeftBottomPanelForPPTNavigation,
|
||||||
|
RightBottomPanelForPPTNavigation,
|
||||||
|
LeftSidePanelForPPTNavigation,
|
||||||
|
RightSidePanelForPPTNavigation,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (other == null || ReferenceEquals(other, bar)) continue;
|
||||||
|
if (other.IsPreviewExpanded) other.IsPreviewExpanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部条展开时,隐藏同侧的中部侧边条避免遮挡;收起后还原可见性
|
||||||
|
ApplyBottomBarSideOcclusion();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_suppressPreviewExpandedSync = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyBottomBarSideOcclusion()
|
||||||
|
{
|
||||||
|
var leftBottomExpanded = LeftBottomPanelForPPTNavigation != null && LeftBottomPanelForPPTNavigation.IsPreviewExpanded;
|
||||||
|
var rightBottomExpanded = RightBottomPanelForPPTNavigation != null && RightBottomPanelForPPTNavigation.IsPreviewExpanded;
|
||||||
|
|
||||||
|
// 同侧的侧边条在底部条展开时隐藏
|
||||||
|
if (LeftSidePanelForPPTNavigation != null)
|
||||||
|
{
|
||||||
|
if (leftBottomExpanded)
|
||||||
|
{
|
||||||
|
LeftSidePanelForPPTNavigation.Tag = LeftSidePanelForPPTNavigation.Visibility;
|
||||||
|
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
else if (LeftSidePanelForPPTNavigation.Tag is Visibility cached)
|
||||||
|
{
|
||||||
|
LeftSidePanelForPPTNavigation.Visibility = cached;
|
||||||
|
LeftSidePanelForPPTNavigation.ClearValue(TagProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (RightSidePanelForPPTNavigation != null)
|
||||||
|
{
|
||||||
|
if (rightBottomExpanded)
|
||||||
|
{
|
||||||
|
RightSidePanelForPPTNavigation.Tag = RightSidePanelForPPTNavigation.Visibility;
|
||||||
|
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
else if (RightSidePanelForPPTNavigation.Tag is Visibility cached)
|
||||||
|
{
|
||||||
|
RightSidePanelForPPTNavigation.Visibility = cached;
|
||||||
|
RightSidePanelForPPTNavigation.ClearValue(TagProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<T> RunOnStaAsync<T>(Func<T> func)
|
||||||
|
{
|
||||||
|
// Office interop 要求 STA + COM 单元;Task.Run 跑到 MTA 线程池里会触发 RPC_E_WRONG_THREAD
|
||||||
|
// 等随机 COM 失败,表现为增强预览空白或崩溃。显式创建 STA worker 在其中执行导出。
|
||||||
|
var tcs = new TaskCompletionSource<T>();
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try { tcs.SetResult(func()); }
|
||||||
|
catch (Exception ex) { tcs.SetException(ex); }
|
||||||
|
});
|
||||||
|
thread.IsBackground = true;
|
||||||
|
thread.SetApartmentState(ApartmentState.STA);
|
||||||
|
thread.Start();
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PptEnhancedPreviewItem> BuildPptPreviewItems()
|
||||||
|
{
|
||||||
|
var result = new List<PptEnhancedPreviewItem>();
|
||||||
|
string tempDir = null;
|
||||||
|
Presentation activePresentation = null;
|
||||||
|
Slides slides = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
activePresentation = _pptManager?.GetCurrentActivePresentation() as Presentation;
|
||||||
|
if (activePresentation == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
slides = activePresentation.Slides;
|
||||||
|
if (slides == null) return result;
|
||||||
|
|
||||||
|
int count = slides.Count;
|
||||||
|
if (count <= 0) return result;
|
||||||
|
|
||||||
|
tempDir = Path.Combine(Path.GetTempPath(), "InkCanvas", "PPTPreviews", Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(tempDir);
|
||||||
|
|
||||||
|
for (int i = 1; i <= count; i++)
|
||||||
|
{
|
||||||
|
Slide slide = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
slide = slides[i];
|
||||||
|
var imagePath = Path.Combine(tempDir, $"slide_{i:0000}.png");
|
||||||
|
slide.Export(imagePath, "PNG", 480, 270);
|
||||||
|
var image = LoadBitmapImage(imagePath);
|
||||||
|
if (image == null) continue;
|
||||||
|
|
||||||
|
result.Add(new PptEnhancedPreviewItem
|
||||||
|
{
|
||||||
|
SlideNumber = i,
|
||||||
|
Thumbnail = image
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"生成PPT第{i}页缩略图失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (slide != null)
|
||||||
|
{
|
||||||
|
try { Marshal.ReleaseComObject(slide); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"构建PPT增强预览列表失败: {ex}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(tempDir) && Directory.Exists(tempDir))
|
||||||
|
{
|
||||||
|
try { Directory.Delete(tempDir, true); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BitmapImage LoadBitmapImage(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(path)) return null;
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
bitmap.BeginInit();
|
||||||
|
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bitmap.UriSource = new Uri(path, UriKind.Absolute);
|
||||||
|
bitmap.EndInit();
|
||||||
|
bitmap.Freeze();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理“开始幻灯片放映”按钮的点击事件
|
/// 处理“开始幻灯片放映”按钮的点击事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2475,6 +2735,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await Application.Current.Dispatcher.InvokeAsync(() => CollapseAllPptNavBarPreviews());
|
||||||
|
|
||||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
|
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
|
||||||
{
|
{
|
||||||
FoldFloatingBar_MouseUp(new object(), null);
|
FoldFloatingBar_MouseUp(new object(), null);
|
||||||
@@ -2499,209 +2761,30 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e)
|
private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
lastBorderMouseDownObject = sender;
|
// 旧 XAML 入口已废弃,事件由 PptNavBar 控件转发;保留方法签名以兼容潜在外部引用。
|
||||||
if (sender == PPTLSPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLSPreviousButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRSPreviousButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLBPreviousButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRBPreviousButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动长按检测
|
|
||||||
if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn)
|
|
||||||
{
|
|
||||||
StartLongPressDetection(sender, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT上一页控制按钮的鼠标离开事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户鼠标离开PPT上一页控制按钮时执行以下操作:
|
|
||||||
/// 1. 重置按下的按钮对象为null
|
|
||||||
/// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
|
||||||
/// 3. 停止长按检测
|
|
||||||
/// </remarks>
|
|
||||||
private void GridPPTControlPrevious_MouseLeave(object sender, MouseEventArgs e)
|
private void GridPPTControlPrevious_MouseLeave(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
lastBorderMouseDownObject = null;
|
|
||||||
if (sender == PPTLSPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLSPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRSPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLBPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRBPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止长按检测
|
|
||||||
StopLongPressDetection();
|
StopLongPressDetection();
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT上一页控制按钮的鼠标释放事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标按钮事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户释放PPT上一页控制按钮时执行以下操作:
|
|
||||||
/// 1. 检查释放的按钮是否与按下的按钮一致
|
|
||||||
/// 2. 根据释放的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
|
||||||
/// 3. 停止长按检测
|
|
||||||
/// 4. 调用上一页按钮的点击事件处理方法,实现切换到上一页的功能
|
|
||||||
/// </remarks>
|
|
||||||
private void GridPPTControlPrevious_MouseUp(object sender, MouseButtonEventArgs e)
|
private void GridPPTControlPrevious_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (lastBorderMouseDownObject != sender) return;
|
|
||||||
if (sender == PPTLSPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLSPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRSPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLBPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBPreviousButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRBPreviousButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止长按检测
|
|
||||||
StopLongPressDetection();
|
StopLongPressDetection();
|
||||||
|
|
||||||
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
|
BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT下一页控制按钮的鼠标按下事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标按钮事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户按下PPT下一页控制按钮时执行以下操作:
|
|
||||||
/// 1. 记录按下的按钮对象
|
|
||||||
/// 2. 根据按下的按钮设置相应的反馈边框透明度
|
|
||||||
/// 3. 如果启用了PPT按钮长按翻页功能,则启动长按检测
|
|
||||||
/// </remarks>
|
|
||||||
private void GridPPTControlNext_MouseDown(object sender, MouseButtonEventArgs e)
|
private void GridPPTControlNext_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
lastBorderMouseDownObject = sender;
|
// 旧 XAML 入口已废弃,事件由 PptNavBar 控件转发;保留方法签名以兼容潜在外部引用。
|
||||||
if (sender == PPTLSNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLSNextButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRSNextButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLBNextButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRBNextButtonFeedbackBorder.Opacity = 0.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动长按检测
|
|
||||||
if (Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn)
|
|
||||||
{
|
|
||||||
StartLongPressDetection(sender, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT下一页控制按钮的鼠标离开事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户鼠标离开PPT下一页控制按钮时执行以下操作:
|
|
||||||
/// 1. 重置按下的按钮对象为null
|
|
||||||
/// 2. 根据离开的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
|
||||||
/// 3. 停止长按检测
|
|
||||||
/// </remarks>
|
|
||||||
private void GridPPTControlNext_MouseLeave(object sender, MouseEventArgs e)
|
private void GridPPTControlNext_MouseLeave(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
lastBorderMouseDownObject = null;
|
|
||||||
if (sender == PPTLSNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLSNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRSNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLBNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRBNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止长按检测
|
|
||||||
StopLongPressDetection();
|
StopLongPressDetection();
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// 处理PPT下一页控制按钮的鼠标释放事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">事件的来源对象</param>
|
|
||||||
/// <param name="e">鼠标按钮事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// 该方法在用户释放PPT下一页控制按钮时执行以下操作:
|
|
||||||
/// 1. 检查释放的按钮是否与按下的按钮一致
|
|
||||||
/// 2. 根据释放的按钮设置相应的反馈边框透明度为0(隐藏反馈效果)
|
|
||||||
/// 3. 停止长按检测
|
|
||||||
/// 4. 调用下一页按钮的点击事件处理方法,实现切换到下一页的功能
|
|
||||||
/// </remarks>
|
|
||||||
private void GridPPTControlNext_MouseUp(object sender, MouseButtonEventArgs e)
|
private void GridPPTControlNext_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (lastBorderMouseDownObject != sender) return;
|
|
||||||
if (sender == PPTLSNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLSNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRSNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRSNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTLBNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTLBNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
else if (sender == PPTRBNextButtonBorder)
|
|
||||||
{
|
|
||||||
PPTRBNextButtonFeedbackBorder.Opacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止长按检测
|
|
||||||
StopLongPressDetection();
|
StopLongPressDetection();
|
||||||
|
|
||||||
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
|
BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return;
|
if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return;
|
||||||
|
|
||||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
AnimationsHelper.HidePopupWithSlideAndFade(BorderTools);
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
AnimationsHelper.HidePopupWithSlideAndFade(BoardBorderToolsPopup);
|
||||||
|
|
||||||
GridNotifications.Visibility = Visibility.Collapsed;
|
GridNotifications.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
@@ -132,7 +132,19 @@ namespace Ink_Canvas
|
|||||||
+ (currentMode == 0 ? "Annotation Strokes" : "BlackBoard Strokes");
|
+ (currentMode == 0 ? "Annotation Strokes" : "BlackBoard Strokes");
|
||||||
if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
|
if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
|
||||||
string savePathWithName;
|
string savePathWithName;
|
||||||
if (currentMode != 0) // 黑板模式下
|
if (Settings.Automation.IsUseCustomSaveFileName)
|
||||||
|
{
|
||||||
|
var ctx = new SaveFileNameContext
|
||||||
|
{
|
||||||
|
Mode = currentMode == 0 ? "Annotation" : "BlackBoard",
|
||||||
|
Type = saveByUser ? "User" : "Auto",
|
||||||
|
Page = currentMode != 0 ? CurrentWhiteboardIndex : (int?)null,
|
||||||
|
Count = inkCanvas.Strokes.Count
|
||||||
|
};
|
||||||
|
var fname = SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate, ctx);
|
||||||
|
savePathWithName = savePath + @"\" + fname + ".icstk";
|
||||||
|
}
|
||||||
|
else if (currentMode != 0) // 黑板模式下
|
||||||
savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + " Page-" +
|
savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + " Page-" +
|
||||||
CurrentWhiteboardIndex + " StrokesCount-" + inkCanvas.Strokes.Count + ".icstk";
|
CurrentWhiteboardIndex + " StrokesCount-" + inkCanvas.Strokes.Count + ".icstk";
|
||||||
else
|
else
|
||||||
@@ -904,8 +916,8 @@ namespace Ink_Canvas
|
|||||||
private void SymbolIconOpenStrokes_MouseUp(object sender, MouseButtonEventArgs e)
|
private void SymbolIconOpenStrokes_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (lastBorderMouseDownObject != sender) return;
|
if (lastBorderMouseDownObject != sender) return;
|
||||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
AnimationsHelper.HidePopupWithSlideAndFade(BorderTools);
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
AnimationsHelper.HidePopupWithSlideAndFade(BoardBorderToolsPopup);
|
||||||
|
|
||||||
var openFileDialog = new OpenFileDialog();
|
var openFileDialog = new OpenFileDialog();
|
||||||
openFileDialog.InitialDirectory = Settings.Automation.AutoSavedStrokesLocation;
|
openFileDialog.InitialDirectory = Settings.Automation.AutoSavedStrokesLocation;
|
||||||
|
|||||||
@@ -43,8 +43,23 @@ namespace Ink_Canvas
|
|||||||
var basePath = Settings.Automation.AutoSavedStrokesLocation
|
var basePath = Settings.Automation.AutoSavedStrokesLocation
|
||||||
+ @"\Auto Saved - BlackBoard Strokes";
|
+ @"\Auto Saved - BlackBoard Strokes";
|
||||||
if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
|
if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);
|
||||||
strokeSavePath = Path.Combine(basePath,
|
string stem;
|
||||||
$"{DateTime.Now:yyyy-MM-dd HH-mm-ss-fff} Page-{pageIndexForStrokes} StrokesCount-{strokesToSave.Count}.icstk");
|
if (Settings.Automation.IsUseCustomSaveFileName)
|
||||||
|
{
|
||||||
|
stem = SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate,
|
||||||
|
new SaveFileNameContext
|
||||||
|
{
|
||||||
|
Mode = "BlackBoard",
|
||||||
|
Type = "Auto",
|
||||||
|
Page = pageIndexForStrokes,
|
||||||
|
Count = strokesToSave.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stem = $"{DateTime.Now:yyyy-MM-dd HH-mm-ss-fff} Page-{pageIndexForStrokes} StrokesCount-{strokesToSave.Count}";
|
||||||
|
}
|
||||||
|
strokeSavePath = Path.Combine(basePath, stem + ".icstk");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -149,7 +164,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
var desktopPath = Path.Combine(
|
var desktopPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
||||||
$"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png");
|
$"{GetScreenshotFileNameStem()}.png");
|
||||||
|
|
||||||
CaptureAndSaveScreenshot(desktopPath, false);
|
CaptureAndSaveScreenshot(desktopPath, false);
|
||||||
|
|
||||||
@@ -188,7 +203,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
var desktopPath = Path.Combine(
|
var desktopPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
|
||||||
$"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png");
|
$"{GetScreenshotFileNameStem()}.png");
|
||||||
|
|
||||||
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
||||||
{
|
{
|
||||||
@@ -353,7 +368,7 @@ namespace Ink_Canvas
|
|||||||
await Task.Delay(150);
|
await Task.Delay(150);
|
||||||
}
|
}
|
||||||
|
|
||||||
BtnWhiteBoardAdd_Click(null, EventArgs.Empty);
|
BtnWhiteBoardAdd_Click(null, new RoutedEventArgs());
|
||||||
|
|
||||||
await InsertBitmapSourceToCanvas(bitmapSourceForClipboard);
|
await InsertBitmapSourceToCanvas(bitmapSourceForClipboard);
|
||||||
}
|
}
|
||||||
@@ -437,7 +452,10 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(fileName))
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
{
|
{
|
||||||
fileName = DateTime.Now.ToString("HH-mm-ss");
|
fileName = Settings.Automation.IsUseCustomSaveFileName
|
||||||
|
? SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate,
|
||||||
|
new SaveFileNameContext { Mode = "Screenshot", Type = "Auto", Count = inkCanvas?.Strokes?.Count })
|
||||||
|
: DateTime.Now.ToString("HH-mm-ss");
|
||||||
}
|
}
|
||||||
|
|
||||||
var basePath = Settings.Automation.AutoSavedStrokesLocation;
|
var basePath = Settings.Automation.AutoSavedStrokesLocation;
|
||||||
@@ -473,7 +491,17 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
return Path.Combine(
|
return Path.Combine(
|
||||||
screenshotsFolder,
|
screenshotsFolder,
|
||||||
$"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png");
|
$"{GetScreenshotFileNameStem()}.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetScreenshotFileNameStem()
|
||||||
|
{
|
||||||
|
if (Settings.Automation.IsUseCustomSaveFileName)
|
||||||
|
{
|
||||||
|
return SaveFileNameHelper.Render(Settings.Automation.CustomSaveFileNameTemplate,
|
||||||
|
new SaveFileNameContext { Mode = "Screenshot", Type = "Auto", Count = inkCanvas?.Strokes?.Count });
|
||||||
|
}
|
||||||
|
return DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ink_Canvas.Controls;
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Controls;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -29,16 +28,16 @@ namespace Ink_Canvas
|
|||||||
/// <param name="sender">事件发送者</param>
|
/// <param name="sender">事件发送者</param>
|
||||||
/// <param name="e">鼠标按钮事件参数</param>
|
/// <param name="e">鼠标按钮事件参数</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 如果发送者是 RandomDrawPanel 或 SingleDrawPanel,且它们被隐藏,则不处理事件
|
/// 如果发送者是 BoardRandomDrawToolBtn 或 BoardSingleDrawToolBtn,且它们被隐藏,则不处理事件
|
||||||
/// 否则存储当前鼠标按下的对象
|
/// 否则存储当前鼠标按下的对象
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void Border_MouseDown(object sender, MouseButtonEventArgs e)
|
private void Border_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
// 如果发送者是 RandomDrawPanel 或 SingleDrawPanel,且它们被隐藏,则不处理事件
|
// 如果发送者是 BoardRandomDrawToolBtn 或 BoardSingleDrawToolBtn,且它们被隐藏,则不处理事件
|
||||||
if (sender is SimpleStackPanel panel)
|
if (sender is FrameworkElement element)
|
||||||
{
|
{
|
||||||
if ((panel == RandomDrawPanel || panel == SingleDrawPanel) &&
|
if ((element == BoardRandomDrawToolBtn || element == BoardSingleDrawToolBtn) &&
|
||||||
panel.Visibility != Visibility.Visible)
|
element.Visibility != Visibility.Visible)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Animation;
|
using System.Windows.Media.Animation;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||||
@@ -33,14 +32,8 @@ namespace Ink_Canvas
|
|||||||
/// 3. 如果形状绘制面板可见,则隐藏它
|
/// 3. 如果形状绘制面板可见,则隐藏它
|
||||||
/// 4. 如果形状绘制面板不可见,则显示它
|
/// 4. 如果形状绘制面板不可见,则显示它
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void ImageDrawShape_MouseUp(object sender, MouseButtonEventArgs e)
|
internal void ImageDrawShape_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
|
||||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
|
||||||
if (sender == ShapeDrawFloatingBarBtn && lastBorderMouseDownObject != ShapeDrawFloatingBarBtn) return;
|
|
||||||
|
|
||||||
// FloatingBarIcons_MouseUp_New(sender);
|
|
||||||
if (BorderDrawShape.Visibility == Visibility.Visible)
|
if (BorderDrawShape.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
||||||
@@ -2495,6 +2488,7 @@ namespace Ink_Canvas
|
|||||||
/// 用于标识鼠标是否处于按下状态,在绘制过程中使用
|
/// 用于标识鼠标是否处于按下状态,在绘制过程中使用
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private bool isMouseDown;
|
private bool isMouseDown;
|
||||||
|
private bool _isMouseRealtimeInking;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 触摸按下状态标志
|
/// 触摸按下状态标志
|
||||||
@@ -2518,6 +2512,18 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void inkCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
private void inkCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (e.ChangedButton == MouseButton.Left && ShouldUseRealtimeVelocityBrushTipForMouse() && drawingShapeMode == 0)
|
||||||
|
{
|
||||||
|
_isMouseRealtimeInking = true;
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
var p = e.GetPosition(inkCanvas);
|
||||||
|
CancelPauseStraightenTimer(MouseRealtimeStrokeId);
|
||||||
|
InitializeRealtimeBrushTipStateFromPoint(MouseRealtimeStrokeId, p);
|
||||||
|
var sv = GetStrokeVisual(MouseRealtimeStrokeId);
|
||||||
|
TryAppendRealtimeVelocityBrushTipPoint(sv, MouseRealtimeStrokeId, p);
|
||||||
|
sv.ForceRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
inkCanvas.CaptureMouse();
|
inkCanvas.CaptureMouse();
|
||||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||||
@@ -2538,7 +2544,24 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void inkCanvas_MouseMove(object sender, MouseEventArgs e)
|
private void inkCanvas_MouseMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (isMouseDown) MouseTouchMove(e.GetPosition(inkCanvas));
|
if (_isMouseRealtimeInking && isMouseDown)
|
||||||
|
{
|
||||||
|
var sv = GetStrokeVisual(MouseRealtimeStrokeId);
|
||||||
|
if (TryAppendRealtimeVelocityBrushTipPoint(sv, MouseRealtimeStrokeId, e.GetPosition(inkCanvas)))
|
||||||
|
{
|
||||||
|
sv.ForceRedraw();
|
||||||
|
ResetPauseStraightenTimer(MouseRealtimeStrokeId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_isMouseRealtimeInking = false;
|
||||||
|
MouseTouchMove(e.GetPosition(inkCanvas));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isMouseDown)
|
||||||
|
{
|
||||||
|
MouseTouchMove(e.GetPosition(inkCanvas));
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.Canvas.IsShowCursor)
|
if (Settings.Canvas.IsShowCursor)
|
||||||
{
|
{
|
||||||
@@ -2566,6 +2589,38 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void inkCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
private void inkCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_isMouseRealtimeInking)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sv = GetStrokeVisual(MouseRealtimeStrokeId);
|
||||||
|
sv?.ForceRedraw();
|
||||||
|
var stroke = sv?.Stroke;
|
||||||
|
if (stroke != null)
|
||||||
|
{
|
||||||
|
if (!stroke.ContainsPropertyData(RealtimeVelocityBrushTipAppliedGuid))
|
||||||
|
stroke.AddPropertyData(RealtimeVelocityBrushTipAppliedGuid, true);
|
||||||
|
inkCanvas.Strokes.Add(stroke);
|
||||||
|
inkCanvas_StrokeCollected(inkCanvas, new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (VisualCanvasList.TryGetValue(MouseRealtimeStrokeId, out var vc) && inkCanvas.Children.Contains(vc))
|
||||||
|
inkCanvas.Children.Remove(vc);
|
||||||
|
StrokeVisualList.Remove(MouseRealtimeStrokeId);
|
||||||
|
VisualCanvasList.Remove(MouseRealtimeStrokeId);
|
||||||
|
TouchDownPointsList.Remove(MouseRealtimeStrokeId);
|
||||||
|
CleanupRealtimeBrushTipState(MouseRealtimeStrokeId);
|
||||||
|
CancelPauseStraightenTimer(MouseRealtimeStrokeId);
|
||||||
|
_isMouseRealtimeInking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HandleEraserOperationEnded();
|
HandleEraserOperationEnded();
|
||||||
inkCanvas.ReleaseMouseCapture();
|
inkCanvas.ReleaseMouseCapture();
|
||||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
private Helpers.ModernInkAnalyzer _modernInkAnalyzer;
|
||||||
|
private Helpers.ModernInkAnalyzer ModernInkAnalyzer =>
|
||||||
|
_modernInkAnalyzer ??= new Helpers.ModernInkAnalyzer();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 存储新的笔画集合,用于形状识别
|
/// 存储新的笔画集合,用于形状识别
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -520,6 +524,7 @@ namespace Ink_Canvas
|
|||||||
&& penType != 1
|
&& penType != 1
|
||||||
&& e.Stroke?.DrawingAttributes != null
|
&& e.Stroke?.DrawingAttributes != null
|
||||||
&& !e.Stroke.DrawingAttributes.IsHighlighter
|
&& !e.Stroke.DrawingAttributes.IsHighlighter
|
||||||
|
&& !e.Stroke.ContainsPropertyData(RealtimeVelocityBrushTipAppliedGuid)
|
||||||
&& e.Stroke.StylusPoints.Count >= 3)
|
&& e.Stroke.StylusPoints.Count >= 3)
|
||||||
{
|
{
|
||||||
ApplyVelocityBrushTipFromSpeed(e.Stroke);
|
ApplyVelocityBrushTipFromSpeed(e.Stroke);
|
||||||
@@ -563,13 +568,10 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
|
straightStroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
|
|
||||||
// Replace the original stroke with the straightened one
|
// Replace the original stroke with the straightened one
|
||||||
SetNewBackupOfStroke();
|
ReplaceStrokesSafely(null, straightStroke, e.Stroke);
|
||||||
_currentCommitType = CommitReason.ShapeRecognition;
|
|
||||||
inkCanvas.Strokes.Remove(e.Stroke);
|
|
||||||
inkCanvas.Strokes.Add(straightStroke);
|
|
||||||
_currentCommitType = CommitReason.UserInput;
|
|
||||||
|
|
||||||
straightStrokeForHandwritingKey = straightStroke;
|
straightStrokeForHandwritingKey = straightStroke;
|
||||||
|
|
||||||
@@ -616,17 +618,26 @@ namespace Ink_Canvas
|
|||||||
ProcessRectangleGuideLines(e.Stroke);
|
ProcessRectangleGuideLines(e.Stroke);
|
||||||
|
|
||||||
var shapeMode = ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine);
|
var shapeMode = ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine);
|
||||||
var strokeReco = new StrokeCollection();
|
InkShapeRecognitionResult result = InkShapeRecognitionResult.Empty;
|
||||||
var result = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(newStrokes, shapeMode);
|
|
||||||
for (var i = newStrokes.Count - 1; i >= 0; i--)
|
if (ShapeRecognitionRouter.ResolveUseWinRt(shapeMode) && Helpers.WinRtInkShapeRecognizer.IsApiAvailable)
|
||||||
{
|
{
|
||||||
strokeReco.Add(newStrokes[i]);
|
result = await ModernInkAnalyzer.AnalyzeAsync(newStrokes);
|
||||||
var newResult = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(strokeReco, shapeMode);
|
}
|
||||||
if (newResult.IsSuccess &&
|
else
|
||||||
(newResult.ShapeName == "Circle" || newResult.ShapeName == "Ellipse"))
|
{
|
||||||
|
var strokeReco = new StrokeCollection();
|
||||||
|
result = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(newStrokes, shapeMode);
|
||||||
|
for (var i = newStrokes.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
result = newResult;
|
strokeReco.Add(newStrokes[i]);
|
||||||
break;
|
var newResult = await InkRecognizeHelper.RecognizeShapeUnifiedAsync(strokeReco, shapeMode);
|
||||||
|
if (newResult.IsSuccess &&
|
||||||
|
(newResult.ShapeName == "Circle" || newResult.ShapeName == "Ellipse"))
|
||||||
|
{
|
||||||
|
result = newResult;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,12 +697,9 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
|
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
circles.Add(new Circle(result.Centroid, result.ShapeWidth / 2.0, stroke));
|
circles.Add(new Circle(result.Centroid, result.ShapeWidth / 2.0, stroke));
|
||||||
SetNewBackupOfStroke();
|
ReplaceStrokesSafely(result.StrokesToRemove, stroke, e.Stroke);
|
||||||
_currentCommitType = CommitReason.ShapeRecognition;
|
|
||||||
inkCanvas.Strokes.Remove(result.StrokesToRemove);
|
|
||||||
inkCanvas.Strokes.Add(stroke);
|
|
||||||
_currentCommitType = CommitReason.UserInput;
|
|
||||||
newStrokes = new StrokeCollection();
|
newStrokes = new StrokeCollection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -718,6 +726,18 @@ namespace Ink_Canvas
|
|||||||
var endP = new Point(result.Centroid.X + result.ShapeWidth / 2,
|
var endP = new Point(result.Centroid.X + result.ShapeWidth / 2,
|
||||||
result.Centroid.Y + result.ShapeHeight / 2);
|
result.Centroid.Y + result.ShapeHeight / 2);
|
||||||
|
|
||||||
|
// WinRT 返回的热点顺序/方向不稳定时,用点集反推 IACore 风格椭圆参数(中心/长短轴/方向/四个端点)
|
||||||
|
var hasEllipseParams = TryEstimateEllipseParamsFromStrokes(
|
||||||
|
result.StrokesToRemove,
|
||||||
|
out var ellipseCentroid,
|
||||||
|
out var ellipseA,
|
||||||
|
out var ellipseB,
|
||||||
|
out var ellipseThetaRad,
|
||||||
|
out var ellipseMajor0,
|
||||||
|
out var ellipseMajor1,
|
||||||
|
out var ellipseMinor0,
|
||||||
|
out var ellipseMinor1);
|
||||||
|
|
||||||
foreach (var circle in circles)
|
foreach (var circle in circles)
|
||||||
//判断是否画同心椭圆
|
//判断是否画同心椭圆
|
||||||
if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 &&
|
if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 &&
|
||||||
@@ -766,9 +786,6 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
var topB = endP.Y - iniP.Y;
|
var topB = endP.Y - iniP.Y;
|
||||||
|
|
||||||
SetNewBackupOfStroke();
|
|
||||||
_currentCommitType = CommitReason.ShapeRecognition;
|
|
||||||
inkCanvas.Strokes.Remove(result.StrokesToRemove);
|
|
||||||
newStrokes = new StrokeCollection();
|
newStrokes = new StrokeCollection();
|
||||||
|
|
||||||
var _pointList = GenerateEllipseGeometry(iniP, endP, false);
|
var _pointList = GenerateEllipseGeometry(iniP, endP, false);
|
||||||
@@ -777,14 +794,14 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
|
_stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
var _dashedLineStroke =
|
var _dashedLineStroke =
|
||||||
GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false);
|
GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false);
|
||||||
var strokes = new StrokeCollection {
|
var strokes = new StrokeCollection {
|
||||||
_stroke,
|
_stroke,
|
||||||
_dashedLineStroke
|
_dashedLineStroke
|
||||||
};
|
};
|
||||||
inkCanvas.Strokes.Add(strokes);
|
ReplaceStrokesSafely(result.StrokesToRemove, strokes, e.Stroke);
|
||||||
_currentCommitType = CommitReason.UserInput;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,13 +822,17 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//纠正垂直与水平关系
|
// 用反推参数替换中心与长短轴(比 WinRT 的包围盒更接近 IACore,且不会竖横翻转)
|
||||||
var newPoints = FixPointsDirection(p[0], p[2]);
|
if (hasEllipseParams)
|
||||||
p[0] = newPoints[0];
|
{
|
||||||
p[2] = newPoints[1];
|
result.Centroid = ellipseCentroid;
|
||||||
newPoints = FixPointsDirection(p[1], p[3]);
|
a = ellipseA;
|
||||||
p[1] = newPoints[0];
|
b = ellipseB;
|
||||||
p[3] = newPoints[1];
|
iniP = new Point(result.Centroid.X - a, result.Centroid.Y - b);
|
||||||
|
endP = new Point(result.Centroid.X + a, result.Centroid.Y + b);
|
||||||
|
// 用端点重写热点,保证后续回退分支也一致
|
||||||
|
p = new PointCollection { ellipseMajor0, ellipseMinor0, ellipseMajor1, ellipseMinor1 };
|
||||||
|
}
|
||||||
|
|
||||||
var pointList = GenerateEllipseGeometry(iniP, endP);
|
var pointList = GenerateEllipseGeometry(iniP, endP);
|
||||||
var point = new StylusPointCollection(pointList);
|
var point = new StylusPointCollection(pointList);
|
||||||
@@ -819,22 +840,18 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
|
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
|
|
||||||
if (needRotation)
|
if (needRotation)
|
||||||
{
|
{
|
||||||
var m = new Matrix();
|
var m = new Matrix();
|
||||||
var fe = e.Source as FrameworkElement;
|
// 优先使用反推参数角度;否则用端点向量角度(使用 Atan2 避免斜率无穷)
|
||||||
var tanTheta = (p[2].Y - p[0].Y) / (p[2].X - p[0].X);
|
var theta = hasEllipseParams ? ellipseThetaRad : Math.Atan2(p[2].Y - p[0].Y, p[2].X - p[0].X);
|
||||||
var theta = Math.Atan(tanTheta);
|
|
||||||
m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y);
|
m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y);
|
||||||
stroke.Transform(m, false);
|
stroke.Transform(m, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetNewBackupOfStroke();
|
ReplaceStrokesSafely(result.StrokesToRemove, stroke, e.Stroke);
|
||||||
_currentCommitType = CommitReason.ShapeRecognition;
|
|
||||||
inkCanvas.Strokes.Remove(result.StrokesToRemove);
|
|
||||||
inkCanvas.Strokes.Add(stroke);
|
|
||||||
_currentCommitType = CommitReason.UserInput;
|
|
||||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||||
newStrokes = new StrokeCollection();
|
newStrokes = new StrokeCollection();
|
||||||
}
|
}
|
||||||
@@ -867,11 +884,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
SetNewBackupOfStroke();
|
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
_currentCommitType = CommitReason.ShapeRecognition;
|
ReplaceStrokesSafely(result.StrokesToRemove, stroke, e.Stroke);
|
||||||
inkCanvas.Strokes.Remove(result.StrokesToRemove);
|
|
||||||
inkCanvas.Strokes.Add(stroke);
|
|
||||||
_currentCommitType = CommitReason.UserInput;
|
|
||||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||||
newStrokes = new StrokeCollection();
|
newStrokes = new StrokeCollection();
|
||||||
}
|
}
|
||||||
@@ -912,11 +926,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
SetNewBackupOfStroke();
|
stroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
_currentCommitType = CommitReason.ShapeRecognition;
|
ReplaceStrokesSafely(result.StrokesToRemove, stroke, e.Stroke);
|
||||||
inkCanvas.Strokes.Remove(result.StrokesToRemove);
|
|
||||||
inkCanvas.Strokes.Add(stroke);
|
|
||||||
_currentCommitType = CommitReason.UserInput;
|
|
||||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||||
newStrokes = new StrokeCollection();
|
newStrokes = new StrokeCollection();
|
||||||
}
|
}
|
||||||
@@ -2189,6 +2200,49 @@ namespace Ink_Canvas
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReplaceStrokesSafely(StrokeCollection strokesToRemove, Stroke replacementStroke, Stroke fallbackOriginalStroke = null)
|
||||||
|
{
|
||||||
|
if (replacementStroke == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetNewBackupOfStroke();
|
||||||
|
_currentCommitType = CommitReason.ShapeRecognition;
|
||||||
|
|
||||||
|
if (strokesToRemove != null && strokesToRemove.Count > 0)
|
||||||
|
inkCanvas.Strokes.Remove(strokesToRemove);
|
||||||
|
if (fallbackOriginalStroke != null && inkCanvas.Strokes.Contains(fallbackOriginalStroke))
|
||||||
|
inkCanvas.Strokes.Remove(fallbackOriginalStroke);
|
||||||
|
|
||||||
|
if (!inkCanvas.Strokes.Contains(replacementStroke))
|
||||||
|
inkCanvas.Strokes.Add(replacementStroke);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_currentCommitType = CommitReason.UserInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplaceStrokesSafely(StrokeCollection strokesToRemove, StrokeCollection replacementStrokes, Stroke fallbackOriginalStroke = null)
|
||||||
|
{
|
||||||
|
if (replacementStrokes == null || replacementStrokes.Count == 0) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SetNewBackupOfStroke();
|
||||||
|
_currentCommitType = CommitReason.ShapeRecognition;
|
||||||
|
|
||||||
|
if (strokesToRemove != null && strokesToRemove.Count > 0)
|
||||||
|
inkCanvas.Strokes.Remove(strokesToRemove);
|
||||||
|
if (fallbackOriginalStroke != null && inkCanvas.Strokes.Contains(fallbackOriginalStroke))
|
||||||
|
inkCanvas.Strokes.Remove(fallbackOriginalStroke);
|
||||||
|
|
||||||
|
inkCanvas.Strokes.Add(replacementStrokes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_currentCommitType = CommitReason.UserInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将沿线速度映射为压感并与硬件压感混合,快写略细、慢写略粗;在落笔时(及手写笔移动时由调用方)统一施加。
|
/// 将沿线速度映射为压感并与硬件压感混合,快写略细、慢写略粗;在落笔时(及手写笔移动时由调用方)统一施加。
|
||||||
/// 无压感设备上系统可能将 <see cref="DrawingAttributes.IgnorePressure"/> 置为 true,此处强制关闭以便粗细随合成压感变化。
|
/// 无压感设备上系统可能将 <see cref="DrawingAttributes.IgnorePressure"/> 置为 true,此处强制关闭以便粗细随合成压感变化。
|
||||||
@@ -2282,6 +2336,124 @@ namespace Ink_Canvas
|
|||||||
return new Point[2] { p1, p2 };
|
return new Point[2] { p1, p2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用点集拟合出 IACore 风格椭圆参数(中心/长短半轴/方向/四个端点)。
|
||||||
|
/// 解决 WinRT 返回热点顺序不稳定导致椭圆纠正角度翻转的问题。
|
||||||
|
/// </summary>
|
||||||
|
private static bool TryEstimateEllipseParamsFromStrokes(
|
||||||
|
StrokeCollection strokes,
|
||||||
|
out Point centroid,
|
||||||
|
out double a,
|
||||||
|
out double b,
|
||||||
|
out double thetaRad,
|
||||||
|
out Point major0,
|
||||||
|
out Point major1,
|
||||||
|
out Point minor0,
|
||||||
|
out Point minor1)
|
||||||
|
{
|
||||||
|
centroid = default;
|
||||||
|
a = b = 0;
|
||||||
|
thetaRad = 0;
|
||||||
|
major0 = major1 = minor0 = minor1 = default;
|
||||||
|
|
||||||
|
if (strokes == null || strokes.Count == 0) return false;
|
||||||
|
|
||||||
|
var pts = new List<Point>(256);
|
||||||
|
foreach (var s in strokes)
|
||||||
|
{
|
||||||
|
if (s?.StylusPoints == null) continue;
|
||||||
|
foreach (var sp in s.StylusPoints)
|
||||||
|
pts.Add(sp.ToPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pts.Count < 12) return false;
|
||||||
|
|
||||||
|
double mx = 0, my = 0;
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
mx += pts[i].X;
|
||||||
|
my += pts[i].Y;
|
||||||
|
}
|
||||||
|
mx /= pts.Count;
|
||||||
|
my /= pts.Count;
|
||||||
|
centroid = new Point(mx, my);
|
||||||
|
|
||||||
|
double sxx = 0, syy = 0, sxy = 0;
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
var dx = pts[i].X - mx;
|
||||||
|
var dy = pts[i].Y - my;
|
||||||
|
sxx += dx * dx;
|
||||||
|
syy += dy * dy;
|
||||||
|
sxy += dx * dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sxx + syy < 1e-6) return false;
|
||||||
|
|
||||||
|
thetaRad = 0.5 * Math.Atan2(2.0 * sxy, sxx - syy);
|
||||||
|
if (double.IsNaN(thetaRad) || double.IsInfinity(thetaRad)) return false;
|
||||||
|
|
||||||
|
// 主轴单位向量 v1=(cos,sin),次轴 v2=(-sin,cos)
|
||||||
|
var cos = Math.Cos(thetaRad);
|
||||||
|
var sin = Math.Sin(thetaRad);
|
||||||
|
|
||||||
|
// 投影收集,用分位数抑制离群点
|
||||||
|
var us = new double[pts.Count];
|
||||||
|
var vs = new double[pts.Count];
|
||||||
|
double maxU = double.MinValue, minU = double.MaxValue;
|
||||||
|
double maxV = double.MinValue, minV = double.MaxValue;
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
var dx = pts[i].X - mx;
|
||||||
|
var dy = pts[i].Y - my;
|
||||||
|
var u = dx * cos + dy * sin;
|
||||||
|
var v = -dx * sin + dy * cos;
|
||||||
|
us[i] = u;
|
||||||
|
vs[i] = v;
|
||||||
|
if (u > maxU) maxU = u;
|
||||||
|
if (u < minU) minU = u;
|
||||||
|
if (v > maxV) maxV = v;
|
||||||
|
if (v < minV) minV = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Sort(us);
|
||||||
|
Array.Sort(vs);
|
||||||
|
|
||||||
|
int hi = (int)Math.Round((pts.Count - 1) * 0.98);
|
||||||
|
int lo = (int)Math.Round((pts.Count - 1) * 0.02);
|
||||||
|
hi = Math.Max(0, Math.Min(pts.Count - 1, hi));
|
||||||
|
lo = Math.Max(0, Math.Min(pts.Count - 1, lo));
|
||||||
|
|
||||||
|
var uHi = us[hi];
|
||||||
|
var uLo = us[lo];
|
||||||
|
var vHi = vs[hi];
|
||||||
|
var vLo = vs[lo];
|
||||||
|
|
||||||
|
var aCandidate = Math.Max(Math.Abs(uHi), Math.Abs(uLo));
|
||||||
|
var bCandidate = Math.Max(Math.Abs(vHi), Math.Abs(vLo));
|
||||||
|
if (aCandidate < 1e-3) aCandidate = Math.Max(Math.Abs(maxU), Math.Abs(minU));
|
||||||
|
if (bCandidate < 1e-3) bCandidate = Math.Max(Math.Abs(maxV), Math.Abs(minV));
|
||||||
|
|
||||||
|
a = aCandidate;
|
||||||
|
b = bCandidate;
|
||||||
|
|
||||||
|
// 保证 a 为长半轴
|
||||||
|
if (b > a)
|
||||||
|
{
|
||||||
|
var t = a; a = b; b = t;
|
||||||
|
thetaRad += Math.PI / 2;
|
||||||
|
cos = Math.Cos(thetaRad);
|
||||||
|
sin = Math.Sin(thetaRad);
|
||||||
|
}
|
||||||
|
|
||||||
|
major0 = new Point(mx - a * cos, my - a * sin);
|
||||||
|
major1 = new Point(mx + a * cos, my + a * sin);
|
||||||
|
minor0 = new Point(mx + b * sin, my - b * cos);
|
||||||
|
minor1 = new Point(mx - b * sin, my + b * cos);
|
||||||
|
|
||||||
|
return a > 1e-2 && b > 1e-2;
|
||||||
|
}
|
||||||
|
|
||||||
public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points)
|
public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points)
|
||||||
{
|
{
|
||||||
var newPoint = new StylusPointCollection();
|
var newPoint = new StylusPointCollection();
|
||||||
@@ -2701,6 +2873,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||||
};
|
};
|
||||||
|
rectangleStroke.AddPropertyData(Helpers.ModernInkAnalyzer.ShapeStrokePropertyGuid, true);
|
||||||
|
|
||||||
// 移除原有的四条直线
|
// 移除原有的四条直线
|
||||||
SetNewBackupOfStroke();
|
SetNewBackupOfStroke();
|
||||||
|
|||||||
@@ -134,23 +134,35 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (item.StrokeHasBeenCleared)
|
if (item.StrokeHasBeenCleared)
|
||||||
{
|
{
|
||||||
foreach (var strokes in item.CurrentStroke)
|
if (item.CurrentStroke != null)
|
||||||
if (canvas.Strokes.Contains(strokes))
|
{
|
||||||
canvas.Strokes.Remove(strokes);
|
foreach (var strokes in item.CurrentStroke)
|
||||||
|
if (canvas.Strokes.Contains(strokes))
|
||||||
|
canvas.Strokes.Remove(strokes);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var strokes in item.ReplacedStroke)
|
if (item.ReplacedStroke != null)
|
||||||
if (!canvas.Strokes.Contains(strokes))
|
{
|
||||||
canvas.Strokes.Add(strokes);
|
foreach (var strokes in item.ReplacedStroke)
|
||||||
|
if (!canvas.Strokes.Contains(strokes))
|
||||||
|
canvas.Strokes.Add(strokes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var strokes in item.CurrentStroke)
|
if (item.CurrentStroke != null)
|
||||||
if (!canvas.Strokes.Contains(strokes))
|
{
|
||||||
canvas.Strokes.Add(strokes);
|
foreach (var strokes in item.CurrentStroke)
|
||||||
|
if (!canvas.Strokes.Contains(strokes))
|
||||||
|
canvas.Strokes.Add(strokes);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var strokes in item.ReplacedStroke)
|
if (item.ReplacedStroke != null)
|
||||||
if (canvas.Strokes.Contains(strokes))
|
{
|
||||||
canvas.Strokes.Remove(strokes);
|
foreach (var strokes in item.ReplacedStroke)
|
||||||
|
if (canvas.Strokes.Contains(strokes))
|
||||||
|
canvas.Strokes.Remove(strokes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (item.CommitType == TimeMachineHistoryType.Manipulation)
|
else if (item.CommitType == TimeMachineHistoryType.Manipulation)
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ namespace Ink_Canvas
|
|||||||
/// 进程终止定时器
|
/// 进程终止定时器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Timer timerKillProcess = new Timer();
|
private Timer timerKillProcess = new Timer();
|
||||||
|
|
||||||
|
public void UpdateAutoKillProcessTimer(bool shouldRun)
|
||||||
|
{
|
||||||
|
if (shouldRun) timerKillProcess.Start();
|
||||||
|
else timerKillProcess.Stop();
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 统一的主窗口定时器
|
/// 统一的主窗口定时器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -89,7 +95,11 @@ namespace Ink_Canvas
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 可用的最新版本号
|
/// 可用的最新版本号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string AvailableLatestVersion;
|
internal string AvailableLatestVersion;
|
||||||
|
/// <summary>
|
||||||
|
/// 最近一次自动检查得到的更新说明(Markdown)
|
||||||
|
/// </summary>
|
||||||
|
internal string AvailableLatestReleaseNotes;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 静默更新检查定时器
|
/// 静默更新检查定时器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -294,7 +304,7 @@ namespace Ink_Canvas
|
|||||||
/// 如果启用,则根据Settings.Automation.AutoSaveStrokesIntervalMinutes设置定时器间隔
|
/// 如果启用,则根据Settings.Automation.AutoSaveStrokesIntervalMinutes设置定时器间隔
|
||||||
/// 最小间隔为1分钟
|
/// 最小间隔为1分钟
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void UpdateAutoSaveStrokesTimer()
|
public void UpdateAutoSaveStrokesTimer()
|
||||||
{
|
{
|
||||||
if (autoSaveStrokesTimer == null) return;
|
if (autoSaveStrokesTimer == null) return;
|
||||||
|
|
||||||
@@ -582,7 +592,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
// 先展开浮动栏,然后进入批注状态
|
// 先展开浮动栏,然后进入批注状态
|
||||||
// UnFoldFloatingBar 方法内部会根据设置自动进入批注模式
|
// UnFoldFloatingBar 方法内部会根据设置自动进入批注模式
|
||||||
UnFoldFloatingBar(null);
|
_ = UnFoldFloatingBar(null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -703,10 +713,11 @@ namespace Ink_Canvas
|
|||||||
var fullScreenWindows = _windowOverviewModel.GetFullScreenWindows();
|
var fullScreenWindows = _windowOverviewModel.GetFullScreenWindows();
|
||||||
if (fullScreenWindows == null || fullScreenWindows.Count == 0) return false;
|
if (fullScreenWindows == null || fullScreenWindows.Count == 0) return false;
|
||||||
|
|
||||||
|
var foregroundHandle = ForegroundWindowInfo.GetForegroundWindowHandle();
|
||||||
|
|
||||||
foreach (var window in fullScreenWindows)
|
foreach (var window in fullScreenWindows)
|
||||||
{
|
{
|
||||||
var windowProcessName = window.ProcessName;
|
var windowProcessName = window.ProcessName;
|
||||||
var windowRect = window.Rect;
|
|
||||||
|
|
||||||
if (windowProcessName == "EasiNote")
|
if (windowProcessName == "EasiNote")
|
||||||
{
|
{
|
||||||
@@ -718,15 +729,18 @@ namespace Ink_Canvas
|
|||||||
string version = versionInfo.FileVersion;
|
string version = versionInfo.FileVersion;
|
||||||
string prodName = versionInfo.ProductName;
|
string prodName = versionInfo.ProductName;
|
||||||
|
|
||||||
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
|
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote &&
|
||||||
|
window.Handle == foregroundHandle)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
|
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3 &&
|
||||||
|
window.Handle == foregroundHandle)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C)
|
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C &&
|
||||||
|
window.Handle == foregroundHandle)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -834,19 +848,15 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 从窗口预览模型中获取窗口列表(已按ZOrder排序,最上层在前)
|
|
||||||
var windows = _windowOverviewModel.Windows;
|
var windows = _windowOverviewModel.Windows;
|
||||||
if (windows == null || windows.Count == 0) return false;
|
if (windows == null || windows.Count == 0) return false;
|
||||||
|
|
||||||
// 获取前台窗口(ZOrder最小的窗口,即最上层)
|
var foregroundHandle = ForegroundWindowInfo.GetForegroundWindowHandle();
|
||||||
var foregroundWindow = windows.FirstOrDefault();
|
var foregroundWindow = windows.FirstOrDefault(w => w.Handle == foregroundHandle);
|
||||||
if (foregroundWindow == null) return false;
|
if (foregroundWindow == null) return false;
|
||||||
|
|
||||||
var windowProcessName = foregroundWindow.ProcessName;
|
var windowProcessName = foregroundWindow.ProcessName;
|
||||||
var windowTitle = foregroundWindow.Title;
|
|
||||||
var windowRect = foregroundWindow.Rect;
|
|
||||||
|
|
||||||
// 检查EasiNote
|
|
||||||
if (windowProcessName == "EasiNote")
|
if (windowProcessName == "EasiNote")
|
||||||
{
|
{
|
||||||
if (foregroundWindow.ProcessPath != "Unknown")
|
if (foregroundWindow.ProcessPath != "Unknown")
|
||||||
@@ -857,25 +867,18 @@ namespace Ink_Canvas
|
|||||||
string version = versionInfo.FileVersion;
|
string version = versionInfo.FileVersion;
|
||||||
string prodName = versionInfo.ProductName;
|
string prodName = versionInfo.ProductName;
|
||||||
|
|
||||||
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
|
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote &&
|
||||||
|
foregroundWindow.IsFullScreen)
|
||||||
{
|
{
|
||||||
bool isAnnotationWindow = windowTitle.Length == 0 && windowRect.Height < 500;
|
return true;
|
||||||
if (Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno && isAnnotationWindow)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (!isAnnotationWindow)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
|
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3 &&
|
||||||
|
foregroundWindow.IsFullScreen)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C &&
|
else if (prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -883,100 +886,78 @@ namespace Ink_Canvas
|
|||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 检查EasiCamera
|
|
||||||
else if (Settings.Automation.IsAutoFoldInEasiCamera && windowProcessName == "EasiCamera" &&
|
else if (Settings.Automation.IsAutoFoldInEasiCamera && windowProcessName == "EasiCamera" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查EasiNote5C
|
|
||||||
else if (Settings.Automation.IsAutoFoldInEasiNote5C && windowProcessName == "EasiNote5C" &&
|
else if (Settings.Automation.IsAutoFoldInEasiNote5C && windowProcessName == "EasiNote5C" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查SeewoPinco
|
|
||||||
else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher &&
|
else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher &&
|
||||||
(windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher"))
|
(windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher") &&
|
||||||
|
foregroundWindow.IsFullScreen)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查HiteCamera
|
|
||||||
else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera" &&
|
else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查HiteTouchPro
|
|
||||||
else if (Settings.Automation.IsAutoFoldInHiteTouchPro && windowProcessName == "HiteTouchPro" &&
|
else if (Settings.Automation.IsAutoFoldInHiteTouchPro && windowProcessName == "HiteTouchPro" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查WxBoardMain
|
|
||||||
else if (Settings.Automation.IsAutoFoldInWxBoardMain && windowProcessName == "WxBoardMain" &&
|
else if (Settings.Automation.IsAutoFoldInWxBoardMain && windowProcessName == "WxBoardMain" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查MSWhiteboard
|
|
||||||
else if (Settings.Automation.IsAutoFoldInMSWhiteboard &&
|
else if (Settings.Automation.IsAutoFoldInMSWhiteboard &&
|
||||||
(windowProcessName == "MicrosoftWhiteboard" || windowProcessName == "msedgewebview2"))
|
(windowProcessName == "MicrosoftWhiteboard" || windowProcessName == "msedgewebview2") &&
|
||||||
|
foregroundWindow.IsFullScreen)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查OldZyBoard
|
|
||||||
else if (Settings.Automation.IsAutoFoldInOldZyBoard &&
|
else if (Settings.Automation.IsAutoFoldInOldZyBoard &&
|
||||||
(WinTabWindowsChecker.IsWindowExisted("WhiteBoard - DrawingWindow") ||
|
(WinTabWindowsChecker.IsWindowExisted("WhiteBoard - DrawingWindow") ||
|
||||||
WinTabWindowsChecker.IsWindowExisted("InstantAnnotationWindow")))
|
WinTabWindowsChecker.IsWindowExisted("InstantAnnotationWindow")) &&
|
||||||
|
foregroundWindow.IsFullScreen)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查HiteLightBoard
|
|
||||||
else if (Settings.Automation.IsAutoFoldInHiteLightBoard && windowProcessName == "HiteLightBoard" &&
|
else if (Settings.Automation.IsAutoFoldInHiteLightBoard && windowProcessName == "HiteLightBoard" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查AdmoxWhiteboard
|
|
||||||
else if (Settings.Automation.IsAutoFoldInAdmoxWhiteboard && windowProcessName == "Amdox.WhiteBoard" &&
|
else if (Settings.Automation.IsAutoFoldInAdmoxWhiteboard && windowProcessName == "Amdox.WhiteBoard" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查AdmoxBooth
|
|
||||||
else if (Settings.Automation.IsAutoFoldInAdmoxBooth && windowProcessName == "Amdox.Booth" &&
|
else if (Settings.Automation.IsAutoFoldInAdmoxBooth && windowProcessName == "Amdox.Booth" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查QPoint
|
|
||||||
else if (Settings.Automation.IsAutoFoldInQPoint && windowProcessName == "QPoint" &&
|
else if (Settings.Automation.IsAutoFoldInQPoint && windowProcessName == "QPoint" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查YiYunVisualPresenter
|
|
||||||
else if (Settings.Automation.IsAutoFoldInYiYunVisualPresenter && windowProcessName == "YiYunVisualPresenter" &&
|
else if (Settings.Automation.IsAutoFoldInYiYunVisualPresenter && windowProcessName == "YiYunVisualPresenter" &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 检查MaxHubWhiteboard
|
|
||||||
else if (Settings.Automation.IsAutoFoldInMaxHubWhiteboard && windowProcessName == "WhiteBoard" &&
|
else if (Settings.Automation.IsAutoFoldInMaxHubWhiteboard && windowProcessName == "WhiteBoard" &&
|
||||||
WinTabWindowsChecker.IsWindowExisted("白板书写") &&
|
WinTabWindowsChecker.IsWindowExisted("白板书写") &&
|
||||||
windowRect.Height >= SystemParameters.WorkArea.Height - 16 &&
|
foregroundWindow.IsFullScreen)
|
||||||
windowRect.Width >= SystemParameters.WorkArea.Width - 16)
|
|
||||||
{
|
{
|
||||||
if (foregroundWindow.ProcessPath != "Unknown")
|
if (foregroundWindow.ProcessPath != "Unknown")
|
||||||
{
|
{
|
||||||
@@ -1017,19 +998,23 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void timerCheckAutoFold_Elapsed(object sender, ElapsedEventArgs e)
|
private void timerCheckAutoFold_Elapsed(object sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
if (isFloatingBarChangingHideMode) return;
|
if (isFloatingBarChangingHideMode) return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool hasFullScreen = HasFullScreenWindowOfAutoFoldApps();
|
bool hasFullScreen = HasFullScreenWindowOfAutoFoldApps();
|
||||||
bool shouldAutoFold = CheckShouldAutoFoldByWindowPreview();
|
bool shouldAutoFold = CheckShouldAutoFoldByWindowPreview();
|
||||||
var windowProcessName = ForegroundWindowInfo.ProcessName();
|
|
||||||
var windowTitle = ForegroundWindowInfo.WindowTitle();
|
|
||||||
|
|
||||||
Thickness currentMargin = new Thickness();
|
Thickness currentMargin = new Thickness();
|
||||||
Dispatcher.Invoke(() =>
|
try
|
||||||
{
|
{
|
||||||
currentMargin = ViewboxFloatingBar.Margin;
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
});
|
Dispatcher.Invoke(() => { currentMargin = ViewboxFloatingBar.Margin; });
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasFullScreen)
|
if (hasFullScreen)
|
||||||
{
|
{
|
||||||
@@ -1046,38 +1031,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
if (shouldAutoFold)
|
if (shouldAutoFold)
|
||||||
{
|
{
|
||||||
if (windowProcessName == "EasiNote")
|
if (!unfoldFloatingBarByUser && !isFloatingBarFolded)
|
||||||
{
|
|
||||||
if (ForegroundWindowInfo.ProcessPath() != "Unknown")
|
|
||||||
{
|
|
||||||
var versionInfo = FileVersionInfo.GetVersionInfo(ForegroundWindowInfo.ProcessPath());
|
|
||||||
string version = versionInfo.FileVersion;
|
|
||||||
|
|
||||||
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
|
|
||||||
{
|
|
||||||
bool isAnnotationWindow = windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500;
|
|
||||||
if (Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno && isAnnotationWindow)
|
|
||||||
{
|
|
||||||
if (!isFloatingBarFolded)
|
|
||||||
{
|
|
||||||
FoldFloatingBar_MouseUp(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!isAnnotationWindow)
|
|
||||||
{
|
|
||||||
if (!unfoldFloatingBarByUser && !isFloatingBarFolded)
|
|
||||||
{
|
|
||||||
FoldFloatingBar_MouseUp(null, null);
|
|
||||||
}
|
|
||||||
else if (unfoldFloatingBarByUser)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 处理其他目标软件
|
|
||||||
else if (!unfoldFloatingBarByUser && !isFloatingBarFolded)
|
|
||||||
{
|
{
|
||||||
FoldFloatingBar_MouseUp(null, null);
|
FoldFloatingBar_MouseUp(null, null);
|
||||||
}
|
}
|
||||||
@@ -1095,6 +1049,8 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// schedule unfold if dispatcher still running
|
||||||
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
UnFoldFloatingBar_MouseUp(new object(), null);
|
UnFoldFloatingBar_MouseUp(new object(), null);
|
||||||
unfoldFloatingBarByUser = false;
|
unfoldFloatingBarByUser = false;
|
||||||
}
|
}
|
||||||
@@ -1215,35 +1171,44 @@ namespace Ink_Canvas
|
|||||||
// 空闲状态的判定为不处于批注模式和画板模式
|
// 空闲状态的判定为不处于批注模式和画板模式
|
||||||
bool canSafelyUpdate = false;
|
bool canSafelyUpdate = false;
|
||||||
|
|
||||||
Dispatcher.Invoke(() =>
|
try
|
||||||
{
|
{
|
||||||
try
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
// 判断是否处于批注模式(inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
try
|
||||||
// 判断是否处于画板模式(!Topmost)
|
|
||||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink && Topmost)
|
|
||||||
{
|
{
|
||||||
// 检查是否有未保存的内容或正在进行的操作
|
// 判断是否处于批注模式(inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||||
if (!isHidingSubPanelsWhenInking)
|
// 判断是否处于画板模式(!Topmost)
|
||||||
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink && Topmost)
|
||||||
{
|
{
|
||||||
canSafelyUpdate = true;
|
// 检查是否有未保存的内容或正在进行的操作
|
||||||
LogHelper.WriteLogToFile("AutoUpdate | Application is in a safe state for update - not in ink or board mode");
|
if (!isHidingSubPanelsWhenInking)
|
||||||
|
{
|
||||||
|
canSafelyUpdate = true;
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | Application is in a safe state for update - not in ink or board mode");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | Application is currently performing operations");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("AutoUpdate | Application is currently performing operations");
|
LogHelper.WriteLogToFile("AutoUpdate | Application is in ink or board mode, cannot update now");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("AutoUpdate | Application is in ink or board mode, cannot update now");
|
LogHelper.WriteLogToFile($"AutoUpdate | Error checking application state: {ex.Message}", LogHelper.LogType.Error);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
catch (Exception ex)
|
}
|
||||||
{
|
catch (Exception)
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | Error checking application state: {ex.Message}", LogHelper.LogType.Error);
|
{
|
||||||
}
|
// Dispatcher not available
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (canSafelyUpdate)
|
if (canSafelyUpdate)
|
||||||
{
|
{
|
||||||
@@ -1256,10 +1221,12 @@ namespace Ink_Canvas
|
|||||||
AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true);
|
AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true);
|
||||||
|
|
||||||
// 关闭应用程序
|
// 关闭应用程序
|
||||||
Dispatcher.Invoke(() =>
|
try
|
||||||
{
|
{
|
||||||
Application.Current.Shutdown();
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
});
|
Dispatcher.Invoke(() => { Application.Current.Shutdown(); });
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1324,11 +1291,13 @@ namespace Ink_Canvas
|
|||||||
// 清除之前的更新状态
|
// 清除之前的更新状态
|
||||||
AvailableLatestVersion = null;
|
AvailableLatestVersion = null;
|
||||||
AvailableLatestLineGroup = null;
|
AvailableLatestLineGroup = null;
|
||||||
|
AvailableLatestReleaseNotes = null;
|
||||||
|
|
||||||
// 使用当前选择的更新通道检查更新
|
// 使用当前选择的更新通道检查更新
|
||||||
var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel);
|
var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel);
|
||||||
AvailableLatestVersion = remoteVersion;
|
AvailableLatestVersion = remoteVersion;
|
||||||
AvailableLatestLineGroup = lineGroup;
|
AvailableLatestLineGroup = lineGroup;
|
||||||
|
AvailableLatestReleaseNotes = apiReleaseNotes;
|
||||||
|
|
||||||
if (AvailableLatestVersion != null)
|
if (AvailableLatestVersion != null)
|
||||||
{
|
{
|
||||||
@@ -1388,6 +1357,11 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StartSilentUpdateTimer()
|
||||||
|
{
|
||||||
|
timerCheckAutoUpdateWithSilence.Start();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化橡皮擦自动切换回批注模式计时器
|
/// 初始化橡皮擦自动切换回批注模式计时器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1400,6 +1374,98 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在窗口关闭时停止并释放所有定时器与事件,防止在 Dispatcher 关闭期间还有后台线程调用 UI
|
||||||
|
/// </summary>
|
||||||
|
private void StopAllTimersAndHandlers()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Stop and detach System.Timers.Timer
|
||||||
|
if (_unifiedMainWindowTimer != null)
|
||||||
|
{
|
||||||
|
_unifiedMainWindowTimer.Stop();
|
||||||
|
_unifiedMainWindowTimer.Elapsed -= OnUnifiedMainWindowTimerElapsed;
|
||||||
|
_unifiedMainWindowTimer.Dispose();
|
||||||
|
_unifiedMainWindowTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerKillProcess != null)
|
||||||
|
{
|
||||||
|
timerKillProcess.Stop();
|
||||||
|
timerKillProcess.Elapsed -= TimerKillProcess_Elapsed;
|
||||||
|
timerKillProcess.Dispose();
|
||||||
|
timerKillProcess = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerCheckAutoUpdateWithSilence != null)
|
||||||
|
{
|
||||||
|
timerCheckAutoUpdateWithSilence.Stop();
|
||||||
|
timerCheckAutoUpdateWithSilence.Elapsed -= timerCheckAutoUpdateWithSilence_Elapsed;
|
||||||
|
timerCheckAutoUpdateWithSilence.Dispose();
|
||||||
|
timerCheckAutoUpdateWithSilence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerCheckAutoUpdateRetry != null)
|
||||||
|
{
|
||||||
|
timerCheckAutoUpdateRetry.Stop();
|
||||||
|
timerCheckAutoUpdateRetry.Elapsed -= timerCheckAutoUpdateRetry_Elapsed;
|
||||||
|
timerCheckAutoUpdateRetry.Dispose();
|
||||||
|
timerCheckAutoUpdateRetry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerDisplayTime != null)
|
||||||
|
{
|
||||||
|
timerDisplayTime.Stop();
|
||||||
|
timerDisplayTime.Elapsed -= TimerDisplayTime_Elapsed;
|
||||||
|
timerDisplayTime.Dispose();
|
||||||
|
timerDisplayTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerDisplayDate != null)
|
||||||
|
{
|
||||||
|
timerDisplayDate.Stop();
|
||||||
|
timerDisplayDate.Elapsed -= TimerDisplayDate_Elapsed;
|
||||||
|
timerDisplayDate.Dispose();
|
||||||
|
timerDisplayDate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerNtpSync != null)
|
||||||
|
{
|
||||||
|
timerNtpSync.Stop();
|
||||||
|
timerNtpSync.Elapsed -= async (s, e) => await TimerNtpSync_ElapsedAsync();
|
||||||
|
timerNtpSync.Dispose();
|
||||||
|
timerNtpSync = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DispatcherTimers run on UI thread
|
||||||
|
if (autoSaveStrokesTimer != null)
|
||||||
|
{
|
||||||
|
autoSaveStrokesTimer.Stop();
|
||||||
|
autoSaveStrokesTimer.Tick -= AutoSaveStrokesTimer_Tick;
|
||||||
|
autoSaveStrokesTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_eraserAutoSwitchBackTimer != null)
|
||||||
|
{
|
||||||
|
_eraserAutoSwitchBackTimer.Stop();
|
||||||
|
_eraserAutoSwitchBackTimer.Tick -= EraserAutoSwitchBackTimer_Tick;
|
||||||
|
_eraserAutoSwitchBackTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"StopAllTimers failed: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosing(CancelEventArgs e)
|
||||||
|
{
|
||||||
|
// Stop timers and handlers to avoid background callbacks invoking Dispatcher after shutdown
|
||||||
|
StopAllTimersAndHandlers();
|
||||||
|
base.OnClosing(e);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动橡皮擦自动切换回批注模式计时器
|
/// 启动橡皮擦自动切换回批注模式计时器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1452,6 +1518,7 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void EraserAutoSwitchBackTimer_Tick(object sender, EventArgs e)
|
private void EraserAutoSwitchBackTimer_Tick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 检查是否仍然在橡皮擦模式
|
// 检查是否仍然在橡皮擦模式
|
||||||
@@ -1470,12 +1537,17 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 切换到批注模式
|
// 切换到批注模式
|
||||||
Dispatcher.Invoke(() =>
|
try
|
||||||
{
|
{
|
||||||
PenIcon_Click(null, null);
|
if (Dispatcher.HasShutdownStarted || Dispatcher.HasShutdownFinished) return;
|
||||||
StopEraserAutoSwitchBackTimer();
|
Dispatcher.Invoke(() =>
|
||||||
LogHelper.WriteLogToFile("橡皮擦自动切换回批注模式", LogHelper.LogType.Event);
|
{
|
||||||
});
|
PenIcon_Click(null, null);
|
||||||
|
StopEraserAutoSwitchBackTimer();
|
||||||
|
LogHelper.WriteLogToFile("橡皮擦自动切换回批注模式", LogHelper.LogType.Event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using Ink_Canvas.Controls;
|
||||||
|
using Ink_Canvas.Controls.Toolbar;
|
||||||
|
using Ink_Canvas.Helpers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas
|
||||||
|
{
|
||||||
|
public partial class MainWindow
|
||||||
|
{
|
||||||
|
internal ToolbarImageButton SymbolIconDelete { get; private set; }
|
||||||
|
internal ToolbarImageButton Eraser_Icon { get; private set; }
|
||||||
|
internal ToolbarImageButton EraserByStrokes_Icon { get; private set; }
|
||||||
|
internal ToolbarImageButton SymbolIconSelect { get; private set; }
|
||||||
|
internal ToolbarImageButton ShapeDrawFloatingBarBtn { get; private set; }
|
||||||
|
internal ToolbarImageButton SymbolIconUndo { get; private set; }
|
||||||
|
internal ToolbarImageButton SymbolIconRedo { get; private set; }
|
||||||
|
internal ToolbarImageButton CursorWithDelFloatingBarBtn { get; private set; }
|
||||||
|
internal ToolbarImageButton WhiteboardFloatingBarBtn { get; private set; }
|
||||||
|
internal ToolbarImageButton ToolsFloatingBarBtn { get; private set; }
|
||||||
|
internal ToolbarImageButton Fold_Icon { get; private set; }
|
||||||
|
|
||||||
|
internal void AttachCursorIconView(ToolbarImageButton btn) => Cursor_Icon = btn;
|
||||||
|
internal void AttachPenIconView(ToolbarImageButton btn) => Pen_Icon = btn;
|
||||||
|
internal void AttachSymbolIconDelete(ToolbarImageButton btn) => SymbolIconDelete = btn;
|
||||||
|
internal void AttachEraserIcon(ToolbarImageButton btn) => Eraser_Icon = btn;
|
||||||
|
internal void AttachEraserByStrokesIcon(ToolbarImageButton btn) => EraserByStrokes_Icon = btn;
|
||||||
|
internal void AttachSymbolIconSelect(ToolbarImageButton btn) => SymbolIconSelect = btn;
|
||||||
|
internal void AttachShapeDrawBtn(ToolbarImageButton btn) => ShapeDrawFloatingBarBtn = btn;
|
||||||
|
internal void AttachSymbolIconUndo(ToolbarImageButton btn) => SymbolIconUndo = btn;
|
||||||
|
internal void AttachSymbolIconRedo(ToolbarImageButton btn) => SymbolIconRedo = btn;
|
||||||
|
internal void AttachCursorWithDelBtn(ToolbarImageButton btn) => CursorWithDelFloatingBarBtn = btn;
|
||||||
|
internal void AttachWhiteboardBtn(ToolbarImageButton btn) => WhiteboardFloatingBarBtn = btn;
|
||||||
|
internal void AttachToolsBtn(ToolbarImageButton btn)
|
||||||
|
{
|
||||||
|
ToolsFloatingBarBtn = btn;
|
||||||
|
BorderTools.PlacementTarget = btn;
|
||||||
|
}
|
||||||
|
internal void AttachFoldIcon(ToolbarImageButton btn) => Fold_Icon = btn;
|
||||||
|
|
||||||
|
internal void InitializeToolbarPlugins()
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("MW_Toolbar: InitializeToolbarPlugins 开始", LogHelper.LogType.Info);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ToolbarHost = new ToolbarHost(this);
|
||||||
|
var slots = new Dictionary<ToolbarSlot, Panel>
|
||||||
|
{
|
||||||
|
{ ToolbarSlot.FloatingBarMain, StackPanelFloatingBar },
|
||||||
|
{ ToolbarSlot.FloatingBarCanvasControls, StackPanelCanvasControls },
|
||||||
|
{ ToolbarSlot.FloatingBarEnd, StackPanelFloatingBarEnd },
|
||||||
|
{ ToolbarSlot.BlackboardLeft, BlackboardLeftSide },
|
||||||
|
{ ToolbarSlot.BlackboardRight, BlackboardRightSide }
|
||||||
|
};
|
||||||
|
ToolbarRegistry.Populate(ToolbarHost, slots, Settings?.Toolbar);
|
||||||
|
LogHelper.WriteLogToFile("MW_Toolbar: InitializeToolbarPlugins 完成", LogHelper.LogType.Info);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"MW_Toolbar: InitializeToolbarPlugins 异常: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RebuildToolbar()
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("MW_Toolbar: RebuildToolbar 开始", LogHelper.LogType.Info);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ToolbarRegistry.ClearInjected(StackPanelFloatingBar);
|
||||||
|
ToolbarRegistry.ClearInjected(StackPanelCanvasControls);
|
||||||
|
ToolbarRegistry.ClearInjected(StackPanelFloatingBarEnd);
|
||||||
|
ToolbarRegistry.ClearInjected(BlackboardLeftSide);
|
||||||
|
ToolbarRegistry.ClearInjected(BlackboardRightSide);
|
||||||
|
InitializeToolbarPlugins();
|
||||||
|
LogHelper.WriteLogToFile("MW_Toolbar: RebuildToolbar 完成", LogHelper.LogType.Info);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"MW_Toolbar: RebuildToolbar 异常: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -8,6 +9,7 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Threading;
|
||||||
using Point = System.Windows.Point;
|
using Point = System.Windows.Point;
|
||||||
|
|
||||||
namespace Ink_Canvas
|
namespace Ink_Canvas
|
||||||
@@ -47,6 +49,375 @@ namespace Ink_Canvas
|
|||||||
private bool isMultiTouchTimerActive;
|
private bool isMultiTouchTimerActive;
|
||||||
private bool isPalmEraserActive;
|
private bool isPalmEraserActive;
|
||||||
private bool palmEraserWasEnabledBeforeMultiTouch;
|
private bool palmEraserWasEnabledBeforeMultiTouch;
|
||||||
|
private InkCanvasEditingMode palmEraserPreviousEditingMode = InkCanvasEditingMode.Ink;
|
||||||
|
private readonly Dictionary<int, RealtimeBrushTipState> _realtimeBrushTipStates = new Dictionary<int, RealtimeBrushTipState>();
|
||||||
|
private readonly Guid RealtimeVelocityBrushTipAppliedGuid = new Guid("74E57D95-945F-4A8C-B52A-7D3EF2D4FD5B");
|
||||||
|
internal const int MouseRealtimeStrokeId = -100001;
|
||||||
|
private readonly HashSet<int> _activeRealtimeTouchStrokeIds = new HashSet<int>();
|
||||||
|
private readonly HashSet<int> _activeTouchStrokeIds = new HashSet<int>();
|
||||||
|
|
||||||
|
private readonly Dictionary<int, DispatcherTimer> _pauseStraightenTimers = new Dictionary<int, DispatcherTimer>();
|
||||||
|
private const int PauseStraightenDelayMs = 300;
|
||||||
|
|
||||||
|
private sealed class OneEuroFilter
|
||||||
|
{
|
||||||
|
private readonly float _minCutoff;
|
||||||
|
private readonly float _beta;
|
||||||
|
private readonly float _dCutoff;
|
||||||
|
private bool _initialized;
|
||||||
|
private float _xPrev;
|
||||||
|
private float _dxPrev;
|
||||||
|
|
||||||
|
public OneEuroFilter(float minCutoff, float beta, float dCutoff)
|
||||||
|
{
|
||||||
|
_minCutoff = minCutoff;
|
||||||
|
_beta = beta;
|
||||||
|
_dCutoff = dCutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Filter(float value, float dt, float speed)
|
||||||
|
{
|
||||||
|
if (!_initialized)
|
||||||
|
{
|
||||||
|
_initialized = true;
|
||||||
|
_xPrev = value;
|
||||||
|
_dxPrev = 0f;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dx = (value - _xPrev) / Math.Max(1e-6f, dt);
|
||||||
|
var aD = Alpha(_dCutoff, dt);
|
||||||
|
var dxHat = Lerp(_dxPrev, dx, aD);
|
||||||
|
var a = Alpha(_minCutoff + _beta * speed, dt);
|
||||||
|
var xHat = Lerp(_xPrev, value, a);
|
||||||
|
_xPrev = xHat;
|
||||||
|
_dxPrev = dxHat;
|
||||||
|
return xHat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float Alpha(float cutoff, float dt)
|
||||||
|
{
|
||||||
|
var tau = 1f / (2f * (float)Math.PI * Math.Max(1e-3f, cutoff));
|
||||||
|
return 1f / (1f + tau / Math.Max(1e-6f, dt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float Lerp(float a, float b, float t) => a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RealtimeBrushTipState
|
||||||
|
{
|
||||||
|
public float LastRawX { get; set; }
|
||||||
|
public float LastRawY { get; set; }
|
||||||
|
public long LastTimestampMs { get; set; }
|
||||||
|
public float SmoothedSampleRateHz { get; set; } = 120f;
|
||||||
|
public bool SawPressureVariation { get; set; }
|
||||||
|
public bool HasSeed { get; set; }
|
||||||
|
public float LastSmoothX { get; set; }
|
||||||
|
public float LastSmoothY { get; set; }
|
||||||
|
public float LastSmoothPressure { get; set; } = 0.5f;
|
||||||
|
public OneEuroFilter FilterX { get; } = new OneEuroFilter(1.2f, 0.015f, 1f);
|
||||||
|
public OneEuroFilter FilterY { get; } = new OneEuroFilter(1.2f, 0.015f, 1f);
|
||||||
|
public OneEuroFilter FilterPressure { get; } = new OneEuroFilter(1f, 0.02f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long RealtimeNowMs() => Environment.TickCount64;
|
||||||
|
|
||||||
|
private static float RealtimeClamp(float x, float min, float max)
|
||||||
|
{
|
||||||
|
if (x < min) return min;
|
||||||
|
if (x > max) return max;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float WidthToPressure(float width, float baseWidth)
|
||||||
|
{
|
||||||
|
if (baseWidth <= 1e-4f) return 0.5f;
|
||||||
|
var scale = width / baseWidth;
|
||||||
|
return RealtimeClamp((scale - 0.42f) / 1.16f, 0.08f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldUseRealtimeVelocityBrushTip()
|
||||||
|
{
|
||||||
|
return Settings.Canvas.InkStyle == 3
|
||||||
|
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||||
|
&& !Settings.Canvas.DisablePressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldUseRealtimeVelocityBrushTipForTouch()
|
||||||
|
{
|
||||||
|
return Settings.Canvas.InkStyle == 3
|
||||||
|
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||||
|
&& !Settings.Canvas.DisablePressure
|
||||||
|
&& drawingShapeMode == 0
|
||||||
|
&& !isPalmEraserActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool ShouldUseRealtimeVelocityBrushTipForMouse()
|
||||||
|
{
|
||||||
|
return ShouldUseRealtimeVelocityBrushTip()
|
||||||
|
&& drawingShapeMode == 0
|
||||||
|
&& !isPalmEraserActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsTouchStylusDevice(StylusDevice stylusDevice)
|
||||||
|
{
|
||||||
|
return stylusDevice?.TabletDevice?.Type == TabletDeviceType.Touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EnsureRealtimeStylusPipelineBinding()
|
||||||
|
{
|
||||||
|
if (inkCanvas == null) return;
|
||||||
|
|
||||||
|
inkCanvas.StylusDown -= MainWindow_StylusDown;
|
||||||
|
inkCanvas.StylusMove -= MainWindow_StylusMove;
|
||||||
|
inkCanvas.StylusUp -= MainWindow_StylusUp;
|
||||||
|
|
||||||
|
inkCanvas.StylusDown += MainWindow_StylusDown;
|
||||||
|
inkCanvas.StylusMove += MainWindow_StylusMove;
|
||||||
|
inkCanvas.StylusUp += MainWindow_StylusUp;
|
||||||
|
|
||||||
|
if (ShouldUseRealtimeVelocityBrushTip()
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
}
|
||||||
|
else if (!ShouldUseRealtimeVelocityBrushTip()
|
||||||
|
&& inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRealtimeBrushTipState(int stylusId, StylusDownEventArgs e)
|
||||||
|
{
|
||||||
|
if (!ShouldUseRealtimeVelocityBrushTip())
|
||||||
|
{
|
||||||
|
_realtimeBrushTipStates.Remove(stylusId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startPoint = e.GetPosition(this);
|
||||||
|
_realtimeBrushTipStates[stylusId] = new RealtimeBrushTipState
|
||||||
|
{
|
||||||
|
LastRawX = (float)startPoint.X,
|
||||||
|
LastRawY = (float)startPoint.Y,
|
||||||
|
LastTimestampMs = RealtimeNowMs()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRealtimeBrushTipStateFromPoint(int strokeId, Point startPoint)
|
||||||
|
{
|
||||||
|
if (!ShouldUseRealtimeVelocityBrushTipForTouch() && strokeId != MouseRealtimeStrokeId)
|
||||||
|
{
|
||||||
|
_realtimeBrushTipStates.Remove(strokeId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ShouldUseRealtimeVelocityBrushTipForMouse() && strokeId == MouseRealtimeStrokeId)
|
||||||
|
{
|
||||||
|
_realtimeBrushTipStates.Remove(strokeId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_realtimeBrushTipStates[strokeId] = new RealtimeBrushTipState
|
||||||
|
{
|
||||||
|
LastRawX = (float)startPoint.X,
|
||||||
|
LastRawY = (float)startPoint.Y,
|
||||||
|
LastTimestampMs = RealtimeNowMs()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupRealtimeBrushTipState(int stylusId)
|
||||||
|
{
|
||||||
|
_realtimeBrushTipStates.Remove(stylusId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryAppendRealtimeVelocityBrushTipPoints(StrokeVisual strokeVisual, StylusEventArgs e)
|
||||||
|
{
|
||||||
|
if (!ShouldUseRealtimeVelocityBrushTip() || strokeVisual == null || e?.StylusDevice == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_realtimeBrushTipStates.TryGetValue(e.StylusDevice.Id, out var state))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var stylusPointCollection = e.GetStylusPoints(this);
|
||||||
|
if (stylusPointCollection == null || stylusPointCollection.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var mix = RealtimeClamp((float)Settings.Canvas.VelocityBrushTipMix, 0f, 1f);
|
||||||
|
var appended = false;
|
||||||
|
var baseWidth = (float)Math.Max(0.35,
|
||||||
|
strokeVisual.Stroke?.DrawingAttributes?.Width ?? inkCanvas.DefaultDrawingAttributes.Width);
|
||||||
|
|
||||||
|
foreach (StylusPoint rawPoint in stylusPointCollection)
|
||||||
|
{
|
||||||
|
var nowMs = RealtimeNowMs();
|
||||||
|
var dtMs = Math.Max(1L, nowMs - state.LastTimestampMs);
|
||||||
|
var dt = dtMs / 1000f;
|
||||||
|
var sampleRate = 1f / Math.Max(1e-4f, dt);
|
||||||
|
state.SmoothedSampleRateHz = state.SmoothedSampleRateHz * 0.85f + sampleRate * 0.15f;
|
||||||
|
|
||||||
|
var rawX = (float)rawPoint.X;
|
||||||
|
var rawY = (float)rawPoint.Y;
|
||||||
|
var dx = rawX - state.LastRawX;
|
||||||
|
var dy = rawY - state.LastRawY;
|
||||||
|
var dist = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
var speed = dist / dt;
|
||||||
|
|
||||||
|
var filteredX = state.FilterX.Filter(rawX, dt, speed);
|
||||||
|
var filteredY = state.FilterY.Filter(rawY, dt, speed);
|
||||||
|
|
||||||
|
var hwPressure = RealtimeClamp((float)rawPoint.PressureFactor, 0f, 1f);
|
||||||
|
if (Math.Abs(hwPressure - 0.5f) > 0.02f)
|
||||||
|
state.SawPressureVariation = true;
|
||||||
|
var usePressure = state.SawPressureVariation && hwPressure > 0f;
|
||||||
|
|
||||||
|
var width = baseWidth;
|
||||||
|
if (usePressure)
|
||||||
|
width *= 0.25f + 0.75f * hwPressure;
|
||||||
|
var speedNormalization = 1800f + state.SmoothedSampleRateHz * 3.5f;
|
||||||
|
width *= RealtimeClamp(1.15f - (speed / speedNormalization), 0.45f, 1.25f);
|
||||||
|
var speedPressure = WidthToPressure(width, baseWidth);
|
||||||
|
|
||||||
|
var pressure = usePressure
|
||||||
|
? ((1f - mix) * hwPressure + mix * speedPressure)
|
||||||
|
: speedPressure;
|
||||||
|
pressure = RealtimeClamp(pressure, 0.08f, 1f);
|
||||||
|
pressure = state.FilterPressure.Filter(pressure, dt, speed);
|
||||||
|
|
||||||
|
// 高频采样时做最小距离门限,避免点爆炸导致实时重绘卡顿
|
||||||
|
var minDist = state.SmoothedSampleRateHz > 160f ? 0.55f
|
||||||
|
: state.SmoothedSampleRateHz > 90f ? 0.4f
|
||||||
|
: 0.25f;
|
||||||
|
if (dist < minDist && state.HasSeed)
|
||||||
|
{
|
||||||
|
state.LastRawX = rawX;
|
||||||
|
state.LastRawY = rawY;
|
||||||
|
state.LastTimestampMs = nowMs;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.HasSeed)
|
||||||
|
{
|
||||||
|
state.HasSeed = true;
|
||||||
|
state.LastSmoothX = filteredX;
|
||||||
|
state.LastSmoothY = filteredY;
|
||||||
|
state.LastSmoothPressure = pressure;
|
||||||
|
strokeVisual.Add(new StylusPoint(filteredX, filteredY, pressure));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 采用中点链减抖:保持实时笔锋同时降低折线锯齿
|
||||||
|
var midX = (state.LastSmoothX + filteredX) * 0.5f;
|
||||||
|
var midY = (state.LastSmoothY + filteredY) * 0.5f;
|
||||||
|
var midPressure = (state.LastSmoothPressure + pressure) * 0.5f;
|
||||||
|
strokeVisual.Add(new StylusPoint(midX, midY, midPressure));
|
||||||
|
state.LastSmoothX = filteredX;
|
||||||
|
state.LastSmoothY = filteredY;
|
||||||
|
state.LastSmoothPressure = pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.LastRawX = rawX;
|
||||||
|
state.LastRawY = rawY;
|
||||||
|
state.LastTimestampMs = nowMs;
|
||||||
|
appended = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var committedStroke = strokeVisual.Stroke;
|
||||||
|
if (appended && committedStroke != null)
|
||||||
|
{
|
||||||
|
if (committedStroke.DrawingAttributes != null)
|
||||||
|
committedStroke.DrawingAttributes.IgnorePressure = false;
|
||||||
|
if (!committedStroke.ContainsPropertyData(RealtimeVelocityBrushTipAppliedGuid))
|
||||||
|
committedStroke.AddPropertyData(RealtimeVelocityBrushTipAppliedGuid, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryAppendRealtimeVelocityBrushTipPoint(StrokeVisual strokeVisual, int strokeId, Point point, float rawPressure = 0.5f)
|
||||||
|
{
|
||||||
|
var allow = strokeId == MouseRealtimeStrokeId
|
||||||
|
? ShouldUseRealtimeVelocityBrushTipForMouse()
|
||||||
|
: ShouldUseRealtimeVelocityBrushTipForTouch();
|
||||||
|
if (!allow || strokeVisual == null)
|
||||||
|
return false;
|
||||||
|
if (!_realtimeBrushTipStates.TryGetValue(strokeId, out var state))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var mix = RealtimeClamp((float)Settings.Canvas.VelocityBrushTipMix, 0f, 1f);
|
||||||
|
var nowMs = RealtimeNowMs();
|
||||||
|
var dtMs = Math.Max(1L, nowMs - state.LastTimestampMs);
|
||||||
|
var dt = dtMs / 1000f;
|
||||||
|
var sampleRate = 1f / Math.Max(1e-4f, dt);
|
||||||
|
state.SmoothedSampleRateHz = state.SmoothedSampleRateHz * 0.85f + sampleRate * 0.15f;
|
||||||
|
var baseWidth = (float)Math.Max(0.35,
|
||||||
|
strokeVisual.Stroke?.DrawingAttributes?.Width ?? inkCanvas.DefaultDrawingAttributes.Width);
|
||||||
|
|
||||||
|
var rawX = (float)point.X;
|
||||||
|
var rawY = (float)point.Y;
|
||||||
|
var dx = rawX - state.LastRawX;
|
||||||
|
var dy = rawY - state.LastRawY;
|
||||||
|
var dist = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
var speed = dist / dt;
|
||||||
|
|
||||||
|
var filteredX = state.FilterX.Filter(rawX, dt, speed);
|
||||||
|
var filteredY = state.FilterY.Filter(rawY, dt, speed);
|
||||||
|
|
||||||
|
rawPressure = RealtimeClamp(rawPressure, 0f, 1f);
|
||||||
|
if (Math.Abs(rawPressure - 0.5f) > 0.02f)
|
||||||
|
state.SawPressureVariation = true;
|
||||||
|
var usePressure = state.SawPressureVariation && rawPressure > 0f;
|
||||||
|
|
||||||
|
var width = baseWidth;
|
||||||
|
if (usePressure)
|
||||||
|
width *= 0.25f + 0.75f * rawPressure;
|
||||||
|
var speedNormalization = 1800f + state.SmoothedSampleRateHz * 3.5f;
|
||||||
|
width *= RealtimeClamp(1.15f - (speed / speedNormalization), 0.45f, 1.25f);
|
||||||
|
var speedPressure = WidthToPressure(width, baseWidth);
|
||||||
|
|
||||||
|
var pressure = usePressure
|
||||||
|
? ((1f - mix) * rawPressure + mix * speedPressure)
|
||||||
|
: speedPressure;
|
||||||
|
pressure = RealtimeClamp(pressure, 0.08f, 1f);
|
||||||
|
pressure = state.FilterPressure.Filter(pressure, dt, speed);
|
||||||
|
|
||||||
|
var minDist = state.SmoothedSampleRateHz > 160f ? 0.55f
|
||||||
|
: state.SmoothedSampleRateHz > 90f ? 0.4f
|
||||||
|
: 0.25f;
|
||||||
|
if (dist < minDist && state.HasSeed)
|
||||||
|
{
|
||||||
|
state.LastRawX = rawX;
|
||||||
|
state.LastRawY = rawY;
|
||||||
|
state.LastTimestampMs = nowMs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.HasSeed)
|
||||||
|
{
|
||||||
|
state.HasSeed = true;
|
||||||
|
state.LastSmoothX = filteredX;
|
||||||
|
state.LastSmoothY = filteredY;
|
||||||
|
state.LastSmoothPressure = pressure;
|
||||||
|
strokeVisual.Add(new StylusPoint(filteredX, filteredY, pressure));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var midX = (state.LastSmoothX + filteredX) * 0.5f;
|
||||||
|
var midY = (state.LastSmoothY + filteredY) * 0.5f;
|
||||||
|
var midPressure = (state.LastSmoothPressure + pressure) * 0.5f;
|
||||||
|
strokeVisual.Add(new StylusPoint(midX, midY, midPressure));
|
||||||
|
state.LastSmoothX = filteredX;
|
||||||
|
state.LastSmoothY = filteredY;
|
||||||
|
state.LastSmoothPressure = pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.LastRawX = rawX;
|
||||||
|
state.LastRawY = rawY;
|
||||||
|
state.LastTimestampMs = nowMs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||||
@@ -233,8 +604,7 @@ namespace Ink_Canvas
|
|||||||
if (palmEraserWasEnabledBeforeMultiTouch)
|
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||||
{
|
{
|
||||||
Settings.Canvas.EnablePalmEraser = true;
|
Settings.Canvas.EnablePalmEraser = true;
|
||||||
if (ToggleSwitchEnablePalmEraser != null)
|
SaveSettingsToFile();
|
||||||
ToggleSwitchEnablePalmEraser.IsOn = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -259,8 +629,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||||
Settings.Canvas.EnablePalmEraser = false;
|
Settings.Canvas.EnablePalmEraser = false;
|
||||||
if (ToggleSwitchEnablePalmEraser != null)
|
SaveSettingsToFile();
|
||||||
ToggleSwitchEnablePalmEraser.IsOn = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +709,9 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void MainWindow_StylusDown(object sender, StylusDownEventArgs e)
|
private void MainWindow_StylusDown(object sender, StylusDownEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (IsTouchStylusDevice(e.StylusDevice))
|
||||||
|
return;
|
||||||
|
|
||||||
// 检查手写笔点击是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
// 检查手写笔点击是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
||||||
var stylusPoint = e.GetPosition(this);
|
var stylusPoint = e.GetPosition(this);
|
||||||
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
||||||
@@ -375,7 +747,9 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
||||||
{
|
{
|
||||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
inkCanvas.EditingMode = ShouldUseRealtimeVelocityBrushTip()
|
||||||
|
? InkCanvasEditingMode.None
|
||||||
|
: InkCanvasEditingMode.Ink;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -392,6 +766,10 @@ namespace Ink_Canvas
|
|||||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke
|
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke
|
||||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
|| inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
||||||
|
|
||||||
|
InitializeRealtimeBrushTipState(e.StylusDevice.Id, e);
|
||||||
|
CancelPauseStraightenTimer(e.StylusDevice.Id);
|
||||||
|
_pauseStraightenInkModeStartPos = e.GetPosition(inkCanvas);
|
||||||
|
_pauseStraightenInkModeTracking = true;
|
||||||
TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None;
|
TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +799,9 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async void MainWindow_StylusUp(object sender, StylusEventArgs e)
|
private async void MainWindow_StylusUp(object sender, StylusEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (IsTouchStylusDevice(e.StylusDevice))
|
||||||
|
return;
|
||||||
|
|
||||||
if (drawingShapeMode != 0)
|
if (drawingShapeMode != 0)
|
||||||
{
|
{
|
||||||
// 重置触摸状态
|
// 重置触摸状态
|
||||||
@@ -491,6 +872,10 @@ namespace Ink_Canvas
|
|||||||
StrokeVisualList.Remove(e.StylusDevice.Id);
|
StrokeVisualList.Remove(e.StylusDevice.Id);
|
||||||
VisualCanvasList.Remove(e.StylusDevice.Id);
|
VisualCanvasList.Remove(e.StylusDevice.Id);
|
||||||
TouchDownPointsList.Remove(e.StylusDevice.Id);
|
TouchDownPointsList.Remove(e.StylusDevice.Id);
|
||||||
|
CleanupRealtimeBrushTipState(e.StylusDevice.Id);
|
||||||
|
CancelPauseStraightenTimer(e.StylusDevice.Id);
|
||||||
|
CancelPauseStraightenTimer(-200001);
|
||||||
|
_pauseStraightenInkModeTracking = false;
|
||||||
if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0)
|
if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0)
|
||||||
{
|
{
|
||||||
// 只清除手写笔预览相关的Canvas,不清除所有子元素
|
// 只清除手写笔预览相关的Canvas,不清除所有子元素
|
||||||
@@ -534,6 +919,9 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (IsTouchStylusDevice(e.StylusDevice))
|
||||||
|
return;
|
||||||
|
|
||||||
if (drawingShapeMode != 0)
|
if (drawingShapeMode != 0)
|
||||||
{
|
{
|
||||||
if (isTouchDown)
|
if (isTouchDown)
|
||||||
@@ -544,7 +932,14 @@ namespace Ink_Canvas
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) return;
|
if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None)
|
||||||
|
{
|
||||||
|
// Regular Ink mode — InkCanvas builds the stroke internally.
|
||||||
|
// Track position for pause-straighten.
|
||||||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink && drawingShapeMode == 0)
|
||||||
|
ResetPauseStraightenTimerInkMode(e.GetPosition(inkCanvas));
|
||||||
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (e.StylusDevice.StylusButtons[1].StylusButtonState == StylusButtonState.Down) return;
|
if (e.StylusDevice.StylusButtons[1].StylusButtonState == StylusButtonState.Down) return;
|
||||||
@@ -553,28 +948,20 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
|
|
||||||
var strokeVisual = GetStrokeVisual(e.StylusDevice.Id);
|
var strokeVisual = GetStrokeVisual(e.StylusDevice.Id);
|
||||||
var stylusPointCollection = e.GetStylusPoints(this);
|
var isHandledByRealtime = TryAppendRealtimeVelocityBrushTipPoints(strokeVisual, e);
|
||||||
foreach (var stylusPoint in stylusPointCollection)
|
if (!isHandledByRealtime)
|
||||||
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
{
|
||||||
|
var stylusPointCollection = e.GetStylusPoints(this);
|
||||||
|
foreach (var stylusPoint in stylusPointCollection)
|
||||||
|
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
||||||
|
}
|
||||||
|
|
||||||
// 实时笔锋:混合度 > 0 时在绘制过程中更新压感并整笔重绘预览;混合为 0 时与普通过程一致用增量 Redraw,避免每点 ForceRedraw 整笔清空(长笔画卡顿)。
|
ResetPauseStraightenTimer(e.StylusDevice.Id);
|
||||||
var committedStroke = strokeVisual.Stroke;
|
|
||||||
if (committedStroke != null
|
if (isHandledByRealtime)
|
||||||
&& Settings.Canvas.InkStyle == 3
|
|
||||||
&& Settings.Canvas.VelocityBrushTipMix > 0
|
|
||||||
&& !Settings.Canvas.DisablePressure
|
|
||||||
&& penType == 0
|
|
||||||
&& committedStroke.DrawingAttributes != null
|
|
||||||
&& !committedStroke.DrawingAttributes.IsHighlighter
|
|
||||||
&& committedStroke.StylusPoints.Count >= 3)
|
|
||||||
{
|
|
||||||
ApplyVelocityBrushTipFromSpeed(committedStroke);
|
|
||||||
strokeVisual.ForceRedraw();
|
strokeVisual.ForceRedraw();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
strokeVisual.Redraw();
|
strokeVisual.Redraw();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
}
|
}
|
||||||
@@ -620,6 +1007,126 @@ namespace Ink_Canvas
|
|||||||
return VisualCanvasList.TryGetValue(id, out var visualCanvas) ? visualCanvas : null;
|
return VisualCanvasList.TryGetValue(id, out var visualCanvas) ? visualCanvas : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResetPauseStraightenTimer(int stylusId)
|
||||||
|
{
|
||||||
|
if (!Settings.Canvas.PauseStraightenLine) return;
|
||||||
|
Debug.WriteLine($"ResetPauseStraightenTimer: id={stylusId}");
|
||||||
|
if (_pauseStraightenTimers.TryGetValue(stylusId, out var existing))
|
||||||
|
{
|
||||||
|
existing.Stop();
|
||||||
|
existing.Interval = TimeSpan.FromMilliseconds(Settings.Canvas.PauseStraightenDelay);
|
||||||
|
existing.Start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Settings.Canvas.PauseStraightenDelay) };
|
||||||
|
var capturedId = stylusId;
|
||||||
|
timer.Tick += (s, e) =>
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
_pauseStraightenTimers.Remove(capturedId);
|
||||||
|
Debug.WriteLine($"PauseStraightenTimer fired: id={capturedId}");
|
||||||
|
TryPauseStraighten(capturedId);
|
||||||
|
};
|
||||||
|
_pauseStraightenTimers[stylusId] = timer;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetPauseStraightenTimerInkMode(Point currentPos)
|
||||||
|
{
|
||||||
|
if (!Settings.Canvas.PauseStraightenLine) return;
|
||||||
|
const int inkModeId = -200001;
|
||||||
|
_pauseStraightenInkModeLastPos = currentPos;
|
||||||
|
if (_pauseStraightenTimers.TryGetValue(inkModeId, out var existing))
|
||||||
|
{
|
||||||
|
existing.Stop();
|
||||||
|
existing.Interval = TimeSpan.FromMilliseconds(Settings.Canvas.PauseStraightenDelay);
|
||||||
|
existing.Start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(Settings.Canvas.PauseStraightenDelay) };
|
||||||
|
timer.Tick += (s, e) =>
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
_pauseStraightenTimers.Remove(inkModeId);
|
||||||
|
TryPauseStraightenInkMode();
|
||||||
|
};
|
||||||
|
_pauseStraightenTimers[inkModeId] = timer;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point _pauseStraightenInkModeLastPos;
|
||||||
|
private Point _pauseStraightenInkModeStartPos;
|
||||||
|
private bool _pauseStraightenInkModeTracking;
|
||||||
|
|
||||||
|
private void TryPauseStraightenInkMode()
|
||||||
|
{
|
||||||
|
if (!Settings.Canvas.PauseStraightenLine) return;
|
||||||
|
if (!_pauseStraightenInkModeTracking) return;
|
||||||
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink) return;
|
||||||
|
if (drawingShapeMode != 0) return;
|
||||||
|
|
||||||
|
var start = _pauseStraightenInkModeStartPos;
|
||||||
|
var end = _pauseStraightenInkModeLastPos;
|
||||||
|
double lineLength = GetDistance(start, end);
|
||||||
|
if (lineLength < 2) return;
|
||||||
|
|
||||||
|
// Commit current stroke by briefly switching mode
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||||
|
|
||||||
|
// The just-committed stroke should now be last in inkCanvas.Strokes
|
||||||
|
if (inkCanvas.Strokes.Count == 0) return;
|
||||||
|
var stroke = inkCanvas.Strokes[inkCanvas.Strokes.Count - 1];
|
||||||
|
if (stroke.StylusPoints.Count < 2) return;
|
||||||
|
|
||||||
|
var newPoints = new StylusPointCollection();
|
||||||
|
newPoints.Add(new StylusPoint(start.X, start.Y, 0.5f));
|
||||||
|
if (lineLength > 100)
|
||||||
|
{
|
||||||
|
newPoints.Add(new StylusPoint(start.X + (end.X - start.X) / 3.0, start.Y + (end.Y - start.Y) / 3.0, 0.5f));
|
||||||
|
newPoints.Add(new StylusPoint(start.X + (end.X - start.X) * 2.0 / 3.0, start.Y + (end.Y - start.Y) * 2.0 / 3.0, 0.5f));
|
||||||
|
}
|
||||||
|
newPoints.Add(new StylusPoint(end.X, end.Y, 0.5f));
|
||||||
|
stroke.StylusPoints = newPoints;
|
||||||
|
|
||||||
|
_pauseStraightenInkModeTracking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelPauseStraightenTimer(int stylusId)
|
||||||
|
{
|
||||||
|
if (_pauseStraightenTimers.TryGetValue(stylusId, out var timer))
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
_pauseStraightenTimers.Remove(stylusId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryPauseStraighten(int stylusId)
|
||||||
|
{
|
||||||
|
if (!Settings.Canvas.PauseStraightenLine) { Debug.WriteLine("PauseStraighten: disabled"); return; }
|
||||||
|
var strokeVisual = StrokeVisualList.TryGetValue(stylusId, out var sv) ? sv : null;
|
||||||
|
if (strokeVisual?.Stroke == null) { Debug.WriteLine($"PauseStraighten: no stroke for id={stylusId}"); return; }
|
||||||
|
var stroke = strokeVisual.Stroke;
|
||||||
|
Debug.WriteLine($"PauseStraighten: points={stroke.StylusPoints.Count}");
|
||||||
|
if (stroke.StylusPoints.Count < 2) return;
|
||||||
|
|
||||||
|
var start = stroke.StylusPoints[0].ToPoint();
|
||||||
|
var end = stroke.StylusPoints[stroke.StylusPoints.Count - 1].ToPoint();
|
||||||
|
double lineLength = GetDistance(start, end);
|
||||||
|
Debug.WriteLine($"PauseStraighten: length={lineLength:F1}, STRAIGHTENING!");
|
||||||
|
|
||||||
|
var newPoints = new StylusPointCollection();
|
||||||
|
newPoints.Add(new StylusPoint(start.X, start.Y, 0.5f));
|
||||||
|
if (lineLength > 100)
|
||||||
|
{
|
||||||
|
newPoints.Add(new StylusPoint(start.X + (end.X - start.X) / 3.0, start.Y + (end.Y - start.Y) / 3.0, 0.5f));
|
||||||
|
newPoints.Add(new StylusPoint(start.X + (end.X - start.X) * 2.0 / 3.0, start.Y + (end.Y - start.Y) * 2.0 / 3.0, 0.5f));
|
||||||
|
}
|
||||||
|
newPoints.Add(new StylusPoint(end.X, end.Y, 0.5f));
|
||||||
|
stroke.StylusPoints = newPoints;
|
||||||
|
strokeVisual.ForceRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取触摸按下点的编辑模式方法
|
/// 获取触摸按下点的编辑模式方法
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -813,6 +1320,53 @@ namespace Ink_Canvas
|
|||||||
lastTouchDownTime = DateTime.Now;
|
lastTouchDownTime = DateTime.Now;
|
||||||
dec.Add(e.TouchDevice.Id);
|
dec.Add(e.TouchDevice.Id);
|
||||||
|
|
||||||
|
if (ShouldUseRealtimeVelocityBrushTipForTouch()
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
var touchId = e.TouchDevice.Id;
|
||||||
|
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||||
|
_activeRealtimeTouchStrokeIds.Add(touchId);
|
||||||
|
CancelPauseStraightenTimer(touchId);
|
||||||
|
InitializeRealtimeBrushTipStateFromPoint(touchId, p);
|
||||||
|
var sv = GetStrokeVisual(touchId);
|
||||||
|
TryAppendRealtimeVelocityBrushTipPoint(sv, touchId, p);
|
||||||
|
sv.ForceRedraw();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((isInMultiTouchMode || Settings.Gesture.IsEnableMultiTouchMode)
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
var touchId = e.TouchDevice.Id;
|
||||||
|
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||||
|
_activeTouchStrokeIds.Add(touchId);
|
||||||
|
CancelPauseStraightenTimer(touchId);
|
||||||
|
var sv = GetStrokeVisual(touchId);
|
||||||
|
sv.Add(new StylusPoint(p.X, p.Y, 0.5f));
|
||||||
|
sv.Redraw();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive && drawingShapeMode == 0)
|
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive && drawingShapeMode == 0)
|
||||||
{
|
{
|
||||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
@@ -848,6 +1402,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
if (Settings.Advanced.IsSpecialScreen)
|
if (Settings.Advanced.IsSpecialScreen)
|
||||||
boundWidth *= Settings.Advanced.TouchMultiplier;
|
boundWidth *= Settings.Advanced.TouchMultiplier;
|
||||||
|
palmEraserPreviousEditingMode = inkCanvas.EditingMode;
|
||||||
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
||||||
isPalmEraserActive = true;
|
isPalmEraserActive = true;
|
||||||
|
|
||||||
@@ -923,6 +1478,42 @@ namespace Ink_Canvas
|
|||||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var touchId = e.TouchDevice.Id;
|
||||||
|
if (ShouldUseRealtimeVelocityBrushTipForTouch())
|
||||||
|
{
|
||||||
|
if (!_activeRealtimeTouchStrokeIds.Contains(touchId))
|
||||||
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||||
|
var sv = GetStrokeVisual(touchId);
|
||||||
|
if (TryAppendRealtimeVelocityBrushTipPoint(sv, touchId, p))
|
||||||
|
sv.ForceRedraw();
|
||||||
|
ResetPauseStraightenTimer(touchId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeTouchStrokeIds.Contains(touchId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||||
|
var sv = GetStrokeVisual(touchId);
|
||||||
|
sv.Add(new StylusPoint(p.X, p.Y, 0.5f));
|
||||||
|
sv.Redraw();
|
||||||
|
ResetPauseStraightenTimer(touchId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -949,6 +1540,68 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||||
{
|
{
|
||||||
|
var touchId = e.TouchDevice.Id;
|
||||||
|
if (_activeRealtimeTouchStrokeIds.Contains(touchId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sv = GetStrokeVisual(touchId);
|
||||||
|
sv?.ForceRedraw();
|
||||||
|
var stroke = sv?.Stroke;
|
||||||
|
if (stroke != null)
|
||||||
|
{
|
||||||
|
if (!stroke.ContainsPropertyData(RealtimeVelocityBrushTipAppliedGuid))
|
||||||
|
stroke.AddPropertyData(RealtimeVelocityBrushTipAppliedGuid, true);
|
||||||
|
inkCanvas.Strokes.Add(stroke);
|
||||||
|
inkCanvas_StrokeCollected(inkCanvas, new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (VisualCanvasList.TryGetValue(touchId, out var visualCanvas) && inkCanvas.Children.Contains(visualCanvas))
|
||||||
|
inkCanvas.Children.Remove(visualCanvas);
|
||||||
|
StrokeVisualList.Remove(touchId);
|
||||||
|
VisualCanvasList.Remove(touchId);
|
||||||
|
TouchDownPointsList.Remove(touchId);
|
||||||
|
CleanupRealtimeBrushTipState(touchId);
|
||||||
|
CancelPauseStraightenTimer(touchId);
|
||||||
|
_activeRealtimeTouchStrokeIds.Remove(touchId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_activeTouchStrokeIds.Contains(touchId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var sv = GetStrokeVisual(touchId);
|
||||||
|
sv?.Redraw();
|
||||||
|
var stroke = sv?.Stroke;
|
||||||
|
if (stroke != null)
|
||||||
|
{
|
||||||
|
inkCanvas.Strokes.Add(stroke);
|
||||||
|
inkCanvas_StrokeCollected(inkCanvas, new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (VisualCanvasList.TryGetValue(touchId, out var visualCanvas) && inkCanvas.Children.Contains(visualCanvas))
|
||||||
|
inkCanvas.Children.Remove(visualCanvas);
|
||||||
|
StrokeVisualList.Remove(touchId);
|
||||||
|
VisualCanvasList.Remove(touchId);
|
||||||
|
TouchDownPointsList.Remove(touchId);
|
||||||
|
CleanupRealtimeBrushTipState(touchId);
|
||||||
|
CancelPauseStraightenTimer(touchId);
|
||||||
|
_activeTouchStrokeIds.Remove(touchId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive)
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -1021,6 +1674,11 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
isPalmEraserActive = false;
|
isPalmEraserActive = false;
|
||||||
DisableEraserOverlay();
|
DisableEraserOverlay();
|
||||||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = palmEraserPreviousEditingMode;
|
||||||
|
SetCursorBasedOnEditingMode(inkCanvas);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,20 +123,6 @@ namespace Ink_Canvas
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsLegacySettingsVisible(MainWindow mainWin)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var borderSettingsField = typeof(MainWindow).GetField("BorderSettings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
||||||
var borderSettings = borderSettingsField?.GetValue(mainWin) as FrameworkElement;
|
|
||||||
return borderSettings?.Visibility == Visibility.Visible;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TempShowMainWindowTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
private void TempShowMainWindowTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var mainWin = Current.MainWindow as MainWindow;
|
var mainWin = Current.MainWindow as MainWindow;
|
||||||
@@ -224,11 +210,6 @@ namespace Ink_Canvas
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsLegacySettingsVisible(mainWin))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var method = typeof(MainWindow).GetMethod("BtnSettings_Click", BindingFlags.NonPublic | BindingFlags.Instance);
|
var method = typeof(MainWindow).GetMethod("BtnSettings_Click", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|||||||
@@ -232,7 +232,6 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
_lastAppliedProfileName = profileName.Trim();
|
_lastAppliedProfileName = profileName.Trim();
|
||||||
ReloadSettingsFromFile();
|
ReloadSettingsFromFile();
|
||||||
RefreshConfigProfileList();
|
|
||||||
File.WriteAllText(resultPath, "ok", System.Text.Encoding.UTF8);
|
File.WriteAllText(resultPath, "ok", System.Text.Encoding.UTF8);
|
||||||
ShowNotification($"已通过 URI 切换至方案「{profileName}」");
|
ShowNotification($"已通过 URI 切换至方案「{profileName}」");
|
||||||
LogHelper.WriteLogToFile($"URI 已切换配置方案: {profileName}", LogHelper.LogType.Event);
|
LogHelper.WriteLogToFile($"URI 已切换配置方案: {profileName}", LogHelper.LogType.Event);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Ink_Canvas.Helpers;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Plugins
|
||||||
|
{
|
||||||
|
public class AppRestartService : IAppRestartService
|
||||||
|
{
|
||||||
|
public bool IsRunningAsAdmin => AppRestartHelper.IsRunningAsAdmin();
|
||||||
|
|
||||||
|
public void RestartApp(bool asAdmin)
|
||||||
|
{
|
||||||
|
AppRestartHelper.RestartApp(asAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestartWithCurrentPrivileges()
|
||||||
|
{
|
||||||
|
AppRestartHelper.RestartWithCurrentPrivileges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestartAsAdmin()
|
||||||
|
{
|
||||||
|
AppRestartHelper.RestartAsAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RestartAsNormal()
|
||||||
|
{
|
||||||
|
AppRestartHelper.RestartAsNormal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwitchToUIATopMostAndRestart()
|
||||||
|
{
|
||||||
|
AppRestartHelper.SwitchToUIATopMostAndRestart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwitchToNormalTopMostAndRestart()
|
||||||
|
{
|
||||||
|
AppRestartHelper.SwitchToNormalTopMostAndRestart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Plugins
|
||||||
|
{
|
||||||
|
public class InkCanvasService : IInkCanvasService
|
||||||
|
{
|
||||||
|
private readonly MainWindow _mainWindow;
|
||||||
|
|
||||||
|
public InkCanvasService(MainWindow mainWindow)
|
||||||
|
{
|
||||||
|
_mainWindow = mainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenWhiteboard()
|
||||||
|
{
|
||||||
|
if (_mainWindow != null)
|
||||||
|
{
|
||||||
|
_mainWindow.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mainWindow.SwitchToBoardMode();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(string.Format("Error opening whiteboard: {0}", ex.Message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseWhiteboard()
|
||||||
|
{
|
||||||
|
if (_mainWindow != null)
|
||||||
|
{
|
||||||
|
_mainWindow.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mainWindow.FoldFloatingBar_MouseUp(null, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(string.Format("Error closing whiteboard: {0}", ex.Message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OpenWhiteboardAsync(int delayMilliseconds = 0)
|
||||||
|
{
|
||||||
|
if (delayMilliseconds > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delayMilliseconds);
|
||||||
|
}
|
||||||
|
OpenWhiteboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Plugins
|
||||||
|
{
|
||||||
|
public class PluginManager : IPluginHost
|
||||||
|
{
|
||||||
|
private static PluginManager _instance;
|
||||||
|
public static PluginManager Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = new PluginManager();
|
||||||
|
}
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();
|
||||||
|
private readonly string _pluginsDirectory;
|
||||||
|
private readonly List<PluginInfo> _plugins = new List<PluginInfo>();
|
||||||
|
private readonly Dictionary<string, AssemblyLoadContext> _assemblyContexts = new Dictionary<string, AssemblyLoadContext>();
|
||||||
|
|
||||||
|
public IReadOnlyList<PluginInfo> Plugins
|
||||||
|
{
|
||||||
|
get { return _plugins.AsReadOnly(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<PluginInfo> PluginLoaded;
|
||||||
|
public event EventHandler<PluginInfo> PluginUnloaded;
|
||||||
|
public event EventHandler<string> LogMessage;
|
||||||
|
|
||||||
|
private PluginManager()
|
||||||
|
{
|
||||||
|
_pluginsDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||||
|
|
||||||
|
if (!Directory.Exists(_pluginsDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_pluginsDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadAllAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(_pluginsDirectory))
|
||||||
|
{
|
||||||
|
Log("Plugins directory does not exist, skipping plugin loading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(string.Format("Loading plugins from: {0}", _pluginsDirectory));
|
||||||
|
|
||||||
|
var pluginFiles = new List<string>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var topLevelFiles = Directory.GetFiles(_pluginsDirectory, "*.dll", SearchOption.TopDirectoryOnly);
|
||||||
|
pluginFiles.AddRange(topLevelFiles);
|
||||||
|
Log(string.Format("Found {0} top-level plugin files", topLevelFiles.Length));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(string.Format("Error getting top-level plugin files: {0}", ex.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var subDirectories = Directory.GetDirectories(_pluginsDirectory);
|
||||||
|
foreach (var subDir in subDirectories)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var subDirFiles = Directory.GetFiles(subDir, "*.dll", SearchOption.TopDirectoryOnly);
|
||||||
|
pluginFiles.AddRange(subDirFiles);
|
||||||
|
Log(string.Format("Found {0} plugin files in directory: {1}", subDirFiles.Length, Path.GetFileName(subDir)));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(string.Format("Error scanning subdirectory {0}: {1}", Path.GetFileName(subDir), ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(string.Format("Error getting subdirectories: {0}", ex.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(string.Format("Found total {0} potential plugin files", pluginFiles.Count));
|
||||||
|
|
||||||
|
foreach (var pluginFile in pluginFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LoadPluginAsync(pluginFile);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(string.Format("Failed to load plugin from {0}", Path.GetFileName(pluginFile)), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_plugins.Sort((a, b) => a.Order.CompareTo(b.Order));
|
||||||
|
Log(string.Format("Plugin loading complete. Loaded {0} plugins", _plugins.Count));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("Failed to load plugins", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPluginAsync(string pluginFile)
|
||||||
|
{
|
||||||
|
var fileName = Path.GetFileName(pluginFile);
|
||||||
|
Log(string.Format("Loading plugin: {0}", fileName));
|
||||||
|
|
||||||
|
var alc = new PluginAssemblyLoadContext(pluginFile, isCollectible: true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = alc.LoadFromAssemblyPath(pluginFile);
|
||||||
|
var pluginTypes = assembly.GetTypes()
|
||||||
|
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && t.IsClass);
|
||||||
|
|
||||||
|
foreach (var pluginType in pluginTypes)
|
||||||
|
{
|
||||||
|
var pluginInstance = Activator.CreateInstance(pluginType) as IPlugin;
|
||||||
|
if (pluginInstance == null) continue;
|
||||||
|
|
||||||
|
var pluginInfo = new PluginInfo
|
||||||
|
{
|
||||||
|
Id = pluginInstance.Id,
|
||||||
|
Name = pluginInstance.Name,
|
||||||
|
Version = pluginInstance.Version,
|
||||||
|
Description = pluginInstance.Description,
|
||||||
|
Author = pluginInstance.Author,
|
||||||
|
Order = pluginInstance.Order,
|
||||||
|
Instance = pluginInstance,
|
||||||
|
IsLoaded = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_plugins.Add(pluginInfo);
|
||||||
|
_assemblyContexts[pluginInfo.Id] = alc;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pluginInstance.Initialize(this);
|
||||||
|
Log(string.Format("Plugin loaded: {0} v{1} by {2}", pluginInfo.Name, pluginInfo.Version, pluginInfo.Author));
|
||||||
|
OnPluginLoaded(pluginInfo);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(string.Format("Failed to initialize plugin {0}", pluginInfo.Name), ex);
|
||||||
|
_plugins.Remove(pluginInfo);
|
||||||
|
_assemblyContexts.Remove(pluginInfo.Id);
|
||||||
|
alc.Unload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
alc.Unload();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnloadPlugin(PluginInfo plugin)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
plugin.Instance.Shutdown();
|
||||||
|
_plugins.Remove(plugin);
|
||||||
|
plugin.IsLoaded = false;
|
||||||
|
|
||||||
|
if (_assemblyContexts.TryGetValue(plugin.Id, out var alc))
|
||||||
|
{
|
||||||
|
_assemblyContexts.Remove(plugin.Id);
|
||||||
|
alc.Unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log(string.Format("Plugin unloaded: {0}", plugin.Name));
|
||||||
|
OnPluginUnloaded(plugin);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(string.Format("Failed to unload plugin {0}", plugin.Name), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnloadAll()
|
||||||
|
{
|
||||||
|
foreach (var plugin in _plugins.ToList())
|
||||||
|
{
|
||||||
|
UnloadPlugin(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string message)
|
||||||
|
{
|
||||||
|
OnLogMessage(message);
|
||||||
|
System.Diagnostics.Debug.WriteLine(string.Format("[Plugin] {0}", message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogError(string message, Exception ex = null)
|
||||||
|
{
|
||||||
|
var fullMessage = ex != null ? string.Format("{0}: {1}", message, ex.Message) : message;
|
||||||
|
OnLogMessage(string.Format("ERROR: {0}", fullMessage));
|
||||||
|
System.Diagnostics.Debug.WriteLine(string.Format("[Plugin ERROR] {0}", fullMessage));
|
||||||
|
if (ex != null)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(ex.StackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetService<T>() where T : class
|
||||||
|
{
|
||||||
|
if (_services.TryGetValue(typeof(T), out var service))
|
||||||
|
{
|
||||||
|
return service as T;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterService<T>(T service) where T : class
|
||||||
|
{
|
||||||
|
_services[typeof(T)] = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPluginLoaded(PluginInfo pluginInfo)
|
||||||
|
{
|
||||||
|
var handler = PluginLoaded;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, pluginInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPluginUnloaded(PluginInfo pluginInfo)
|
||||||
|
{
|
||||||
|
var handler = PluginUnloaded;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, pluginInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnLogMessage(string message)
|
||||||
|
{
|
||||||
|
var handler = LogMessage;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PluginAssemblyLoadContext : AssemblyLoadContext
|
||||||
|
{
|
||||||
|
private readonly AssemblyDependencyResolver _resolver;
|
||||||
|
|
||||||
|
public PluginAssemblyLoadContext(string pluginPath, bool isCollectible)
|
||||||
|
: base(string.Format("PluginContext_{0}", Path.GetFileNameWithoutExtension(pluginPath)), isCollectible)
|
||||||
|
{
|
||||||
|
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Assembly Load(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||||
|
if (assemblyPath != null)
|
||||||
|
{
|
||||||
|
return LoadFromAssemblyPath(assemblyPath);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,5 +43,5 @@ using System.Windows;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.7.18.9")]
|
[assembly: AssemblyVersion("1.7.18.10")]
|
||||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
[assembly: AssemblyFileVersion("1.7.18.10")]
|
||||||
|
|||||||
@@ -207,6 +207,15 @@
|
|||||||
<data name="Btn_Exit" xml:space="preserve">
|
<data name="Btn_Exit" xml:space="preserve">
|
||||||
<value>Exit</value>
|
<value>Exit</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Home_RestartApp" xml:space="preserve">
|
||||||
|
<value>Restart application</value>
|
||||||
|
</data>
|
||||||
|
<data name="Home_ResetSettings" xml:space="preserve">
|
||||||
|
<value>Reset to recommended settings</value>
|
||||||
|
</data>
|
||||||
|
<data name="Home_ExitApp" xml:space="preserve">
|
||||||
|
<value>Exit application</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_Mode" xml:space="preserve">
|
<data name="Settings_Mode" xml:space="preserve">
|
||||||
<value>Mode</value>
|
<value>Mode</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -256,7 +265,28 @@
|
|||||||
<value>UIA topmost</value>
|
<value>UIA topmost</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UIATopMostHint" xml:space="preserve">
|
<data name="Startup_UIATopMostHint" xml:space="preserve">
|
||||||
<value># UIA topmost requires admin to take effect.</value>
|
<value>UIA topmost requires admin to take effect.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode" xml:space="preserve">
|
||||||
|
<value>Topmost Mode</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_Normal" xml:space="preserve">
|
||||||
|
<value>Normal Topmost</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_UIA" xml:space="preserve">
|
||||||
|
<value>UIA Topmost (Requires Admin)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_UIA_RestartRequired" xml:space="preserve">
|
||||||
|
<value>Switching to UIA topmost mode requires a restart to take effect. Restart now?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_Normal_RestartRequired" xml:space="preserve">
|
||||||
|
<value>Switching to normal topmost mode requires a restart to take effect. Restart now?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_RestartAsAdmin" xml:space="preserve">
|
||||||
|
<value>Restart as Administrator</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_RestartAsNormal" xml:space="preserve">
|
||||||
|
<value>Switch to Non-Admin Mode</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Header_AutoUpdate" xml:space="preserve">
|
<data name="Header_AutoUpdate" xml:space="preserve">
|
||||||
<value>Auto-update</value>
|
<value>Auto-update</value>
|
||||||
@@ -265,7 +295,7 @@
|
|||||||
<value>Silent update</value>
|
<value>Silent update</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SilentUpdate_Hint" xml:space="preserve">
|
<data name="SilentUpdate_Hint" xml:space="preserve">
|
||||||
<value># Silent update installs when app is idle.</value>
|
<value>Silent update installs when app is idle.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Update_Channel" xml:space="preserve">
|
<data name="Update_Channel" xml:space="preserve">
|
||||||
<value>Update channel</value>
|
<value>Update channel</value>
|
||||||
@@ -280,28 +310,28 @@
|
|||||||
<value>Beta</value>
|
<value>Beta</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Channel_Hint" xml:space="preserve">
|
<data name="Channel_Hint" xml:space="preserve">
|
||||||
<value># Stable for reliability; Preview for new features.</value>
|
<value>Stable for reliability; Preview for new features.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_ManualUpdate" xml:space="preserve">
|
<data name="Btn_ManualUpdate" xml:space="preserve">
|
||||||
<value>Check for updates</value>
|
<value>Check for updates</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ManualUpdate_Hint" xml:space="preserve">
|
<data name="ManualUpdate_Hint" xml:space="preserve">
|
||||||
<value># Check and download now.</value>
|
<value>Check and download now.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_VersionFix" xml:space="preserve">
|
<data name="Btn_VersionFix" xml:space="preserve">
|
||||||
<value>Version fix</value>
|
<value>Version fix</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VersionFix_Hint" xml:space="preserve">
|
<data name="VersionFix_Hint" xml:space="preserve">
|
||||||
<value># Download and install latest for current channel.</value>
|
<value>Download and install latest for current channel.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_Rollback" xml:space="preserve">
|
<data name="Btn_Rollback" xml:space="preserve">
|
||||||
<value>Rollback</value>
|
<value>Rollback</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Rollback_Hint" xml:space="preserve">
|
<data name="Rollback_Hint" xml:space="preserve">
|
||||||
<value># Open rollback page.</value>
|
<value>Open rollback page.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SilentUpdate_AfterDownloadHint" xml:space="preserve">
|
<data name="SilentUpdate_AfterDownloadHint" xml:space="preserve">
|
||||||
<value># When silent update is off, you will be prompted after download.</value>
|
<value>When silent update is off, you will be prompted after download.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SilentUpdate_TimeRange" xml:space="preserve">
|
<data name="SilentUpdate_TimeRange" xml:space="preserve">
|
||||||
<value>Silent update time range</value>
|
<value>Silent update time range</value>
|
||||||
@@ -313,7 +343,7 @@
|
|||||||
<value>End time</value>
|
<value>End time</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TimeRange_Hint" xml:space="preserve">
|
<data name="TimeRange_Hint" xml:space="preserve">
|
||||||
<value># If end < start…</value>
|
<value>If end < start…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_RunAtLogin" xml:space="preserve">
|
<data name="Startup_RunAtLogin" xml:space="preserve">
|
||||||
<value>Run at login</value>
|
<value>Run at login</value>
|
||||||
@@ -331,13 +361,13 @@
|
|||||||
<value>Pressure-sensitive touch</value>
|
<value>Pressure-sensitive touch</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_PressureTouchHint" xml:space="preserve">
|
<data name="Canvas_PressureTouchHint" xml:space="preserve">
|
||||||
<value># Touch devices will support pressure.</value>
|
<value>Touch devices will support pressure.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_IgnorePressure" xml:space="preserve">
|
<data name="Canvas_IgnorePressure" xml:space="preserve">
|
||||||
<value>Ignore pressure</value>
|
<value>Ignore pressure</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_IgnorePressureHint" xml:space="preserve">
|
<data name="Canvas_IgnorePressureHint" xml:space="preserve">
|
||||||
<value># Ignore all device pressure.</value>
|
<value>Ignore all device pressure.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EraserSize" xml:space="preserve">
|
<data name="Canvas_EraserSize" xml:space="preserve">
|
||||||
<value>Eraser size</value>
|
<value>Eraser size</value>
|
||||||
@@ -358,13 +388,13 @@
|
|||||||
<value>Very large</value>
|
<value>Very large</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EraserSize_SwitchHint" xml:space="preserve">
|
<data name="EraserSize_SwitchHint" xml:space="preserve">
|
||||||
<value># Takes effect on next area eraser use.</value>
|
<value>Takes effect on next area eraser use.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkOnExit" xml:space="preserve">
|
<data name="Canvas_HideInkOnExit" xml:space="preserve">
|
||||||
<value>Hide ink when leaving canvas</value>
|
<value>Hide ink when leaving canvas</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkOnExitHint" xml:space="preserve">
|
<data name="Canvas_HideInkOnExitHint" xml:space="preserve">
|
||||||
<value># When enabled…</value>
|
<value>When enabled…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ClearInkHistory" xml:space="preserve">
|
<data name="Canvas_ClearInkHistory" xml:space="preserve">
|
||||||
<value>Clear ink history when clearing</value>
|
<value>Clear ink history when clearing</value>
|
||||||
@@ -388,7 +418,7 @@
|
|||||||
<value>Ask each time</value>
|
<value>Ask each time</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_AsymptoteHint" xml:space="preserve">
|
<data name="Canvas_AsymptoteHint" xml:space="preserve">
|
||||||
<value># Disabling may cause undo bugs.</value>
|
<value>Disabling may cause undo bugs.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ShowCircleCenter" xml:space="preserve">
|
<data name="Canvas_ShowCircleCenter" xml:space="preserve">
|
||||||
<value>Show circle center when drawing</value>
|
<value>Show circle center when drawing</value>
|
||||||
@@ -403,7 +433,7 @@
|
|||||||
<value>Ink fade</value>
|
<value>Ink fade</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_InkFadeHint" xml:space="preserve">
|
<data name="Canvas_InkFadeHint" xml:space="preserve">
|
||||||
<value># Ink will not be drawn on canvas when enabled.</value>
|
<value>Ink will not be drawn on canvas when enabled.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_InkFadeTime" xml:space="preserve">
|
<data name="Canvas_InkFadeTime" xml:space="preserve">
|
||||||
<value>Ink fade time</value>
|
<value>Ink fade time</value>
|
||||||
@@ -412,7 +442,7 @@
|
|||||||
<value>Hide fade in pen menu</value>
|
<value>Hide fade in pen menu</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideFadeInPenMenuHint" xml:space="preserve">
|
<data name="Canvas_HideFadeInPenMenuHint" xml:space="preserve">
|
||||||
<value># Fade control will be hidden in pen context menu.</value>
|
<value>Fade control will be hidden in pen context menu.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Color" xml:space="preserve">
|
<data name="Color" xml:space="preserve">
|
||||||
<value>Color</value>
|
<value>Color</value>
|
||||||
@@ -487,7 +517,7 @@
|
|||||||
<value>No action</value>
|
<value>No action</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Crash_Hint" xml:space="preserve">
|
<data name="Crash_Hint" xml:space="preserve">
|
||||||
<value># Silent restart: automatically restart without prompt. No action: only log, do not restart.</value>
|
<value>Silent restart: automatically restart without prompt. No action: only log, do not restart.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_Title" xml:space="preserve">
|
<data name="Gesture_Title" xml:space="preserve">
|
||||||
<value>Gestures</value>
|
<value>Gestures</value>
|
||||||
@@ -496,13 +526,13 @@
|
|||||||
<value>Auto-toggle two-finger move in/out of whiteboard</value>
|
<value>Auto-toggle two-finger move in/out of whiteboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_AutoToggleHint" xml:space="preserve">
|
<data name="Gesture_AutoToggleHint" xml:space="preserve">
|
||||||
<value># When enabled: leaving canvas disables two-finger move; entering whiteboard enables it.</value>
|
<value>When enabled: leaving canvas disables two-finger move; entering whiteboard enables it.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_AllowRotateScale" xml:space="preserve">
|
<data name="Gesture_AllowRotateScale" xml:space="preserve">
|
||||||
<value>Allow rotate & scale selected ink</value>
|
<value>Allow rotate & scale selected ink</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_AllowRotateScaleHint" xml:space="preserve">
|
<data name="Gesture_AllowRotateScaleHint" xml:space="preserve">
|
||||||
<value># Allows scaling selected ink with two or more fingers (independent of rotate setting).</value>
|
<value>Allows scaling selected ink with two or more fingers (independent of rotate setting).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_EnablePalmEraser" xml:space="preserve">
|
<data name="Gesture_EnablePalmEraser" xml:space="preserve">
|
||||||
<value>Enable palm eraser</value>
|
<value>Enable palm eraser</value>
|
||||||
@@ -520,7 +550,7 @@
|
|||||||
<value>High sensitivity</value>
|
<value>High sensitivity</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_PalmHint" xml:space="preserve">
|
<data name="Gesture_PalmHint" xml:space="preserve">
|
||||||
<value># Low: larger area/more touches required (less false positive); High: easier to trigger but may mis-detect fingers.</value>
|
<value>Low: larger area/more touches required (less false positive); High: easier to trigger but may mis-detect fingers.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_Title" xml:space="preserve">
|
<data name="InkRecog_Title" xml:space="preserve">
|
||||||
<value>Ink correction</value>
|
<value>Ink correction</value>
|
||||||
@@ -528,6 +558,18 @@
|
|||||||
<data name="InkRecog_EnableInkRecognition" xml:space="preserve">
|
<data name="InkRecog_EnableInkRecognition" xml:space="preserve">
|
||||||
<value>Enable ink recognition</value>
|
<value>Enable ink recognition</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingBeautify" xml:space="preserve">
|
||||||
|
<value>WinRT recognition to handwriting glyphs</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingBeautifyHint" xml:space="preserve">
|
||||||
|
<value>When enabled, ink correction API: first recognizes handwritten words via WinRT, then replaces original strokes with glyph outlines rendered in a handwriting-style font. Requires 64-bit and WinRT.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingFont" xml:space="preserve">
|
||||||
|
<value>Handwriting glyph font</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingFontHint" xml:space="preserve">
|
||||||
|
<value>Font used to replace original strokes with glyph outlines. Falls back through the list if the chosen font is missing.</value>
|
||||||
|
</data>
|
||||||
<data name="InkRecog_BlockRectFakePressure" xml:space="preserve">
|
<data name="InkRecog_BlockRectFakePressure" xml:space="preserve">
|
||||||
<value>Block fake pressure on corrected rectangles</value>
|
<value>Block fake pressure on corrected rectangles</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -556,7 +598,7 @@
|
|||||||
<value>High-precision straightening</value>
|
<value>High-precision straightening</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_HighPrecisionHint" xml:space="preserve">
|
<data name="InkRecog_HighPrecisionHint" xml:space="preserve">
|
||||||
<value># When enabled, lines longer than the threshold will be straightened. Sensitivity 0.05–2.0: smaller = stricter; larger = easier to treat as straight. High-precision samples every 10px for better judgement.</value>
|
<value>When enabled, lines longer than the threshold will be straightened. Sensitivity 0.05–2.0: smaller = stricter; larger = easier to treat as straight. High-precision samples every 10px for better judgement.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_LineEndpointSnapping" xml:space="preserve">
|
<data name="InkRecog_LineEndpointSnapping" xml:space="preserve">
|
||||||
<value>Line endpoint snapping</value>
|
<value>Line endpoint snapping</value>
|
||||||
@@ -664,7 +706,7 @@
|
|||||||
<value>Floating bar opacity in PPT</value>
|
<value>Floating bar opacity in PPT</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Theme_FloatingBarOpacityInPPTHint" xml:space="preserve">
|
<data name="Theme_FloatingBarOpacityInPPTHint" xml:space="preserve">
|
||||||
<value># Takes effect after re-entering slide show</value>
|
<value>Takes effect after re-entering slide show</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Theme_ShowNibButton" xml:space="preserve">
|
<data name="Theme_ShowNibButton" xml:space="preserve">
|
||||||
<value>Show nib-mode button in palette</value>
|
<value>Show nib-mode button in palette</value>
|
||||||
@@ -796,22 +838,22 @@
|
|||||||
<value>Kill WPP process (avoid leftovers)</value>
|
<value>Kill WPP process (avoid leftovers)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_KillWppHint" xml:space="preserve">
|
<data name="PPT_KillWppHint" xml:space="preserve">
|
||||||
<value># When disabled, leftover WPP processes may cause slow close or cannot exit completely.</value>
|
<value>When disabled, leftover WPP processes may cause slow close or cannot exit completely.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_WpsHint1" xml:space="preserve">
|
<data name="PPT_WpsHint1" xml:space="preserve">
|
||||||
<value># If you only use PowerPoint, do not enable WPS integration. If you use WPS, it is recommended not to use PowerPoint together.</value>
|
<value>If you only use PowerPoint, do not enable WPS integration. If you use WPS, it is recommended not to use PowerPoint together.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_WpsLagWarning" xml:space="preserve">
|
<data name="PPT_WpsLagWarning" xml:space="preserve">
|
||||||
<value>Enabling WPS support may cause lag when closing WPS!</value>
|
<value>Enabling WPS support may cause lag when closing WPS!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_WpsSupportHint" xml:space="preserve">
|
<data name="PPT_WpsSupportHint" xml:space="preserve">
|
||||||
<value># WPS is supported, but MS Office and WPS cannot be supported at the same time. To enable WPS support, make sure “WPS Office compatibility with third-party systems and software” is enabled in the WPS config tool, otherwise WPS cannot be detected.</value>
|
<value>WPS is supported, but MS Office and WPS cannot be supported at the same time. To enable WPS support, make sure “WPS Office compatibility with third-party systems and software” is enabled in the WPS config tool, otherwise WPS cannot be detected.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideStrokeWhenSelecting" xml:space="preserve">
|
<data name="Canvas_HideStrokeWhenSelecting" xml:space="preserve">
|
||||||
<value>Hide ink when exiting board mode</value>
|
<value>Hide ink when exiting board mode</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideStrokeWhenSelectingHint" xml:space="preserve">
|
<data name="Canvas_HideStrokeWhenSelectingHint" xml:space="preserve">
|
||||||
<value># When this option is on, ink will not be shown in PPT mode if not in annotation mode.</value>
|
<value>When this option is on, ink will not be shown in PPT mode if not in annotation mode.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ClearInkAlsoClearHistory" xml:space="preserve">
|
<data name="Canvas_ClearInkAlsoClearHistory" xml:space="preserve">
|
||||||
<value>Clear ink history when clearing ink</value>
|
<value>Clear ink history when clearing ink</value>
|
||||||
@@ -853,7 +895,7 @@
|
|||||||
<value>Right opacity</value>
|
<value>Right opacity</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_OffsetHint" xml:space="preserve">
|
<data name="PPT_OffsetHint" xml:space="preserve">
|
||||||
<value># Increase for up, decrease for down; 0 = no offset, centered.</value>
|
<value>Increase for up, decrease for down; 0 = no offset, centered.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_LeftBottomOffset" xml:space="preserve">
|
<data name="PPT_LeftBottomOffset" xml:space="preserve">
|
||||||
<value>Bottom left offset</value>
|
<value>Bottom left offset</value>
|
||||||
@@ -868,7 +910,7 @@
|
|||||||
<value>Bottom right opacity</value>
|
<value>Bottom right opacity</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_OffsetHintHorizontal" xml:space="preserve">
|
<data name="PPT_OffsetHintHorizontal" xml:space="preserve">
|
||||||
<value># Increase for right, decrease for left; 0 = no offset, centered.</value>
|
<value>Increase for right, decrease for left; 0 = no offset, centered.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_SideGroupTitle" xml:space="preserve">
|
<data name="PPT_SideGroupTitle" xml:space="preserve">
|
||||||
<value>Sides</value>
|
<value>Sides</value>
|
||||||
@@ -889,19 +931,28 @@
|
|||||||
<value>PPT page button clickable</value>
|
<value>PPT page button clickable</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_PageButtonClickableHint" xml:space="preserve">
|
<data name="PPT_PageButtonClickableHint" xml:space="preserve">
|
||||||
<value># When enabled, clicking the page button opens PowerPoint grid thumbnails. Not supported in WPS.</value>
|
<value>Enable page-button navigation on click. By default it uses PowerPoint grid thumbnails (not supported in WPS); use the sub-setting below to manually enable enhanced preview for WPS support.</value>
|
||||||
|
</data>
|
||||||
|
<data name="PPT_PageButtonClickable_SubSettings" xml:space="preserve">
|
||||||
|
<value>Page button click sub-settings</value>
|
||||||
|
</data>
|
||||||
|
<data name="PPT_EnhancedPreview" xml:space="preserve">
|
||||||
|
<value>Enhanced preview for page button</value>
|
||||||
|
</data>
|
||||||
|
<data name="PPT_EnhancedPreviewHint" xml:space="preserve">
|
||||||
|
<value>Manually enable this option to show a thumbnail page list when clicking the page button (supports both WPS and PowerPoint), then click a thumbnail to jump.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_LongPressPageTurn" xml:space="preserve">
|
<data name="PPT_LongPressPageTurn" xml:space="preserve">
|
||||||
<value>PPT long-press to turn page</value>
|
<value>PPT long-press to turn page</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_LongPressPageTurnHint" xml:space="preserve">
|
<data name="PPT_LongPressPageTurnHint" xml:space="preserve">
|
||||||
<value># When enabled, long-press on PPT page button to turn pages continuously.</value>
|
<value>When enabled, long-press on PPT page button to turn pages continuously.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UIAccessTopMostHint" xml:space="preserve">
|
<data name="Startup_UIAccessTopMostHint" xml:space="preserve">
|
||||||
<value># With UIA topmost on, app needs admin to stay on top. To turn off, fully quit then start again; restart will not disable it.</value>
|
<value>With UIA topmost on, app needs admin to stay on top. To turn off, fully quit then start again; restart will not disable it.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_SilentUpdateHint" xml:space="preserve">
|
<data name="Startup_SilentUpdateHint" xml:space="preserve">
|
||||||
<value># Silent update installs when the app is idle; no manual action needed.</value>
|
<value>Silent update installs when the app is idle; no manual action needed.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UpdateChannel" xml:space="preserve">
|
<data name="Startup_UpdateChannel" xml:space="preserve">
|
||||||
<value>Update channel</value>
|
<value>Update channel</value>
|
||||||
@@ -916,25 +967,25 @@
|
|||||||
<value>Beta</value>
|
<value>Beta</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UpdateChannelHint" xml:space="preserve">
|
<data name="Startup_UpdateChannelHint" xml:space="preserve">
|
||||||
<value># Stable: reliable updates. Preview: new features with better stability than Beta. Beta: earliest new features.</value>
|
<value>Stable: reliable updates. Preview: new features with better stability than Beta. Beta: earliest new features.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_ManualUpdateHint" xml:space="preserve">
|
<data name="Startup_ManualUpdateHint" xml:space="preserve">
|
||||||
<value># Check and download the latest version now.</value>
|
<value>Check and download the latest version now.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_FixVersion" xml:space="preserve">
|
<data name="Btn_FixVersion" xml:space="preserve">
|
||||||
<value>Repair installation</value>
|
<value>Repair installation</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_FixVersionHint" xml:space="preserve">
|
<data name="Startup_FixVersionHint" xml:space="preserve">
|
||||||
<value># Repair downloads the latest build for the selected channel and reinstalls; use to fix broken installs.</value>
|
<value>Repair downloads the latest build for the selected channel and reinstalls; use to fix broken installs.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_HistoryRollback" xml:space="preserve">
|
<data name="Btn_HistoryRollback" xml:space="preserve">
|
||||||
<value>Rollback to previous version</value>
|
<value>Rollback to previous version</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_HistoryRollbackHint" xml:space="preserve">
|
<data name="Startup_HistoryRollbackHint" xml:space="preserve">
|
||||||
<value># Opens a page to manually roll back to an earlier version.</value>
|
<value>Opens a page to manually roll back to an earlier version.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_SilentUpdateFullHint" xml:space="preserve">
|
<data name="Startup_SilentUpdateFullHint" xml:space="preserve">
|
||||||
<value># When silent update is off, you will be prompted after download. When on, every 10 minutes the app checks: 1) within silent-update time window 2) not in writing mode 3) not in canvas. If all pass, it will close and update.</value>
|
<value>When silent update is off, you will be prompted after download. When on, every 10 minutes the app checks: 1) within silent-update time window 2) not in writing mode 3) not in canvas. If all pass, it will close and update.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_SilentUpdateTimePeriod" xml:space="preserve">
|
<data name="Startup_SilentUpdateTimePeriod" xml:space="preserve">
|
||||||
<value>Silent update time window</value>
|
<value>Silent update time window</value>
|
||||||
@@ -946,7 +997,7 @@
|
|||||||
<value>End time</value>
|
<value>End time</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_TimePeriodHint" xml:space="preserve">
|
<data name="Startup_TimePeriodHint" xml:space="preserve">
|
||||||
<value># If end < start, end is next day. If start = end, window is 24h.</value>
|
<value>If end < start, end is next day. If start = end, window is 24h.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_RunAtStartup" xml:space="preserve">
|
<data name="Startup_RunAtStartup" xml:space="preserve">
|
||||||
<value>Run at startup</value>
|
<value>Run at startup</value>
|
||||||
@@ -954,8 +1005,26 @@
|
|||||||
<data name="Startup_FoldAtStartup" xml:space="preserve">
|
<data name="Startup_FoldAtStartup" xml:space="preserve">
|
||||||
<value>Dock to sidebar after startup</value>
|
<value>Dock to sidebar after startup</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Startup_ExternalProtocol" xml:space="preserve">
|
||||||
|
<value>External protocol (icc://)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_ExternalProtocolHint" xml:space="preserve">
|
||||||
|
<value>Control via icc:// protocol</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_EnableNibMode" xml:space="preserve">
|
||||||
|
<value>Nib Mode</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_EnableNibModeHint" xml:space="preserve">
|
||||||
|
<value>Only respond to pen tip touch, ignore palm and finger</value>
|
||||||
|
</data>
|
||||||
<data name="Canvas_GroupTitle" xml:space="preserve">
|
<data name="Canvas_GroupTitle" xml:space="preserve">
|
||||||
<value>Canvas and ink</value>
|
<value>Canvas</value>
|
||||||
|
</data>
|
||||||
|
<data name="Canvas_DisableHardwareAcceleration" xml:space="preserve">
|
||||||
|
<value>Disable hardware acceleration</value>
|
||||||
|
</data>
|
||||||
|
<data name="Canvas_DisableHardwareAccelerationHint" xml:space="preserve">
|
||||||
|
<value>Improves compatibility but may reduce performance; some effects may require an app restart to fully apply.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ShowCursor" xml:space="preserve">
|
<data name="Canvas_ShowCursor" xml:space="preserve">
|
||||||
<value>Show pen cursor</value>
|
<value>Show pen cursor</value>
|
||||||
@@ -964,13 +1033,13 @@
|
|||||||
<value>Enable pressure-sensitive touch</value>
|
<value>Enable pressure-sensitive touch</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve">
|
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve">
|
||||||
<value># When on, touch screens that support pressure will show pressure; for devices not recognized by the system.</value>
|
<value>When on, touch screens that support pressure will show pressure; for devices not recognized by the system.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_DisablePressure" xml:space="preserve">
|
<data name="Canvas_DisablePressure" xml:space="preserve">
|
||||||
<value>Ignore pressure</value>
|
<value>Ignore pressure</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_DisablePressureHint" xml:space="preserve">
|
<data name="Canvas_DisablePressureHint" xml:space="preserve">
|
||||||
<value># When on, all strokes use uniform thickness; mutually exclusive with pressure-sensitive touch.</value>
|
<value>When on, all strokes use uniform thickness; mutually exclusive with pressure-sensitive touch.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve">
|
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve">
|
||||||
<value>Very small</value>
|
<value>Very small</value>
|
||||||
@@ -988,7 +1057,7 @@
|
|||||||
<value>Very large</value>
|
<value>Very large</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EraserSizeHint" xml:space="preserve">
|
<data name="Canvas_EraserSizeHint" xml:space="preserve">
|
||||||
<value># Change takes effect next time you use area eraser.</value>
|
<value>Change takes effect next time you use area eraser.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_KeepHyperbolaAsymptote" xml:space="preserve">
|
<data name="Canvas_KeepHyperbolaAsymptote" xml:space="preserve">
|
||||||
<value>Keep hyperbola asymptotes</value>
|
<value>Keep hyperbola asymptotes</value>
|
||||||
@@ -1003,7 +1072,7 @@
|
|||||||
<value>Ask each time</value>
|
<value>Ask each time</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HyperbolaAsymptoteHint" xml:space="preserve">
|
<data name="Canvas_HyperbolaAsymptoteHint" xml:space="preserve">
|
||||||
<value># If not kept, undo-related bugs may occur.</value>
|
<value>If not kept, undo-related bugs may occur.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_WPFBezierSmoothing" xml:space="preserve">
|
<data name="Canvas_WPFBezierSmoothing" xml:space="preserve">
|
||||||
<value>Use WPF default Bezier smoothing</value>
|
<value>Use WPF default Bezier smoothing</value>
|
||||||
@@ -1011,23 +1080,29 @@
|
|||||||
<data name="Canvas_AdvancedBezierSmoothing" xml:space="preserve">
|
<data name="Canvas_AdvancedBezierSmoothing" xml:space="preserve">
|
||||||
<value>Use advanced curve smoothing (recommended)</value>
|
<value>Use advanced curve smoothing (recommended)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Canvas_CurveSmoothingMode" xml:space="preserve">
|
||||||
|
<value>Curve smoothing mode</value>
|
||||||
|
</data>
|
||||||
|
<data name="Canvas_CurveSmoothing_Off" xml:space="preserve">
|
||||||
|
<value>Off</value>
|
||||||
|
</data>
|
||||||
<data name="Canvas_EnableInkFade" xml:space="preserve">
|
<data name="Canvas_EnableInkFade" xml:space="preserve">
|
||||||
<value>Enable ink fade</value>
|
<value>Enable ink fade</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EnableInkFadeHint" xml:space="preserve">
|
<data name="Canvas_EnableInkFadeHint" xml:space="preserve">
|
||||||
<value># When on, ink is not committed to canvas; it fades after the set time.</value>
|
<value>When on, ink is not committed to canvas; it fades after the set time.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkFadeInPenMenu" xml:space="preserve">
|
<data name="Canvas_HideInkFadeInPenMenu" xml:space="preserve">
|
||||||
<value>Hide ink fade control in pen menu</value>
|
<value>Hide ink fade control in pen menu</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkFadeInPenMenuHint" xml:space="preserve">
|
<data name="Canvas_HideInkFadeInPenMenuHint" xml:space="preserve">
|
||||||
<value># When on, the pen context menu will not show the ink fade control.</value>
|
<value>When on, the pen context menu will not show the ink fade control.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_BrushAutoRestore" xml:space="preserve">
|
<data name="Canvas_BrushAutoRestore" xml:space="preserve">
|
||||||
<value>Enable brush auto-restore</value>
|
<value>Enable brush auto-restore</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_BrushAutoRestoreHint" xml:space="preserve">
|
<data name="Canvas_BrushAutoRestoreHint" xml:space="preserve">
|
||||||
<value># When on, temporary brush changes will restore at the configured time(s) to the color/opacity/width set here.</value>
|
<value>When on, temporary brush changes will restore at the configured time(s) to the color/opacity/width set here.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_AutoRestoreTimePoints" xml:space="preserve">
|
<data name="Canvas_AutoRestoreTimePoints" xml:space="preserve">
|
||||||
<value>Auto-restore time points (HH:mm, multiple with ;)</value>
|
<value>Auto-restore time points (HH:mm, multiple with ;)</value>
|
||||||
@@ -1072,16 +1147,16 @@
|
|||||||
<value>Switch back to annotation after eraser</value>
|
<value>Switch back to annotation after eraser</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_SwitchBackAfterEraserHint" xml:space="preserve">
|
<data name="Canvas_SwitchBackAfterEraserHint" xml:space="preserve">
|
||||||
<value># When on, after erasing, staying idle for a while will switch back to annotation mode.</value>
|
<value>When on, after erasing, staying idle for a while will switch back to annotation mode.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_SwitchBackDelay" xml:space="preserve">
|
<data name="Canvas_SwitchBackDelay" xml:space="preserve">
|
||||||
<value>Auto switch delay</value>
|
<value>Auto switch delay</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_SwitchBackDelayHint" xml:space="preserve">
|
<data name="Canvas_SwitchBackDelayHint" xml:space="preserve">
|
||||||
<value># If you erase again within the delay, the timer resets.</value>
|
<value>If you erase again within the delay, the timer resets.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_LineEndpointSnappingHint" xml:space="preserve">
|
<data name="InkRecog_LineEndpointSnappingHint" xml:space="preserve">
|
||||||
<value># When on, line endpoints near other endpoints will snap and connect.</value>
|
<value>When on, line endpoints near other endpoints will snap and connect.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_EnterAnnotationOnShow" xml:space="preserve">
|
<data name="PPT_EnterAnnotationOnShow" xml:space="preserve">
|
||||||
<value>Enter annotation mode when starting PPT slide show</value>
|
<value>Enter annotation mode when starting PPT slide show</value>
|
||||||
@@ -1096,19 +1171,19 @@
|
|||||||
<value>Allow finger gesture to turn slides</value>
|
<value>Allow finger gesture to turn slides</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_FingerGestureSlideHint" xml:space="preserve">
|
<data name="PPT_FingerGestureSlideHint" xml:space="preserve">
|
||||||
<value># When canvas is on, finger swipe (not pen) can turn slides in show mode when canvas has no ink.</value>
|
<value>When canvas is on, finger swipe (not pen) can turn slides in show mode when canvas has no ink.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_ShowGestureButtonInShow" xml:space="preserve">
|
<data name="PPT_ShowGestureButtonInShow" xml:space="preserve">
|
||||||
<value>Show gesture buttons in PPT slide show</value>
|
<value>Show gesture buttons in PPT slide show</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_ShowGestureButtonInShowHint" xml:space="preserve">
|
<data name="PPT_ShowGestureButtonInShowHint" xml:space="preserve">
|
||||||
<value># When on, gesture buttons are shown in PPT slide show.</value>
|
<value>When on, gesture buttons are shown in PPT slide show.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_TimeCapsule" xml:space="preserve">
|
<data name="PPT_TimeCapsule" xml:space="preserve">
|
||||||
<value>PPT time capsule</value>
|
<value>PPT time capsule</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_TimeCapsuleHint" xml:space="preserve">
|
<data name="PPT_TimeCapsuleHint" xml:space="preserve">
|
||||||
<value># When on, show time capsule in PPT show; can replace minimized timer window.</value>
|
<value>When on, show time capsule in PPT show; can replace minimized timer window.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_TimeCapsulePosition" xml:space="preserve">
|
<data name="PPT_TimeCapsulePosition" xml:space="preserve">
|
||||||
<value>Time capsule position:</value>
|
<value>Time capsule position:</value>
|
||||||
@@ -1126,25 +1201,25 @@
|
|||||||
<value>Show quick panel in PPT slide show</value>
|
<value>Show quick panel in PPT slide show</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_ShowQuickPanelInShowHint" xml:space="preserve">
|
<data name="PPT_ShowQuickPanelInShowHint" xml:space="preserve">
|
||||||
<value># When off, quick panel is hidden in PPT slide show.</value>
|
<value>When off, quick panel is hidden in PPT slide show.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoScreenshot" xml:space="preserve">
|
<data name="PPT_AutoScreenshot" xml:space="preserve">
|
||||||
<value>Auto screenshot on slide change</value>
|
<value>Auto screenshot on slide change</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoScreenshotHint" xml:space="preserve">
|
<data name="PPT_AutoScreenshotHint" xml:space="preserve">
|
||||||
<value># When on, auto-screenshot when turning page with ink on slide.</value>
|
<value>When on, auto-screenshot when turning page with ink on slide.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoSaveStrokes" xml:space="preserve">
|
<data name="PPT_AutoSaveStrokes" xml:space="preserve">
|
||||||
<value>Auto-save slide ink</value>
|
<value>Auto-save slide ink</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoSaveStrokesHint" xml:space="preserve">
|
<data name="PPT_AutoSaveStrokesHint" xml:space="preserve">
|
||||||
<value># When on, ink is saved when ending slide show and loaded next time (same file and page).</value>
|
<value>When on, ink is saved when ending slide show and loaded next time (same file and page).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_RememberLastPage" xml:space="preserve">
|
<data name="PPT_RememberLastPage" xml:space="preserve">
|
||||||
<value>Remember and prompt last slide position</value>
|
<value>Remember and prompt last slide position</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_RememberLastPageHint" xml:space="preserve">
|
<data name="PPT_RememberLastPageHint" xml:space="preserve">
|
||||||
<value># When on, last page is recorded; choose Yes to jump to it.</value>
|
<value>When on, last page is recorded; choose Yes to jump to it.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_GoToFirstPageOnReenter" xml:space="preserve">
|
<data name="PPT_GoToFirstPageOnReenter" xml:space="preserve">
|
||||||
<value>Go to first slide when entering show</value>
|
<value>Go to first slide when entering show</value>
|
||||||
@@ -1171,13 +1246,13 @@
|
|||||||
<value>Tap with pen in the area below to estimate touch size multiplier</value>
|
<value>Tap with pen in the area below to estimate touch size multiplier</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_TouchMultiplierValueHint" xml:space="preserve">
|
<data name="Advanced_TouchMultiplierValueHint" xml:space="preserve">
|
||||||
<value># Value is for reference only</value>
|
<value>Value is for reference only</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EraserBindTouchMultiplier" xml:space="preserve">
|
<data name="Advanced_EraserBindTouchMultiplier" xml:space="preserve">
|
||||||
<value>Bind eraser to touch size multiplier</value>
|
<value>Bind eraser to touch size multiplier</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EraserBindTouchHint" xml:space="preserve">
|
<data name="Advanced_EraserBindTouchHint" xml:space="preserve">
|
||||||
<value># BoundsWidth is used as contact area threshold</value>
|
<value>BoundsWidth is used as contact area threshold</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_QuadIRMode" xml:space="preserve">
|
<data name="Advanced_QuadIRMode" xml:space="preserve">
|
||||||
<value>Quad IR mode</value>
|
<value>Quad IR mode</value>
|
||||||
@@ -1189,7 +1264,7 @@
|
|||||||
<value>Save logs by date</value>
|
<value>Save logs by date</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_LogRotateHint" xml:space="preserve">
|
<data name="Advanced_LogRotateHint" xml:space="preserve">
|
||||||
<value># Log files over 512 KB are auto-deleted. With date save, logs go to Logs folder; folder is cleared when over 5 MB.</value>
|
<value>Log files over 512 KB are auto-deleted. With date save, logs go to Logs folder; folder is cleared when over 5 MB.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ConfirmExit" xml:space="preserve">
|
<data name="Advanced_ConfirmExit" xml:space="preserve">
|
||||||
<value>Confirm exit with dialog</value>
|
<value>Confirm exit with dialog</value>
|
||||||
@@ -1201,13 +1276,13 @@
|
|||||||
<value>Experimental</value>
|
<value>Experimental</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_FullScreenHelperHint" xml:space="preserve">
|
<data name="Advanced_FullScreenHelperHint" xml:space="preserve">
|
||||||
<value># Thanks to lindexi for FullScreenHelper; reduces taskbar pop-up and supports multi-monitor fullscreen. Disable if you see odd issues; restart ICC to apply.</value>
|
<value>Thanks to lindexi for FullScreenHelper; reduces taskbar pop-up and supports multi-monitor fullscreen. Disable if you see odd issues; restart ICC to apply.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_AvoidFullScreenHelper" xml:space="preserve">
|
<data name="Advanced_AvoidFullScreenHelper" xml:space="preserve">
|
||||||
<value>Enable AvoidFullScreenHelper</value>
|
<value>Enable AvoidFullScreenHelper</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_AvoidFullScreenHelperHint" xml:space="preserve">
|
<data name="Advanced_AvoidFullScreenHelperHint" xml:space="preserve">
|
||||||
<value># Avoid canvas fullscreen; may fix taskbar not on top and Win11 taskbar unclickable. Can cause floating bar offset with AppBar on left/top. Restart ICC to apply.</value>
|
<value>Avoid canvas fullscreen; may fix taskbar not on top and Win11 taskbar unclickable. Can cause floating bar offset with AppBar on left/top. Restart ICC to apply.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EdgeGestureUtil" xml:space="preserve">
|
<data name="Advanced_EdgeGestureUtil" xml:space="preserve">
|
||||||
<value>Enable EdgeGestureUtil</value>
|
<value>Enable EdgeGestureUtil</value>
|
||||||
@@ -1267,7 +1342,7 @@
|
|||||||
<value>Settings backup & restore</value>
|
<value>Settings backup & restore</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Backup_Desc" xml:space="preserve">
|
<data name="Backup_Desc" xml:space="preserve">
|
||||||
<value># You can manually back up current settings or restore previous backups; backups are also created automatically before updates.</value>
|
<value>You can manually back up current settings or restore previous backups; backups are also created automatically before updates.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Backup_AutoBeforeUpdate" xml:space="preserve">
|
<data name="Backup_AutoBeforeUpdate" xml:space="preserve">
|
||||||
<value>Backup before update</value>
|
<value>Backup before update</value>
|
||||||
@@ -1306,7 +1381,7 @@
|
|||||||
<value>Config profiles & hot reload</value>
|
<value>Config profiles & hot reload</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ConfigProfiles_Desc" xml:space="preserve">
|
<data name="ConfigProfiles_Desc" xml:space="preserve">
|
||||||
<value># Selecting a profile switches and hot-reloads it; \"Save as\" saves current settings as a new profile.</value>
|
<value>Selecting a profile switches and hot-reloads it; \"Save as\" saves current settings as a new profile.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ConfigProfiles_Label" xml:space="preserve">
|
<data name="ConfigProfiles_Label" xml:space="preserve">
|
||||||
<value>Profile:</value>
|
<value>Profile:</value>
|
||||||
@@ -1375,7 +1450,7 @@
|
|||||||
<value>Ignore EN5 desktop annotation window when auto folding</value>
|
<value>Ignore EN5 desktop annotation window when auto folding</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoFold_OldZyBoard" xml:space="preserve">
|
<data name="AutoFold_OldZyBoard" xml:space="preserve">
|
||||||
<value>Auto fold when entering old ZhongYuan whiteboard</value>
|
<value>old ZhongYuan whiteboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Automation_AutoFoldInPPT" xml:space="preserve">
|
<data name="Automation_AutoFoldInPPT" xml:space="preserve">
|
||||||
<value>Auto fold while playing PPT</value>
|
<value>Auto fold while playing PPT</value>
|
||||||
@@ -1384,16 +1459,22 @@
|
|||||||
<value>Keep folded after app exit</value>
|
<value>Keep folded after app exit</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Automation_KeepFoldAfterExitHint" xml:space="preserve">
|
<data name="Automation_KeepFoldAfterExitHint" xml:space="preserve">
|
||||||
<value># When on, apps that trigger auto fold will stay folded even after they exit.</value>
|
<value>When on, apps that trigger auto fold will stay folded even after they exit.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_Title" xml:space="preserve">
|
<data name="AutoKill_Title" xml:space="preserve">
|
||||||
<value>Auto kill</value>
|
<value>Auto kill</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AutoFold_Mode" xml:space="preserve">
|
||||||
|
<value>Fold mode</value>
|
||||||
|
</data>
|
||||||
|
<data name="AutoSave_Title" xml:space="preserve">
|
||||||
|
<value>Auto save</value>
|
||||||
|
</data>
|
||||||
<data name="AutoKill_PptTools" xml:space="preserve">
|
<data name="AutoKill_PptTools" xml:space="preserve">
|
||||||
<value>Auto kill Seewo PPT tools</value>
|
<value>Auto kill Seewo PPT tools</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_PptToolsHint" xml:space="preserve">
|
<data name="AutoKill_PptToolsHint" xml:space="preserve">
|
||||||
<value># Killing PPT tools disables Seewo classroom helper. Delete Office.dll in its install folder to stop the PPT toolbar without auto kill.</value>
|
<value>Killing PPT tools disables Seewo classroom helper. Delete Office.dll in its install folder to stop the PPT toolbar without auto kill.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_EasiNote5" xml:space="preserve">
|
<data name="AutoKill_EasiNote5" xml:space="preserve">
|
||||||
<value>Auto kill Seewo Whiteboard 5</value>
|
<value>Auto kill Seewo Whiteboard 5</value>
|
||||||
@@ -1411,7 +1492,7 @@
|
|||||||
<value>Auto kill Seewo Desktop 2.0 annotation</value>
|
<value>Auto kill Seewo Desktop 2.0 annotation</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_SeewoDesktop2AnnoHint" xml:space="preserve">
|
<data name="AutoKill_SeewoDesktop2AnnoHint" xml:space="preserve">
|
||||||
<value># Seewo Desktop 2.0 annotation is 64-bit so ICC (32-bit) cannot inspect it deeply; only process name DesktopAnnotation is matched. If you have another app with the same name, keep this off.</value>
|
<value>Seewo Desktop 2.0 annotation is 64-bit so ICC (32-bit) cannot inspect it deeply; only process name DesktopAnnotation is matched. If you have another app with the same name, keep this off.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_SameAppTitle" xml:space="preserve">
|
<data name="AutoKill_SameAppTitle" xml:space="preserve">
|
||||||
<value>Kill similar apps</value>
|
<value>Kill similar apps</value>
|
||||||
@@ -1489,19 +1570,19 @@
|
|||||||
<value>60 minutes</value>
|
<value>60 minutes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoSaveHint" xml:space="preserve">
|
<data name="Storage_AutoSaveHint" xml:space="preserve">
|
||||||
<value># When on, strokes are auto-saved at the set interval, only when canvas is visible and has ink.</value>
|
<value>When on, strokes are auto-saved at the set interval, only when canvas is visible and has ink.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveFullPageStrokes" xml:space="preserve">
|
<data name="Storage_SaveFullPageStrokes" xml:space="preserve">
|
||||||
<value>Save full-page strokes</value>
|
<value>Save full-page strokes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveFullPageHint" xml:space="preserve">
|
<data name="Storage_SaveFullPageHint" xml:space="preserve">
|
||||||
<value># When on, auto/manual saves store all pages in fullscreen; multiple pages are packed in one archive (whiteboard strokes open only in whiteboard mode; PPT strokes only in slide show mode).</value>
|
<value>When on, auto/manual saves store all pages in fullscreen; multiple pages are packed in one archive (whiteboard strokes open only in whiteboard mode; PPT strokes only in slide show mode).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveAsXml" xml:space="preserve">
|
<data name="Storage_SaveAsXml" xml:space="preserve">
|
||||||
<value>Save as XML format</value>
|
<value>Save as XML format</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveAsXmlHint" xml:space="preserve">
|
<data name="Storage_SaveAsXmlHint" xml:space="preserve">
|
||||||
<value># When on, strokes are saved as XML (ISF) for easier inspection and editing.</value>
|
<value>When on, strokes are saved as XML (ISF) for easier inspection and editing.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoScreenshotMinInk" xml:space="preserve">
|
<data name="Storage_AutoScreenshotMinInk" xml:space="preserve">
|
||||||
<value>Minimum ink for auto screenshot</value>
|
<value>Minimum ink for auto screenshot</value>
|
||||||
@@ -1519,13 +1600,13 @@
|
|||||||
<value>Set save path to Documents</value>
|
<value>Set save path to Documents</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_PathPermissionHint" xml:space="preserve">
|
<data name="Storage_PathPermissionHint" xml:space="preserve">
|
||||||
<value># Please ensure the save folder is writable.</value>
|
<value>Please ensure the save folder is writable.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoDeleteTitle" xml:space="preserve">
|
<data name="Storage_AutoDeleteTitle" xml:space="preserve">
|
||||||
<value>Auto delete old strokes and screenshots</value>
|
<value>Auto delete old strokes and screenshots</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoDeleteHint" xml:space="preserve">
|
<data name="Storage_AutoDeleteHint" xml:space="preserve">
|
||||||
<value># When on, all .icstk and .png files in the auto-save folder may be deleted!</value>
|
<value>When on, all .icstk and .png files in the auto-save folder may be deleted!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_RetentionTitle" xml:space="preserve">
|
<data name="Storage_RetentionTitle" xml:space="preserve">
|
||||||
<value>Retention duration</value>
|
<value>Retention duration</value>
|
||||||
@@ -1543,19 +1624,19 @@
|
|||||||
<value>Switch to annotation when exiting fold mode</value>
|
<value>Switch to annotation when exiting fold mode</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_ExitToAnnotationHint" xml:space="preserve">
|
<data name="FoldMode_ExitToAnnotationHint" xml:space="preserve">
|
||||||
<value># When on, exiting fold mode switches back to annotation for convenience.</value>
|
<value>When on, exiting fold mode switches back to annotation for convenience.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterPPT" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterPPT" xml:space="preserve">
|
||||||
<value>Auto fold floating bar after PPT show</value>
|
<value>Auto fold floating bar after PPT show</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterPPTHint" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterPPTHint" xml:space="preserve">
|
||||||
<value># When on, floating bar is auto-folded after exiting PPT slide show.</value>
|
<value>When on, floating bar is auto-folded after exiting PPT slide show.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterWhiteboard" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterWhiteboard" xml:space="preserve">
|
||||||
<value>Auto fold when exiting whiteboard</value>
|
<value>Auto fold when exiting whiteboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterWhiteboardHint" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterWhiteboardHint" xml:space="preserve">
|
||||||
<value># When on, exiting whiteboard folds back to sidebar.</value>
|
<value>When on, exiting whiteboard folds back to sidebar.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Random_Title" xml:space="preserve">
|
<data name="Random_Title" xml:space="preserve">
|
||||||
<value>Random roll call</value>
|
<value>Random roll call</value>
|
||||||
@@ -1624,7 +1705,7 @@
|
|||||||
<value>Avoidance weight</value>
|
<value>Avoidance weight</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Random_ML_Hint" xml:space="preserve">
|
<data name="Random_ML_Hint" xml:space="preserve">
|
||||||
<value># ML analyzes recent roll-call history to avoid repeating the same students.</value>
|
<value>ML analyzes recent roll-call history to avoid repeating the same students.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Timer_Title" xml:space="preserve">
|
<data name="Timer_Title" xml:space="preserve">
|
||||||
<value>Timer settings</value>
|
<value>Timer settings</value>
|
||||||
@@ -1701,6 +1782,12 @@
|
|||||||
<data name="About_PrivacyCheckboxSuffix" xml:space="preserve">
|
<data name="About_PrivacyCheckboxSuffix" xml:space="preserve">
|
||||||
<value> privacy statement</value>
|
<value> privacy statement</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="About_PrivacyAndTelemetry" xml:space="preserve">
|
||||||
|
<value>Privacy & Telemetry</value>
|
||||||
|
</data>
|
||||||
|
<data name="About_PrivacyAgreement" xml:space="preserve">
|
||||||
|
<value>I have read and agree to the privacy statement</value>
|
||||||
|
</data>
|
||||||
<data name="About_TelemetryLabel" xml:space="preserve">
|
<data name="About_TelemetryLabel" xml:space="preserve">
|
||||||
<value>Anonymous usage data upload:</value>
|
<value>Anonymous usage data upload:</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1714,7 +1801,7 @@
|
|||||||
<value>Upload basic + optional data</value>
|
<value>Upload basic + optional data</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="About_LicenseHint" xml:space="preserve">
|
<data name="About_LicenseHint" xml:space="preserve">
|
||||||
<value># Before using or distributing this software, you must be aware of the related open-source licenses. This software is based on https://github.com/WXRIW/Ink-Canvas.</value>
|
<value>Before using or distributing this software, you must be aware of the related open-source licenses. This software is based on https://github.com/WXRIW/Ink-Canvas.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="About_LicenseTitle" xml:space="preserve">
|
<data name="About_LicenseTitle" xml:space="preserve">
|
||||||
<value>This software, ICA and Ink Canvas are all open sourced under a license</value>
|
<value>This software, ICA and Ink Canvas are all open sourced under a license</value>
|
||||||
@@ -1777,7 +1864,7 @@
|
|||||||
<value>Finger mode BoundsWidth</value>
|
<value>Finger mode BoundsWidth</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EdgeGestureUtilHint_Part1" xml:space="preserve">
|
<data name="Advanced_EdgeGestureUtilHint_Part1" xml:space="preserve">
|
||||||
<value># EdgeGestureUtil is newly introduced in ICC to temporarily block edge gestures when using touch (e.g., on Windows 10: swipe from the left edge to Task View, from the right edge to Action Center; on Windows 11: swipe up from the bottom to open Start). It works by using</value>
|
<value>EdgeGestureUtil is newly introduced in ICC to temporarily block edge gestures when using touch (e.g., on Windows 10: swipe from the left edge to Task View, from the right edge to Action Center; on Windows 11: swipe up from the bottom to open Start). It works by using System.EdgeGesture.DisableTouchWhenFullscreen (When the app window is active and in full-screen mode (or an owned window is active), prevents edge gesture behavior.) If anything is abnormal, turn this option off; it should take effect immediately. (Not available on Windows 7/8.)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EdgeGestureUtilHint_Part2" xml:space="preserve">
|
<data name="Advanced_EdgeGestureUtilHint_Part2" xml:space="preserve">
|
||||||
<value>(When the app window is active and in full-screen mode (or an owned window is active), prevents edge gesture behavior.) If anything is abnormal, turn this option off; it should take effect immediately. (Not available on Windows 7/8.)</value>
|
<value>(When the app window is active and in full-screen mode (or an owned window is active), prevents edge gesture behavior.) If anything is abnormal, turn this option off; it should take effect immediately. (Not available on Windows 7/8.)</value>
|
||||||
@@ -1786,19 +1873,19 @@
|
|||||||
<value>Enable ForceFullScreen</value>
|
<value>Enable ForceFullScreen</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ForceFullScreenHint" xml:space="preserve">
|
<data name="Advanced_ForceFullScreenHint" xml:space="preserve">
|
||||||
<value># When a window size change is detected, automatically uses Win32 API to set this window size to the primary monitor size (in device pixels). Turn it off if you don't need it; takes effect immediately.</value>
|
<value>When a window size change is detected, automatically uses Win32 API to set this window size to the primary monitor size (in device pixels). Turn it off if you don't need it; takes effect immediately.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_DPIChangeDetection" xml:space="preserve">
|
<data name="Advanced_DPIChangeDetection" xml:space="preserve">
|
||||||
<value>Enable DPIChangeDetection</value>
|
<value>Enable DPIChangeDetection</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_DPIChangeDetectionHint" xml:space="preserve">
|
<data name="Advanced_DPIChangeDetectionHint" xml:space="preserve">
|
||||||
<value># When a system DPI change is detected, it tries to keep FloatingBar visible. If it goes off-screen, it will attempt to move it into the visible area. (Increasing DPI may trigger this; decreasing DPI won't auto-move—adjust manually.)</value>
|
<value>When a system DPI change is detected, it tries to keep FloatingBar visible. If it goes off-screen, it will attempt to move it into the visible area. (Increasing DPI may trigger this; decreasing DPI won't auto-move—adjust manually.)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ResolutionChangeDetection" xml:space="preserve">
|
<data name="Advanced_ResolutionChangeDetection" xml:space="preserve">
|
||||||
<value>Enable ResolutionChangeDetection</value>
|
<value>Enable ResolutionChangeDetection</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ResolutionChangeDetectionHint" xml:space="preserve">
|
<data name="Advanced_ResolutionChangeDetectionHint" xml:space="preserve">
|
||||||
<value># When a screen resolution change is detected, it tries to keep FloatingBar visible. If it goes off-screen, it will attempt to move it into the visible area. (Reducing resolution may trigger this; if it's still on-screen it won't auto-adjust—adjust manually.)</value>
|
<value>When a screen resolution change is detected, it tries to keep FloatingBar visible. If it goes off-screen, it will attempt to move it into the visible area. (Reducing resolution may trigger this; if it's still on-screen it won't auto-adjust—adjust manually.)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FloatingInterceptor_App_SeewoBoard3" xml:space="preserve">
|
<data name="FloatingInterceptor_App_SeewoBoard3" xml:space="preserve">
|
||||||
<value>Seewo Whiteboard 3</value>
|
<value>Seewo Whiteboard 3</value>
|
||||||
@@ -2088,6 +2175,9 @@ Hide</value>
|
|||||||
<data name="FloatingBar_GestureButton" xml:space="preserve">
|
<data name="FloatingBar_GestureButton" xml:space="preserve">
|
||||||
<value>Gesture</value>
|
<value>Gesture</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FloatingBar_ExitButton" xml:space="preserve">
|
||||||
|
<value>Exit</value>
|
||||||
|
</data>
|
||||||
<data name="FloatingBar_GesturePanelTitle" xml:space="preserve">
|
<data name="FloatingBar_GesturePanelTitle" xml:space="preserve">
|
||||||
<value>Gesture options</value>
|
<value>Gesture options</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2583,4 +2673,187 @@ Hide</value>
|
|||||||
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve">
|
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve">
|
||||||
<value>When enabled, the whiteboard toolbar Booth button launches Seewo Video Showcase (must be installed). When disabled, the built-in booth is used.</value>
|
<value>When enabled, the whiteboard toolbar Booth button launches Seewo Video Showcase (must be installed). When disabled, the built-in booth is used.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Storage_Title" xml:space="preserve">
|
||||||
|
<value>Storage</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_TotalUsage" xml:space="preserve">
|
||||||
|
<value>ICC CE total usage</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_DiskPercent" xml:space="preserve">
|
||||||
|
<value>Disk percentage</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Calculating" xml:space="preserve">
|
||||||
|
<value>Calculating…</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_CalculateFailed" xml:space="preserve">
|
||||||
|
<value>Calculation failed</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Refresh" xml:space="preserve">
|
||||||
|
<value>Refresh</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_OpenAppFolder" xml:space="preserve">
|
||||||
|
<value>Open app folder</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_CategoryDetails" xml:space="preserve">
|
||||||
|
<value>Category details</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Clean" xml:space="preserve">
|
||||||
|
<value>Clean</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Core" xml:space="preserve">
|
||||||
|
<value>Core</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Logs" xml:space="preserve">
|
||||||
|
<value>Logs</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Ink" xml:space="preserve">
|
||||||
|
<value>Ink</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Backups" xml:space="preserve">
|
||||||
|
<value>Backups</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Custom" xml:space="preserve">
|
||||||
|
<value>Custom</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Plugins" xml:space="preserve">
|
||||||
|
<value>Plugins</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Update" xml:space="preserve">
|
||||||
|
<value>Update</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Other" xml:space="preserve">
|
||||||
|
<value>Other</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Core_Header" xml:space="preserve">
|
||||||
|
<value>Core files</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Core_Desc" xml:space="preserve">
|
||||||
|
<value>Includes .json configs, .enc usage stats, .exe / .dll executables and .dat data files.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Core_NotCleanable" xml:space="preserve">
|
||||||
|
<value>Core files cannot be cleaned to keep the application functional.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Logs_Header" xml:space="preserve">
|
||||||
|
<value>Logs</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Logs_Desc" xml:space="preserve">
|
||||||
|
<value>.txt logs and crash reports under Logs / Crashes. Cleanable.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Hotkey_NotSet" xml:space="preserve">
|
||||||
|
<value>Not set</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Security" xml:space="preserve">
|
||||||
|
<value>Security</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Security_Tooltip" xml:space="preserve">
|
||||||
|
<value>Security password and process protection</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Hotkey" xml:space="preserve">
|
||||||
|
<value>Hotkeys</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Hotkey_Tooltip" xml:space="preserve">
|
||||||
|
<value>Hotkey settings</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Ink_Header" xml:space="preserve">
|
||||||
|
<value>Ink</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Ink_Desc" xml:space="preserve">
|
||||||
|
<value>.icstk / .xml / screenshot .png files under Saves.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Backups_Header" xml:space="preserve">
|
||||||
|
<value>Backups</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Backups_Desc" xml:space="preserve">
|
||||||
|
<value>Settings / config backups (.json / .zip) under Backups.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Custom_Header" xml:space="preserve">
|
||||||
|
<value>Custom files</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Custom_Desc" xml:space="preserve">
|
||||||
|
<value>Custom icons and roll-call backgrounds (icons / backgrounds, .png).</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Custom_Hint" xml:space="preserve">
|
||||||
|
<value>Custom files are added by the user and won't be auto-cleaned. Manage them in their dedicated UI.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Plugins_Header" xml:space="preserve">
|
||||||
|
<value>Plugins</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Plugins_Desc" xml:space="preserve">
|
||||||
|
<value>.iccpp plugin packages under the plugins directory.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Plugins_Hint" xml:space="preserve">
|
||||||
|
<value>Use the plugins page to uninstall plugins.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Update_Header" xml:space="preserve">
|
||||||
|
<value>Auto update</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Update_Desc" xml:space="preserve">
|
||||||
|
<value>Installer packages and download cache under AutoUpdate. Refreshed each release.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Other_Header" xml:space="preserve">
|
||||||
|
<value>Other</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Other_Desc" xml:space="preserve">
|
||||||
|
<value>Files not classified into the categories above.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Title" xml:space="preserve">
|
||||||
|
<value>Confirm cleanup</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Body" xml:space="preserve">
|
||||||
|
<value>All files in the "{0}" category will be permanently deleted. Continue?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Second_Title" xml:space="preserve">
|
||||||
|
<value>Confirm again</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Second_Body" xml:space="preserve">
|
||||||
|
<value>Confirm again: are you sure you want to delete "{0}"?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_CleanFailed" xml:space="preserve">
|
||||||
|
<value>Cleanup failed: {0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_NavTooltip" xml:space="preserve">
|
||||||
|
<value>View and clean storage used by ICC CE</value>
|
||||||
|
</data>
|
||||||
|
<data name="Debug_ShowConsole_Header" xml:space="preserve">
|
||||||
|
<value>Show debug console</value>
|
||||||
|
</data>
|
||||||
|
<data name="Debug_ShowConsole_Desc" xml:space="preserve">
|
||||||
|
<value>Show a separate console window for live log output (takes effect immediately; if "Enable logging" is off in settings, no content will be emitted).</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_UseCustomSaveFileName_Header" xml:space="preserve">
|
||||||
|
<value>Use custom save file name</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_UseCustomSaveFileName_Desc" xml:space="preserve">
|
||||||
|
<value>When enabled, choose how saved files are named</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileNameFormat" xml:space="preserve">
|
||||||
|
<value>File name format</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_Timestamp" xml:space="preserve">
|
||||||
|
<value>Timestamp (default)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_Date" xml:space="preserve">
|
||||||
|
<value>Date</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateTime" xml:space="preserve">
|
||||||
|
<value>Date + Time</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateMode" xml:space="preserve">
|
||||||
|
<value>Date + Mode</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateModePage" xml:space="preserve">
|
||||||
|
<value>Date + Mode + Page</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateModePageCount" xml:space="preserve">
|
||||||
|
<value>Date + Mode + Page + Stroke count</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_Custom" xml:space="preserve">
|
||||||
|
<value>Custom...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_CustomTemplate_Header" xml:space="preserve">
|
||||||
|
<value>Custom template</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_CustomTemplate_Desc" xml:space="preserve">
|
||||||
|
<value>Available placeholders: {date} {time} {datetime} {mode} {page} {count} {type}</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -222,6 +222,15 @@
|
|||||||
<data name="Btn_Exit" xml:space="preserve">
|
<data name="Btn_Exit" xml:space="preserve">
|
||||||
<value>退出</value>
|
<value>退出</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Home_RestartApp" xml:space="preserve">
|
||||||
|
<value>重启应用程序</value>
|
||||||
|
</data>
|
||||||
|
<data name="Home_ResetSettings" xml:space="preserve">
|
||||||
|
<value>重置为推荐设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="Home_ExitApp" xml:space="preserve">
|
||||||
|
<value>退出应用程序</value>
|
||||||
|
</data>
|
||||||
<data name="Settings_Mode" xml:space="preserve">
|
<data name="Settings_Mode" xml:space="preserve">
|
||||||
<value>模式设置</value>
|
<value>模式设置</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -271,7 +280,28 @@
|
|||||||
<value>UIA置顶</value>
|
<value>UIA置顶</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UIATopMostHint" xml:space="preserve">
|
<data name="Startup_UIATopMostHint" xml:space="preserve">
|
||||||
<value># 开启UIA置顶后,软件需要管理员启动才能置顶…</value>
|
<value>开启UIA置顶后,软件需要管理员启动才能置顶…</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode" xml:space="preserve">
|
||||||
|
<value>置顶模式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_Normal" xml:space="preserve">
|
||||||
|
<value>普通置顶</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_UIA" xml:space="preserve">
|
||||||
|
<value>UIA置顶(需要管理员权限)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_UIA_RestartRequired" xml:space="preserve">
|
||||||
|
<value>切换到UIA置顶模式需要重启软件才能生效,是否立即重启?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_Normal_RestartRequired" xml:space="preserve">
|
||||||
|
<value>切换到普通置顶模式需要重启软件才能生效,是否立即重启?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_RestartAsAdmin" xml:space="preserve">
|
||||||
|
<value>重启到管理员模式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_TopMostMode_RestartAsNormal" xml:space="preserve">
|
||||||
|
<value>切换到非管理员模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Header_AutoUpdate" xml:space="preserve">
|
<data name="Header_AutoUpdate" xml:space="preserve">
|
||||||
<value>自动检查更新</value>
|
<value>自动检查更新</value>
|
||||||
@@ -280,7 +310,7 @@
|
|||||||
<value>静默更新</value>
|
<value>静默更新</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SilentUpdate_Hint" xml:space="preserve">
|
<data name="SilentUpdate_Hint" xml:space="preserve">
|
||||||
<value># 静默更新将在软件不使用时自动安装,无需手动操作</value>
|
<value>静默更新将在软件不使用时自动安装,无需手动操作</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Update_Channel" xml:space="preserve">
|
<data name="Update_Channel" xml:space="preserve">
|
||||||
<value>更新通道</value>
|
<value>更新通道</value>
|
||||||
@@ -295,28 +325,28 @@
|
|||||||
<value>测试版 (Beta)</value>
|
<value>测试版 (Beta)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Channel_Hint" xml:space="preserve">
|
<data name="Channel_Hint" xml:space="preserve">
|
||||||
<value># 稳定版提供可靠更新,预览版提供新功能体验…</value>
|
<value>稳定版提供可靠更新,预览版提供新功能体验…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_ManualUpdate" xml:space="preserve">
|
<data name="Btn_ManualUpdate" xml:space="preserve">
|
||||||
<value>手动更新</value>
|
<value>手动更新</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ManualUpdate_Hint" xml:space="preserve">
|
<data name="ManualUpdate_Hint" xml:space="preserve">
|
||||||
<value># 点击后立即检查并下载最新版本</value>
|
<value>点击后立即检查并下载最新版本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_VersionFix" xml:space="preserve">
|
<data name="Btn_VersionFix" xml:space="preserve">
|
||||||
<value>版本修复</value>
|
<value>版本修复</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="VersionFix_Hint" xml:space="preserve">
|
<data name="VersionFix_Hint" xml:space="preserve">
|
||||||
<value># 版本修复会根据当前选择的通道下载最新版本并执行安装…</value>
|
<value>版本修复会根据当前选择的通道下载最新版本并执行安装…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_Rollback" xml:space="preserve">
|
<data name="Btn_Rollback" xml:space="preserve">
|
||||||
<value>历史版本回滚</value>
|
<value>历史版本回滚</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Rollback_Hint" xml:space="preserve">
|
<data name="Rollback_Hint" xml:space="preserve">
|
||||||
<value># 历史版本回滚,点击后会弹出相应页面…</value>
|
<value>历史版本回滚,点击后会弹出相应页面…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SilentUpdate_AfterDownloadHint" xml:space="preserve">
|
<data name="SilentUpdate_AfterDownloadHint" xml:space="preserve">
|
||||||
<value># 关闭静默更新后,已完成安装包的下载后将会弹窗询问…</value>
|
<value>关闭静默更新后,已完成安装包的下载后将会弹窗询问…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SilentUpdate_TimeRange" xml:space="preserve">
|
<data name="SilentUpdate_TimeRange" xml:space="preserve">
|
||||||
<value>静默更新时间段</value>
|
<value>静默更新时间段</value>
|
||||||
@@ -328,7 +358,7 @@
|
|||||||
<value>终止时间</value>
|
<value>终止时间</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TimeRange_Hint" xml:space="preserve">
|
<data name="TimeRange_Hint" xml:space="preserve">
|
||||||
<value># 若终止时间小于起始时间…</value>
|
<value>若终止时间小于起始时间…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_RunAtLogin" xml:space="preserve">
|
<data name="Startup_RunAtLogin" xml:space="preserve">
|
||||||
<value>开机时运行</value>
|
<value>开机时运行</value>
|
||||||
@@ -346,13 +376,13 @@
|
|||||||
<value>启用压感触屏模式</value>
|
<value>启用压感触屏模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_PressureTouchHint" xml:space="preserve">
|
<data name="Canvas_PressureTouchHint" xml:space="preserve">
|
||||||
<value># 开启后,触屏设备也将支持压感效果…</value>
|
<value>开启后,触屏设备也将支持压感效果…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_IgnorePressure" xml:space="preserve">
|
<data name="Canvas_IgnorePressure" xml:space="preserve">
|
||||||
<value>屏蔽压感</value>
|
<value>屏蔽压感</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_IgnorePressureHint" xml:space="preserve">
|
<data name="Canvas_IgnorePressureHint" xml:space="preserve">
|
||||||
<value># 开启后,将忽略所有设备的压感信息…</value>
|
<value>开启后,将忽略所有设备的压感信息…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EraserSize" xml:space="preserve">
|
<data name="Canvas_EraserSize" xml:space="preserve">
|
||||||
<value>橡皮大小</value>
|
<value>橡皮大小</value>
|
||||||
@@ -373,13 +403,13 @@
|
|||||||
<value>很大</value>
|
<value>很大</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EraserSize_SwitchHint" xml:space="preserve">
|
<data name="EraserSize_SwitchHint" xml:space="preserve">
|
||||||
<value># 非实时切换,下一次使用面积擦时生效。</value>
|
<value>非实时切换,下一次使用面积擦时生效。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkOnExit" xml:space="preserve">
|
<data name="Canvas_HideInkOnExit" xml:space="preserve">
|
||||||
<value>退出画板模式后隐藏墨迹</value>
|
<value>退出画板模式后隐藏墨迹</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkOnExitHint" xml:space="preserve">
|
<data name="Canvas_HideInkOnExitHint" xml:space="preserve">
|
||||||
<value># 开启 退出画板模式后隐藏墨迹 选项后…</value>
|
<value>开启 退出画板模式后隐藏墨迹 选项后…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ClearInkHistory" xml:space="preserve">
|
<data name="Canvas_ClearInkHistory" xml:space="preserve">
|
||||||
<value>清空墨迹时删除墨迹历史记录</value>
|
<value>清空墨迹时删除墨迹历史记录</value>
|
||||||
@@ -403,7 +433,7 @@
|
|||||||
<value>每次询问</value>
|
<value>每次询问</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_AsymptoteHint" xml:space="preserve">
|
<data name="Canvas_AsymptoteHint" xml:space="preserve">
|
||||||
<value># 请注意,若不保留双曲线渐近线可能会有遇到撤回相关的 BUG…</value>
|
<value>请注意,若不保留双曲线渐近线可能会有遇到撤回相关的 BUG…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ShowCircleCenter" xml:space="preserve">
|
<data name="Canvas_ShowCircleCenter" xml:space="preserve">
|
||||||
<value>绘制圆时显示圆心位置</value>
|
<value>绘制圆时显示圆心位置</value>
|
||||||
@@ -418,7 +448,7 @@
|
|||||||
<value>启用墨迹渐隐功能</value>
|
<value>启用墨迹渐隐功能</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_InkFadeHint" xml:space="preserve">
|
<data name="Canvas_InkFadeHint" xml:space="preserve">
|
||||||
<value># 开启后墨迹不会绘制到画布上…</value>
|
<value>开启后墨迹不会绘制到画布上…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_InkFadeTime" xml:space="preserve">
|
<data name="Canvas_InkFadeTime" xml:space="preserve">
|
||||||
<value>墨迹渐隐时间</value>
|
<value>墨迹渐隐时间</value>
|
||||||
@@ -427,7 +457,7 @@
|
|||||||
<value>在笔工具菜单中隐藏墨迹渐隐控制</value>
|
<value>在笔工具菜单中隐藏墨迹渐隐控制</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideFadeInPenMenuHint" xml:space="preserve">
|
<data name="Canvas_HideFadeInPenMenuHint" xml:space="preserve">
|
||||||
<value># 开启后,主工具栏上点击笔工具后弹出的上下文菜单中将不显示…</value>
|
<value>开启后,主工具栏上点击笔工具后弹出的上下文菜单中将不显示…</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Color" xml:space="preserve">
|
<data name="Color" xml:space="preserve">
|
||||||
<value>颜色</value>
|
<value>颜色</value>
|
||||||
@@ -502,7 +532,7 @@
|
|||||||
<value>无操作</value>
|
<value>无操作</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Crash_Hint" xml:space="preserve">
|
<data name="Crash_Hint" xml:space="preserve">
|
||||||
<value># 静默重启:崩溃后自动重启软件,无提示。无操作:崩溃后仅记录日志,不自动重启。</value>
|
<value>静默重启:崩溃后自动重启软件,无提示。无操作:崩溃后仅记录日志,不自动重启。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_Title" xml:space="preserve">
|
<data name="Gesture_Title" xml:space="preserve">
|
||||||
<value>手势</value>
|
<value>手势</value>
|
||||||
@@ -511,13 +541,13 @@
|
|||||||
<value>进退白板模式自动开关双指移动功能</value>
|
<value>进退白板模式自动开关双指移动功能</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_AutoToggleHint" xml:space="preserve">
|
<data name="Gesture_AutoToggleHint" xml:space="preserve">
|
||||||
<value># 开启后退出画板模式时自动关闭双指移动手势,进入白板模式时自动开启双指移动手势</value>
|
<value>开启后退出画板模式时自动关闭双指移动手势,进入白板模式时自动开启双指移动手势</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_AllowRotateScale" xml:space="preserve">
|
<data name="Gesture_AllowRotateScale" xml:space="preserve">
|
||||||
<value>允许双指旋转与缩放选中的墨迹</value>
|
<value>允许双指旋转与缩放选中的墨迹</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_AllowRotateScaleHint" xml:space="preserve">
|
<data name="Gesture_AllowRotateScaleHint" xml:space="preserve">
|
||||||
<value># 允许选中墨迹后对墨迹进行双指或多指缩放操作(此设置不受“允许双指旋转”设置的影响)</value>
|
<value>允许选中墨迹后对墨迹进行双指或多指缩放操作(此设置不受“允许双指旋转”设置的影响)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_EnablePalmEraser" xml:space="preserve">
|
<data name="Gesture_EnablePalmEraser" xml:space="preserve">
|
||||||
<value>启用手掌擦</value>
|
<value>启用手掌擦</value>
|
||||||
@@ -535,7 +565,7 @@
|
|||||||
<value>高敏感度</value>
|
<value>高敏感度</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Gesture_PalmHint" xml:space="preserve">
|
<data name="Gesture_PalmHint" xml:space="preserve">
|
||||||
<value># 低敏感度:需要更大的触摸面积和更多触摸点,减少误判;高敏感度:更容易触发手掌擦,但可能误判手指。</value>
|
<value>低敏感度:需要更大的触摸面积和更多触摸点,减少误判;高敏感度:更容易触发手掌擦,但可能误判手指。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_Title" xml:space="preserve">
|
<data name="InkRecog_Title" xml:space="preserve">
|
||||||
<value>墨迹纠正</value>
|
<value>墨迹纠正</value>
|
||||||
@@ -543,11 +573,23 @@
|
|||||||
<data name="InkRecog_EnableInkRecognition" xml:space="preserve">
|
<data name="InkRecog_EnableInkRecognition" xml:space="preserve">
|
||||||
<value>启用墨迹识别</value>
|
<value>启用墨迹识别</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingBeautify" xml:space="preserve">
|
||||||
|
<value>WinRT识别转手写体字形</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingBeautifyHint" xml:space="preserve">
|
||||||
|
<value>开启后,调用墨迹纠正API时:先WinRT识别手写词,再将识别成功的文字用手写风格字体转成字形轮廓墨迹替换原笔画。需WinRT。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingFont" xml:space="preserve">
|
||||||
|
<value>手写体字形字体</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_HandwritingFontHint" xml:space="preserve">
|
||||||
|
<value>选择用于替换原笔画的手写风格字体。若所选字体不存在,将按列表回退。</value>
|
||||||
|
</data>
|
||||||
<data name="InkRecog_ShapeEngine" xml:space="preserve">
|
<data name="InkRecog_ShapeEngine" xml:space="preserve">
|
||||||
<value>识别引擎</value>
|
<value>识别引擎</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_ShapeEngineHint" xml:space="preserve">
|
<data name="InkRecog_ShapeEngineHint" xml:space="preserve">
|
||||||
<value> 自动:64 位进程使用 WinRT(Windows 10+),32 位使用 IACore。可强制指定 IACore 或 WinRT。</value>
|
<value> 自动:在 Windows 10+ 使用 WinRT,否则使用 IACore。可强制指定 IACore 或 WinRT。IACore 通过 IPC 辅助进程运行,在 32/64 位主进程下均可用。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_ShapeEngineAuto" xml:space="preserve">
|
<data name="InkRecog_ShapeEngineAuto" xml:space="preserve">
|
||||||
<value>自动</value>
|
<value>自动</value>
|
||||||
@@ -586,7 +628,16 @@
|
|||||||
<value>高精度直线拉直</value>
|
<value>高精度直线拉直</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_HighPrecisionHint" xml:space="preserve">
|
<data name="InkRecog_HighPrecisionHint" xml:space="preserve">
|
||||||
<value># 开启后,当绘制的直线超过设定长度阈值时,将自动调整为完美直线。灵敏度范围0.05-2.0,越小要求越严格,越弯曲的线条越不容易被拉直;值越大越容易识别为直线。高精度模式下,每隔10像素取一个计数点,获取更准确的平均值用于判断。</value>
|
<value>开启后,当绘制的直线超过设定长度阈值时,将自动调整为完美直线。灵敏度范围0.05-2.0,越小要求越严格,越弯曲的线条越不容易被拉直;值越大越容易识别为直线。高精度模式下,每隔10像素取一个计数点,获取更准确的平均值用于判断。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_PauseStraightenLine" xml:space="preserve">
|
||||||
|
<value>停顿拉直</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_PauseStraightenHint" xml:space="preserve">
|
||||||
|
<value>书写中停顿时,自动将当前笔画拉直为直线(需同时开启直线自动拉直)。</value>
|
||||||
|
</data>
|
||||||
|
<data name="InkRecog_PauseStraightenDelay" xml:space="preserve">
|
||||||
|
<value>停顿触发延迟</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_LineEndpointSnapping" xml:space="preserve">
|
<data name="InkRecog_LineEndpointSnapping" xml:space="preserve">
|
||||||
<value>直线端点吸附</value>
|
<value>直线端点吸附</value>
|
||||||
@@ -694,7 +745,7 @@
|
|||||||
<value>浮栏在PPT下透明度</value>
|
<value>浮栏在PPT下透明度</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Theme_FloatingBarOpacityInPPTHint" xml:space="preserve">
|
<data name="Theme_FloatingBarOpacityInPPTHint" xml:space="preserve">
|
||||||
<value># 重新进入PPT放映后生效</value>
|
<value>重新进入PPT放映后生效</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Theme_ShowNibButton" xml:space="preserve">
|
<data name="Theme_ShowNibButton" xml:space="preserve">
|
||||||
<value>在调色盘窗口中显示 笔尖模式 按钮</value>
|
<value>在调色盘窗口中显示 笔尖模式 按钮</value>
|
||||||
@@ -826,22 +877,22 @@
|
|||||||
<value>WPP进程查杀(防止WPP残留进程)</value>
|
<value>WPP进程查杀(防止WPP残留进程)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_KillWppHint" xml:space="preserve">
|
<data name="PPT_KillWppHint" xml:space="preserve">
|
||||||
<value># 关闭后将不会自动查杀WPP残留进程,可能导致WPP关闭卡顿或无法彻底退出。</value>
|
<value>关闭后将不会自动查杀WPP残留进程,可能导致WPP关闭卡顿或无法彻底退出。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_WpsHint1" xml:space="preserve">
|
<data name="PPT_WpsHint1" xml:space="preserve">
|
||||||
<value># 如果您只使用PowerPoint请不要打开WPS联动开关,如果使用WPS建议不要使用PowerPoint!</value>
|
<value>如果您只使用PowerPoint请不要打开WPS联动开关,如果使用WPS建议不要使用PowerPoint!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_WpsLagWarning" xml:space="preserve">
|
<data name="PPT_WpsLagWarning" xml:space="preserve">
|
||||||
<value>开启WPS支持后会导致WPS关闭时卡顿!</value>
|
<value>开启WPS支持后会导致WPS关闭时卡顿!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_WpsSupportHint" xml:space="preserve">
|
<data name="PPT_WpsSupportHint" xml:space="preserve">
|
||||||
<value># 可支持 WPS,但目前无法同时支持 MSOffice 和 WPS。若要启用WPS支持,请确保 WPS 是否在 “配置工具” 中开启了 “WPS Office 兼容第三方系统和软件” 选项,否则将无法识别到WPS!</value>
|
<value>可支持 WPS,但目前无法同时支持 MSOffice 和 WPS。若要启用WPS支持,请确保 WPS 是否在 “配置工具” 中开启了 “WPS Office 兼容第三方系统和软件” 选项,否则将无法识别到WPS!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideStrokeWhenSelecting" xml:space="preserve">
|
<data name="Canvas_HideStrokeWhenSelecting" xml:space="preserve">
|
||||||
<value>退出画板模式后隐藏墨迹</value>
|
<value>退出画板模式后隐藏墨迹</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideStrokeWhenSelectingHint" xml:space="preserve">
|
<data name="Canvas_HideStrokeWhenSelectingHint" xml:space="preserve">
|
||||||
<value># 开启 退出画板模式后隐藏墨迹 选项后,进入 PPT 模式时未处于批注模式时不会显示墨迹。</value>
|
<value>开启 退出画板模式后隐藏墨迹 选项后,进入 PPT 模式时未处于批注模式时不会显示墨迹。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ClearInkAlsoClearHistory" xml:space="preserve">
|
<data name="Canvas_ClearInkAlsoClearHistory" xml:space="preserve">
|
||||||
<value>清空墨迹时删除墨迹历史记录</value>
|
<value>清空墨迹时删除墨迹历史记录</value>
|
||||||
@@ -883,7 +934,7 @@
|
|||||||
<value>右侧透明度</value>
|
<value>右侧透明度</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_OffsetHint" xml:space="preserve">
|
<data name="PPT_OffsetHint" xml:space="preserve">
|
||||||
<value># 调大往上偏移,调小往下偏移,修改为0为不偏移,居中放置</value>
|
<value>调大往上偏移,调小往下偏移,修改为0为不偏移,居中放置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_LeftBottomOffset" xml:space="preserve">
|
<data name="PPT_LeftBottomOffset" xml:space="preserve">
|
||||||
<value>左下偏移</value>
|
<value>左下偏移</value>
|
||||||
@@ -898,7 +949,7 @@
|
|||||||
<value>右下透明度</value>
|
<value>右下透明度</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_OffsetHintHorizontal" xml:space="preserve">
|
<data name="PPT_OffsetHintHorizontal" xml:space="preserve">
|
||||||
<value># 调大往右偏移,调小往左偏移,修改为0为不偏移,居中放置</value>
|
<value>调大往右偏移,调小往左偏移,修改为0为不偏移,居中放置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_SideGroupTitle" xml:space="preserve">
|
<data name="PPT_SideGroupTitle" xml:space="preserve">
|
||||||
<value>两侧</value>
|
<value>两侧</value>
|
||||||
@@ -919,19 +970,28 @@
|
|||||||
<value>PPT 页码按钮可点击</value>
|
<value>PPT 页码按钮可点击</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_PageButtonClickableHint" xml:space="preserve">
|
<data name="PPT_PageButtonClickableHint" xml:space="preserve">
|
||||||
<value># 开启该选项后,点击页码按钮可以唤起PowerPoint自带的网格缩略图视图。WPS不支持该功能,开启也没用。</value>
|
<value>开启该选项后,点击页码按钮可唤起页码导航。默认调用 PowerPoint 网格缩略图(WPS 不支持);可在下方子设置手动开启增强型预览以支持 WPS。</value>
|
||||||
|
</data>
|
||||||
|
<data name="PPT_PageButtonClickable_SubSettings" xml:space="preserve">
|
||||||
|
<value>页码按钮点击子设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="PPT_EnhancedPreview" xml:space="preserve">
|
||||||
|
<value>PPT 页码按钮增强型预览</value>
|
||||||
|
</data>
|
||||||
|
<data name="PPT_EnhancedPreviewHint" xml:space="preserve">
|
||||||
|
<value>手动开启后,点击页码按钮将显示缩略图页列表(支持 WPS 与 PowerPoint),并可点击跳转到目标页。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_LongPressPageTurn" xml:space="preserve">
|
<data name="PPT_LongPressPageTurn" xml:space="preserve">
|
||||||
<value>PPT 翻页按钮长按翻页</value>
|
<value>PPT 翻页按钮长按翻页</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_LongPressPageTurnHint" xml:space="preserve">
|
<data name="PPT_LongPressPageTurnHint" xml:space="preserve">
|
||||||
<value># 开启该选项后,长按PPT翻页按钮可以连续翻页,提高翻页效率。</value>
|
<value>开启该选项后,长按PPT翻页按钮可以连续翻页,提高翻页效率。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UIAccessTopMostHint" xml:space="preserve">
|
<data name="Startup_UIAccessTopMostHint" xml:space="preserve">
|
||||||
<value># 开启UIA置顶后,软件需要管理员启动才能置顶,关闭此功能需要完全关闭软件后再手动启动,无法使用重启来关闭此功能</value>
|
<value>开启UIA置顶后,软件需要管理员启动才能置顶,关闭此功能需要完全关闭软件后再手动启动,无法使用重启来关闭此功能</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_SilentUpdateHint" xml:space="preserve">
|
<data name="Startup_SilentUpdateHint" xml:space="preserve">
|
||||||
<value># 静默更新将在软件不使用时自动安装,无需手动操作</value>
|
<value>静默更新将在软件不使用时自动安装,无需手动操作</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UpdateChannel" xml:space="preserve">
|
<data name="Startup_UpdateChannel" xml:space="preserve">
|
||||||
<value>更新通道</value>
|
<value>更新通道</value>
|
||||||
@@ -955,28 +1015,28 @@
|
|||||||
<value>64 位 (x64)</value>
|
<value>64 位 (x64)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UpdatePackageArchitectureHint" xml:space="preserve">
|
<data name="Startup_UpdatePackageArchitectureHint" xml:space="preserve">
|
||||||
<value># 选择要下载架构</value>
|
<value>选择要下载架构</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_UpdateChannelHint" xml:space="preserve">
|
<data name="Startup_UpdateChannelHint" xml:space="preserve">
|
||||||
<value># 稳定版提供可靠更新,预览版提供新功能体验同时拥有相较Beta版更强的稳定性,测试版提供新功能抢先体验</value>
|
<value>稳定版提供可靠更新,预览版提供新功能体验同时拥有相较Beta版更强的稳定性,测试版提供新功能抢先体验</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_ManualUpdateHint" xml:space="preserve">
|
<data name="Startup_ManualUpdateHint" xml:space="preserve">
|
||||||
<value># 点击后立即检查并下载最新版本</value>
|
<value>点击后立即检查并下载最新版本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_FixVersion" xml:space="preserve">
|
<data name="Btn_FixVersion" xml:space="preserve">
|
||||||
<value>版本修复</value>
|
<value>版本修复</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_FixVersionHint" xml:space="preserve">
|
<data name="Startup_FixVersionHint" xml:space="preserve">
|
||||||
<value># 版本修复会根据当前选择的通道下载最新版本并执行安装,可用于修复损坏的安装</value>
|
<value>版本修复会根据当前选择的通道下载最新版本并执行安装,可用于修复损坏的安装</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Btn_HistoryRollback" xml:space="preserve">
|
<data name="Btn_HistoryRollback" xml:space="preserve">
|
||||||
<value>历史版本回滚</value>
|
<value>历史版本回滚</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_HistoryRollbackHint" xml:space="preserve">
|
<data name="Startup_HistoryRollbackHint" xml:space="preserve">
|
||||||
<value># 历史版本回滚,点击后会弹出相应页面供用户手动回滚到之前的版本</value>
|
<value>历史版本回滚,点击后会弹出相应页面供用户手动回滚到之前的版本</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_SilentUpdateFullHint" xml:space="preserve">
|
<data name="Startup_SilentUpdateFullHint" xml:space="preserve">
|
||||||
<value># 关闭静默更新后,已完成安装包的下载后将会弹窗询问是否进行更新,开启静默更新后将会在安装包下载完成后每隔十分钟进行如下检测:①处于静默更新时间段内 ②未处于书写模式 ③未处于画板内。若以上检测通过即会关闭软件进行自动更新。</value>
|
<value>关闭静默更新后,已完成安装包的下载后将会弹窗询问是否进行更新,开启静默更新后将会在安装包下载完成后每隔十分钟进行如下检测:①处于静默更新时间段内 ②未处于书写模式 ③未处于画板内。若以上检测通过即会关闭软件进行自动更新。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_SilentUpdateTimePeriod" xml:space="preserve">
|
<data name="Startup_SilentUpdateTimePeriod" xml:space="preserve">
|
||||||
<value>静默更新时间段</value>
|
<value>静默更新时间段</value>
|
||||||
@@ -988,7 +1048,7 @@
|
|||||||
<value>终止时间</value>
|
<value>终止时间</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_TimePeriodHint" xml:space="preserve">
|
<data name="Startup_TimePeriodHint" xml:space="preserve">
|
||||||
<value># 若终止时间小于起始时间,即将终止时间视为第二天的时间。# 若起始时间与终止时间相同,即视为全天候时间。</value>
|
<value>若终止时间小于起始时间,即将终止时间视为第二天的时间。若起始时间与终止时间相同,即视为全天候时间。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Startup_RunAtStartup" xml:space="preserve">
|
<data name="Startup_RunAtStartup" xml:space="preserve">
|
||||||
<value>开机时运行</value>
|
<value>开机时运行</value>
|
||||||
@@ -996,8 +1056,26 @@
|
|||||||
<data name="Startup_FoldAtStartup" xml:space="preserve">
|
<data name="Startup_FoldAtStartup" xml:space="preserve">
|
||||||
<value>开机运行后收纳到侧边栏</value>
|
<value>开机运行后收纳到侧边栏</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Startup_ExternalProtocol" xml:space="preserve">
|
||||||
|
<value>外部协议调用 (icc://)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_ExternalProtocolHint" xml:space="preserve">
|
||||||
|
<value>通过 icc:// 协议从外部控制软件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_EnableNibMode" xml:space="preserve">
|
||||||
|
<value>笔尖模式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Startup_EnableNibModeHint" xml:space="preserve">
|
||||||
|
<value>启用后仅响应笔尖触摸,忽略手掌和手指</value>
|
||||||
|
</data>
|
||||||
<data name="Canvas_GroupTitle" xml:space="preserve">
|
<data name="Canvas_GroupTitle" xml:space="preserve">
|
||||||
<value>画板和墨迹</value>
|
<value>画板</value>
|
||||||
|
</data>
|
||||||
|
<data name="Canvas_DisableHardwareAcceleration" xml:space="preserve">
|
||||||
|
<value>关闭硬件加速</value>
|
||||||
|
</data>
|
||||||
|
<data name="Canvas_DisableHardwareAccelerationHint" xml:space="preserve">
|
||||||
|
<value>关闭后可提升兼容性,但可能降低性能;部分效果可能需要重启程序后完全生效。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_ShowCursor" xml:space="preserve">
|
<data name="Canvas_ShowCursor" xml:space="preserve">
|
||||||
<value>显示画笔光标</value>
|
<value>显示画笔光标</value>
|
||||||
@@ -1006,13 +1084,13 @@
|
|||||||
<value>启用压感触屏模式</value>
|
<value>启用压感触屏模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve">
|
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve">
|
||||||
<value># 开启后,触屏设备也将支持压感效果,适用于部分支持压感但无法被系统识别的触屏设备。</value>
|
<value>开启后,触屏设备也将支持压感效果,适用于部分支持压感但无法被系统识别的触屏设备。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_DisablePressure" xml:space="preserve">
|
<data name="Canvas_DisablePressure" xml:space="preserve">
|
||||||
<value>屏蔽压感</value>
|
<value>屏蔽压感</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_DisablePressureHint" xml:space="preserve">
|
<data name="Canvas_DisablePressureHint" xml:space="preserve">
|
||||||
<value># 开启后,将忽略所有设备的压感信息,使所有笔画具有统一的粗细。与压感触屏模式互斥。</value>
|
<value>开启后,将忽略所有设备的压感信息,使所有笔画具有统一的粗细。与压感触屏模式互斥。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve">
|
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve">
|
||||||
<value>很小</value>
|
<value>很小</value>
|
||||||
@@ -1030,7 +1108,7 @@
|
|||||||
<value>很大</value>
|
<value>很大</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EraserSizeHint" xml:space="preserve">
|
<data name="Canvas_EraserSizeHint" xml:space="preserve">
|
||||||
<value># 非实时切换,下一次使用面积擦时生效。</value>
|
<value>非实时切换,下一次使用面积擦时生效。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_KeepHyperbolaAsymptote" xml:space="preserve">
|
<data name="Canvas_KeepHyperbolaAsymptote" xml:space="preserve">
|
||||||
<value>保留双曲线渐近线</value>
|
<value>保留双曲线渐近线</value>
|
||||||
@@ -1045,7 +1123,7 @@
|
|||||||
<value>每次询问</value>
|
<value>每次询问</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HyperbolaAsymptoteHint" xml:space="preserve">
|
<data name="Canvas_HyperbolaAsymptoteHint" xml:space="preserve">
|
||||||
<value># 请注意,若不保留双曲线渐近线可能会有遇到撤回相关的 BUG 影响用。</value>
|
<value>请注意,若不保留双曲线渐近线可能会有遇到撤回相关的 BUG 影响使用。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_WPFBezierSmoothing" xml:space="preserve">
|
<data name="Canvas_WPFBezierSmoothing" xml:space="preserve">
|
||||||
<value>使用WPF默认贝塞尔曲线平滑</value>
|
<value>使用WPF默认贝塞尔曲线平滑</value>
|
||||||
@@ -1053,23 +1131,29 @@
|
|||||||
<data name="Canvas_AdvancedBezierSmoothing" xml:space="preserve">
|
<data name="Canvas_AdvancedBezierSmoothing" xml:space="preserve">
|
||||||
<value>使用高级曲线平滑(推荐)</value>
|
<value>使用高级曲线平滑(推荐)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Canvas_CurveSmoothingMode" xml:space="preserve">
|
||||||
|
<value>曲线平滑模式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Canvas_CurveSmoothing_Off" xml:space="preserve">
|
||||||
|
<value>关闭</value>
|
||||||
|
</data>
|
||||||
<data name="Canvas_EnableInkFade" xml:space="preserve">
|
<data name="Canvas_EnableInkFade" xml:space="preserve">
|
||||||
<value>启用墨迹渐隐功能</value>
|
<value>启用墨迹渐隐功能</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_EnableInkFadeHint" xml:space="preserve">
|
<data name="Canvas_EnableInkFadeHint" xml:space="preserve">
|
||||||
<value># 开启后墨迹不会绘制到画布上,而是保持湿墨迹状态,根据设置的渐隐时间自动消失</value>
|
<value>开启后墨迹不会绘制到画布上,而是保持湿墨迹状态,根据设置的渐隐时间自动消失</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkFadeInPenMenu" xml:space="preserve">
|
<data name="Canvas_HideInkFadeInPenMenu" xml:space="preserve">
|
||||||
<value>在笔工具菜单中隐藏墨迹渐隐控制</value>
|
<value>在笔工具菜单中隐藏墨迹渐隐控制</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_HideInkFadeInPenMenuHint" xml:space="preserve">
|
<data name="Canvas_HideInkFadeInPenMenuHint" xml:space="preserve">
|
||||||
<value># 开启后,主工具栏上点击笔工具后弹出的上下文菜单中将不显示墨迹渐隐控制开关</value>
|
<value>开启后,主工具栏上点击笔工具后弹出的上下文菜单中将不显示墨迹渐隐控制开关</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_BrushAutoRestore" xml:space="preserve">
|
<data name="Canvas_BrushAutoRestore" xml:space="preserve">
|
||||||
<value>启用画笔自动恢复</value>
|
<value>启用画笔自动恢复</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_BrushAutoRestoreHint" xml:space="preserve">
|
<data name="Canvas_BrushAutoRestoreHint" xml:space="preserve">
|
||||||
<value># 启用后,临时修改画笔设置后将在指定时间点自动恢复到你在此处配置的颜色 / 透明度 / 粗细</value>
|
<value>启用后,临时修改画笔设置后将在指定时间点自动恢复到你在此处配置的颜色 / 透明度 / 粗细</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_AutoRestoreTimePoints" xml:space="preserve">
|
<data name="Canvas_AutoRestoreTimePoints" xml:space="preserve">
|
||||||
<value>自动恢复时间点 (HH:mm,可多个,用 ; 分隔)</value>
|
<value>自动恢复时间点 (HH:mm,可多个,用 ; 分隔)</value>
|
||||||
@@ -1114,16 +1198,16 @@
|
|||||||
<value>使用橡皮擦后自动切换回批注模式</value>
|
<value>使用橡皮擦后自动切换回批注模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_SwitchBackAfterEraserHint" xml:space="preserve">
|
<data name="Canvas_SwitchBackAfterEraserHint" xml:space="preserve">
|
||||||
<value># 开启后,使用橡皮擦进行擦除操作后静置一段时间将自动切换回批注模式</value>
|
<value>开启后,使用橡皮擦进行擦除操作后静置一段时间将自动切换回批注模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_SwitchBackDelay" xml:space="preserve">
|
<data name="Canvas_SwitchBackDelay" xml:space="preserve">
|
||||||
<value>自动切换延迟时间</value>
|
<value>自动切换延迟时间</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Canvas_SwitchBackDelayHint" xml:space="preserve">
|
<data name="Canvas_SwitchBackDelayHint" xml:space="preserve">
|
||||||
<value># 若在计时时间内再次进行擦除操作,计时器将重新开始计时</value>
|
<value>若在计时时间内再次进行擦除操作,计时器将重新开始计时</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InkRecog_LineEndpointSnappingHint" xml:space="preserve">
|
<data name="InkRecog_LineEndpointSnappingHint" xml:space="preserve">
|
||||||
<value># 开启后,当绘制的直线端点靠近其他直线端点时,将自动吸附连接。</value>
|
<value>开启后,当绘制的直线端点靠近其他直线端点时,将自动吸附连接。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_EnterAnnotationOnShow" xml:space="preserve">
|
<data name="PPT_EnterAnnotationOnShow" xml:space="preserve">
|
||||||
<value>进入 PPT 放映时自动进入批注模式</value>
|
<value>进入 PPT 放映时自动进入批注模式</value>
|
||||||
@@ -1138,19 +1222,19 @@
|
|||||||
<value>允许使用手指手势进行幻灯片翻页</value>
|
<value>允许使用手指手势进行幻灯片翻页</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_FingerGestureSlideHint" xml:space="preserve">
|
<data name="PPT_FingerGestureSlideHint" xml:space="preserve">
|
||||||
<value># 允许开启画板时使用手指手势进行幻灯片翻页(启用后,在幻灯片放映模式下,当画板无墨迹时,使用手指(笔尖或手掌无法识别)左右滑动即可控制幻灯片翻页。)</value>
|
<value>允许开启画板时使用手指手势进行幻灯片翻页(启用后,在幻灯片放映模式下,当画板无墨迹时,使用手指(笔尖或手掌无法识别)左右滑动即可控制幻灯片翻页。)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_ShowGestureButtonInShow" xml:space="preserve">
|
<data name="PPT_ShowGestureButtonInShow" xml:space="preserve">
|
||||||
<value>PPT 放映模式显示手势按钮</value>
|
<value>PPT 放映模式显示手势按钮</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_ShowGestureButtonInShowHint" xml:space="preserve">
|
<data name="PPT_ShowGestureButtonInShowHint" xml:space="preserve">
|
||||||
<value># 开启后在 PPT 放映模式下也显示手势按钮</value>
|
<value>开启后在 PPT 放映模式下也显示手势按钮</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_TimeCapsule" xml:space="preserve">
|
<data name="PPT_TimeCapsule" xml:space="preserve">
|
||||||
<value>PPT时间显示胶囊</value>
|
<value>PPT时间显示胶囊</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_TimeCapsuleHint" xml:space="preserve">
|
<data name="PPT_TimeCapsuleHint" xml:space="preserve">
|
||||||
<value># 开启后在 PPT 放映模式下显示时间胶囊,可替代最小化计时器窗口</value>
|
<value>开启后在 PPT 放映模式下显示时间胶囊,可替代最小化计时器窗口</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_TimeCapsulePosition" xml:space="preserve">
|
<data name="PPT_TimeCapsulePosition" xml:space="preserve">
|
||||||
<value>时间胶囊位置:</value>
|
<value>时间胶囊位置:</value>
|
||||||
@@ -1168,25 +1252,25 @@
|
|||||||
<value>PPT 放映时显示快速面板</value>
|
<value>PPT 放映时显示快速面板</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_ShowQuickPanelInShowHint" xml:space="preserve">
|
<data name="PPT_ShowQuickPanelInShowHint" xml:space="preserve">
|
||||||
<value># 关闭后在 PPT 放映时不显示快速面板</value>
|
<value>关闭后在 PPT 放映时不显示快速面板</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoScreenshot" xml:space="preserve">
|
<data name="PPT_AutoScreenshot" xml:space="preserve">
|
||||||
<value>自动幻灯片截屏</value>
|
<value>自动幻灯片截屏</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoScreenshotHint" xml:space="preserve">
|
<data name="PPT_AutoScreenshotHint" xml:space="preserve">
|
||||||
<value># 开启 自动幻灯片截屏 后将会在幻灯片有墨迹时翻页自动截屏</value>
|
<value>开启 自动幻灯片截屏 后将会在幻灯片有墨迹时翻页自动截屏</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoSaveStrokes" xml:space="preserve">
|
<data name="PPT_AutoSaveStrokes" xml:space="preserve">
|
||||||
<value>自动保存幻灯片墨迹</value>
|
<value>自动保存幻灯片墨迹</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_AutoSaveStrokesHint" xml:space="preserve">
|
<data name="PPT_AutoSaveStrokesHint" xml:space="preserve">
|
||||||
<value># 开启 自动保存幻灯片墨迹 后将在结束幻灯片放映时自动将保存已有墨迹,并在下次打开时自动加载(文件名和幻灯片页数都要相同)</value>
|
<value>开启 自动保存幻灯片墨迹 后将在结束幻灯片放映时自动将保存已有墨迹,并在下次打开时自动加载(文件名和幻灯片页数都要相同)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_RememberLastPage" xml:space="preserve">
|
<data name="PPT_RememberLastPage" xml:space="preserve">
|
||||||
<value>记忆并提示上次播放位置</value>
|
<value>记忆并提示上次播放位置</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_RememberLastPageHint" xml:space="preserve">
|
<data name="PPT_RememberLastPageHint" xml:space="preserve">
|
||||||
<value>#开启后会记录上次播放的页数,点击"是"后会自动跳转</value>
|
<value>开启后会记录上次播放的页数,点击"是"后会自动跳转</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PPT_GoToFirstPageOnReenter" xml:space="preserve">
|
<data name="PPT_GoToFirstPageOnReenter" xml:space="preserve">
|
||||||
<value>进入放映时回到首页</value>
|
<value>进入放映时回到首页</value>
|
||||||
@@ -1214,13 +1298,13 @@
|
|||||||
<value>在下方区域内用笔尖点击以估计触摸大小倍数</value>
|
<value>在下方区域内用笔尖点击以估计触摸大小倍数</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_TouchMultiplierValueHint" xml:space="preserve">
|
<data name="Advanced_TouchMultiplierValueHint" xml:space="preserve">
|
||||||
<value># 数值仅供参考</value>
|
<value>数值仅供参考</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EraserBindTouchMultiplier" xml:space="preserve">
|
<data name="Advanced_EraserBindTouchMultiplier" xml:space="preserve">
|
||||||
<value>橡皮擦绑定触摸大小倍数</value>
|
<value>橡皮擦绑定触摸大小倍数</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EraserBindTouchHint" xml:space="preserve">
|
<data name="Advanced_EraserBindTouchHint" xml:space="preserve">
|
||||||
<value># BoundsWidth 参数作为接触面积区分界限</value>
|
<value>BoundsWidth 参数作为接触面积区分界限</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_QuadIRMode" xml:space="preserve">
|
<data name="Advanced_QuadIRMode" xml:space="preserve">
|
||||||
<value>四边红外模式</value>
|
<value>四边红外模式</value>
|
||||||
@@ -1232,7 +1316,7 @@
|
|||||||
<value>日志以日期保存</value>
|
<value>日志以日期保存</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_LogRotateHint" xml:space="preserve">
|
<data name="Advanced_LogRotateHint" xml:space="preserve">
|
||||||
<value># 日志文件超过 512 KB 时会自动删除。开启日期保存后,日志将保存在Logs文件夹中,当文件夹大小超过5MB时自动清空。</value>
|
<value>日志文件超过 512 KB 时会自动删除。开启日期保存后,日志将保存在Logs文件夹中,当文件夹大小超过5MB时自动清空。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ConfirmExit" xml:space="preserve">
|
<data name="Advanced_ConfirmExit" xml:space="preserve">
|
||||||
<value>关闭软件时二次弹窗确认</value>
|
<value>关闭软件时二次弹窗确认</value>
|
||||||
@@ -1244,13 +1328,13 @@
|
|||||||
<value>实验性选项</value>
|
<value>实验性选项</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_FullScreenHelperHint" xml:space="preserve">
|
<data name="Advanced_FullScreenHelperHint" xml:space="preserve">
|
||||||
<value># 感谢lindexi大佬提供的FullScreenHelper,可以减少任务栏弹出的问题,且支持多显示器自动全屏(虽然对icc来说没什么用就是了),如果遇到一些玄学问题,可以关闭该功能,重启icc后生效。</value>
|
<value>感谢lindexi大佬提供的FullScreenHelper,可以减少任务栏弹出的问题,且支持多显示器自动全屏(虽然对icc来说没什么用就是了),如果遇到一些玄学问题,可以关闭该功能,重启icc后生效。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_AvoidFullScreenHelper" xml:space="preserve">
|
<data name="Advanced_AvoidFullScreenHelper" xml:space="preserve">
|
||||||
<value>启用AvoidFullScreenHelper</value>
|
<value>启用AvoidFullScreenHelper</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_AvoidFullScreenHelperHint" xml:space="preserve">
|
<data name="Advanced_AvoidFullScreenHelperHint" xml:space="preserve">
|
||||||
<value># 避免画布全屏,应该可解决任务栏非置顶和Win11任务栏无法点击的问题,会导致左侧或顶部有AppBar(Dock栏软件)时导致浮动工具栏偏移,重启icc后生效。</value>
|
<value>避免画布全屏,应该可解决任务栏非置顶和Win11任务栏无法点击的问题,会导致左侧或顶部有AppBar(Dock栏软件)时导致浮动工具栏偏移,重启icc后生效。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EdgeGestureUtil" xml:space="preserve">
|
<data name="Advanced_EdgeGestureUtil" xml:space="preserve">
|
||||||
<value>启用EdgeGestureUtil</value>
|
<value>启用EdgeGestureUtil</value>
|
||||||
@@ -1310,7 +1394,7 @@
|
|||||||
<value>设置备份与还原</value>
|
<value>设置备份与还原</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Backup_Desc" xml:space="preserve">
|
<data name="Backup_Desc" xml:space="preserve">
|
||||||
<value># 可手动备份当前设置或还原之前的备份,自动更新前也会自动备份</value>
|
<value>可手动备份当前设置或还原之前的备份,自动更新前也会自动备份</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Backup_AutoBeforeUpdate" xml:space="preserve">
|
<data name="Backup_AutoBeforeUpdate" xml:space="preserve">
|
||||||
<value>自动更新前备份</value>
|
<value>自动更新前备份</value>
|
||||||
@@ -1349,7 +1433,7 @@
|
|||||||
<value>配置文件切换与热重载</value>
|
<value>配置文件切换与热重载</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ConfigProfiles_Desc" xml:space="preserve">
|
<data name="ConfigProfiles_Desc" xml:space="preserve">
|
||||||
<value># 选择配置文件即切换并热重载;另存为可将当前配置保存为新配置文件</value>
|
<value>选择配置文件即切换并热重载;另存为可将当前配置保存为新配置文件</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ConfigProfiles_Label" xml:space="preserve">
|
<data name="ConfigProfiles_Label" xml:space="preserve">
|
||||||
<value>配置文件:</value>
|
<value>配置文件:</value>
|
||||||
@@ -1418,7 +1502,7 @@
|
|||||||
<value>自动收纳忽略桌面EN5批注窗口</value>
|
<value>自动收纳忽略桌面EN5批注窗口</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoFold_OldZyBoard" xml:space="preserve">
|
<data name="AutoFold_OldZyBoard" xml:space="preserve">
|
||||||
<value>进入“中原旧白板”时自动收纳</value>
|
<value>中原旧白板</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Automation_AutoFoldInPPT" xml:space="preserve">
|
<data name="Automation_AutoFoldInPPT" xml:space="preserve">
|
||||||
<value>播放PPT时自动收纳</value>
|
<value>播放PPT时自动收纳</value>
|
||||||
@@ -1427,16 +1511,22 @@
|
|||||||
<value>软件退出后保持收纳模式</value>
|
<value>软件退出后保持收纳模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Automation_KeepFoldAfterExitHint" xml:space="preserve">
|
<data name="Automation_KeepFoldAfterExitHint" xml:space="preserve">
|
||||||
<value># 开启后,执行自动收纳的软件在软件退出后不退出收纳模式,保持收纳状态</value>
|
<value>开启后,执行自动收纳的软件在软件退出后不退出收纳模式,保持收纳状态</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_Title" xml:space="preserve">
|
<data name="AutoKill_Title" xml:space="preserve">
|
||||||
<value>自动查杀</value>
|
<value>自动查杀</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="AutoFold_Mode" xml:space="preserve">
|
||||||
|
<value>收纳模式</value>
|
||||||
|
</data>
|
||||||
|
<data name="AutoSave_Title" xml:space="preserve">
|
||||||
|
<value>自动保存</value>
|
||||||
|
</data>
|
||||||
<data name="AutoKill_PptTools" xml:space="preserve">
|
<data name="AutoKill_PptTools" xml:space="preserve">
|
||||||
<value>自动查杀希沃“PPT 小工具”</value>
|
<value>自动查杀希沃“PPT 小工具”</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_PptToolsHint" xml:space="preserve">
|
<data name="AutoKill_PptToolsHint" xml:space="preserve">
|
||||||
<value># 请注意,查杀 PPT 小工具会导致希沃课堂授课助手无法使用,直接进入希沃课堂授课助手安装目录删除 Office.dll 文件即可进入 PPT 放映时不会启动希沃的 PPT 工具栏。</value>
|
<value>请注意,查杀 PPT 小工具会导致希沃课堂授课助手无法使用,直接进入希沃课堂授课助手安装目录删除 Office.dll 文件即可进入 PPT 放映时不会启动希沃的 PPT 工具栏。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_EasiNote5" xml:space="preserve">
|
<data name="AutoKill_EasiNote5" xml:space="preserve">
|
||||||
<value>自动查杀 希沃白板5</value>
|
<value>自动查杀 希沃白板5</value>
|
||||||
@@ -1454,7 +1544,7 @@
|
|||||||
<value>自动查杀 希沃桌面2.0 桌面批注</value>
|
<value>自动查杀 希沃桌面2.0 桌面批注</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_SeewoDesktop2AnnoHint" xml:space="preserve">
|
<data name="AutoKill_SeewoDesktop2AnnoHint" xml:space="preserve">
|
||||||
<value># 由于希沃桌面2.0提供的桌面批注是64位应用程序,icc是32位程序无法访问,所以目前暂不做精准匹配,只匹配进程名称DesktopAnnotation,后面会考虑封装一套基于P/Invoke和WMI的综合进程识别方案。如果遇到同进程名的软件直接不开启该选项就行了,见谅!</value>
|
<value>由于希沃桌面2.0提供的桌面批注是64位应用程序,icc是32位程序无法访问,所以目前暂不做精准匹配,只匹配进程名称DesktopAnnotation,后面会考虑封装一套基于P/Invoke和WMI的综合进程识别方案。如果遇到同进程名的软件直接不开启该选项就行了,见谅!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoKill_SameAppTitle" xml:space="preserve">
|
<data name="AutoKill_SameAppTitle" xml:space="preserve">
|
||||||
<value>同类软件查杀</value>
|
<value>同类软件查杀</value>
|
||||||
@@ -1532,19 +1622,19 @@
|
|||||||
<value>60分钟</value>
|
<value>60分钟</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoSaveHint" xml:space="preserve">
|
<data name="Storage_AutoSaveHint" xml:space="preserve">
|
||||||
<value># 开启后将在设定时间间隔自动保存墨迹,仅在画布可见且有墨迹时才会保存</value>
|
<value>开启后将在设定时间间隔自动保存墨迹,仅在画布可见且有墨迹时才会保存</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveFullPageStrokes" xml:space="preserve">
|
<data name="Storage_SaveFullPageStrokes" xml:space="preserve">
|
||||||
<value>墨迹全页面保存</value>
|
<value>墨迹全页面保存</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveFullPageHint" xml:space="preserve">
|
<data name="Storage_SaveFullPageHint" xml:space="preserve">
|
||||||
<value># 开启后自动保存和手动保存墨迹时将以全屏模式保存。如果存在多个画布和墨迹,将把所有页面的墨迹按照每页为单位保存进一个压缩包中(注意,白板的墨迹只能在白板模式下打开,PPT的墨迹只能在PPT放映模式下打开)</value>
|
<value>开启后自动保存和手动保存墨迹时将以全屏模式保存。如果存在多个画布和墨迹,将把所有页面的墨迹按照每页为单位保存进一个压缩包中(注意,白板的墨迹只能在白板模式下打开,PPT的墨迹只能在PPT放映模式下打开)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveAsXml" xml:space="preserve">
|
<data name="Storage_SaveAsXml" xml:space="preserve">
|
||||||
<value>保存为XML格式</value>
|
<value>保存为XML格式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_SaveAsXmlHint" xml:space="preserve">
|
<data name="Storage_SaveAsXmlHint" xml:space="preserve">
|
||||||
<value># 开启后保存墨迹时将使用XML格式(ISF格式),便于查看和编辑墨迹数据</value>
|
<value>开启后保存墨迹时将使用XML格式(ISF格式),便于查看和编辑墨迹数据</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoScreenshotMinInk" xml:space="preserve">
|
<data name="Storage_AutoScreenshotMinInk" xml:space="preserve">
|
||||||
<value>自动截图最小墨迹量</value>
|
<value>自动截图最小墨迹量</value>
|
||||||
@@ -1562,13 +1652,13 @@
|
|||||||
<value>设置保存到 文档</value>
|
<value>设置保存到 文档</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_PathPermissionHint" xml:space="preserve">
|
<data name="Storage_PathPermissionHint" xml:space="preserve">
|
||||||
<value># 请注意检查保存文件夹是否有写入权限</value>
|
<value>请注意检查保存文件夹是否有写入权限</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoDeleteTitle" xml:space="preserve">
|
<data name="Storage_AutoDeleteTitle" xml:space="preserve">
|
||||||
<value>定期自动删除超过保存时间的墨迹、截图文件</value>
|
<value>定期自动删除超过保存时间的墨迹、截图文件</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_AutoDeleteHint" xml:space="preserve">
|
<data name="Storage_AutoDeleteHint" xml:space="preserve">
|
||||||
<value># 请注意如果开启自动删除功能,将会删除自动保存目录下所有后缀名为 .icstk 和 .png 的文件!</value>
|
<value>请注意如果开启自动删除功能,将会删除自动保存目录下所有后缀名为 .icstk 和 .png 的文件!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Storage_RetentionTitle" xml:space="preserve">
|
<data name="Storage_RetentionTitle" xml:space="preserve">
|
||||||
<value>保存时长</value>
|
<value>保存时长</value>
|
||||||
@@ -1586,19 +1676,19 @@
|
|||||||
<value>退出收纳模式时自动切换至批注模式</value>
|
<value>退出收纳模式时自动切换至批注模式</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_ExitToAnnotationHint" xml:space="preserve">
|
<data name="FoldMode_ExitToAnnotationHint" xml:space="preserve">
|
||||||
<value># 开启后,退出收纳模式时将自动切换至批注模式,便于快速批注</value>
|
<value>开启后,退出收纳模式时将自动切换至批注模式,便于快速批注</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterPPT" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterPPT" xml:space="preserve">
|
||||||
<value>退出PPT放映后自动收纳浮动栏</value>
|
<value>退出PPT放映后自动收纳浮动栏</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterPPTHint" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterPPTHint" xml:space="preserve">
|
||||||
<value># 开启后,退出PPT放映后会自动收纳浮动栏</value>
|
<value>开启后,退出PPT放映后会自动收纳浮动栏</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterWhiteboard" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterWhiteboard" xml:space="preserve">
|
||||||
<value>退出白板时自动收纳</value>
|
<value>退出白板时自动收纳</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FoldMode_AutoFoldAfterWhiteboardHint" xml:space="preserve">
|
<data name="FoldMode_AutoFoldAfterWhiteboardHint" xml:space="preserve">
|
||||||
<value># 开启后,退出白板模式时会自动收纳到侧边栏</value>
|
<value>开启后,退出白板模式时会自动收纳到侧边栏</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Random_Title" xml:space="preserve">
|
<data name="Random_Title" xml:space="preserve">
|
||||||
<value>随机点名</value>
|
<value>随机点名</value>
|
||||||
@@ -1667,7 +1757,7 @@
|
|||||||
<value>避免重复权重</value>
|
<value>避免重复权重</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Random_ML_Hint" xml:space="preserve">
|
<data name="Random_ML_Hint" xml:space="preserve">
|
||||||
<value># 机器学习算法会分析最近的点名历史,智能避免重复选择相同人员</value>
|
<value>机器学习算法会分析最近的点名历史,智能避免重复选择相同人员</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Timer_Title" xml:space="preserve">
|
<data name="Timer_Title" xml:space="preserve">
|
||||||
<value>计时器设置</value>
|
<value>计时器设置</value>
|
||||||
@@ -1744,6 +1834,12 @@
|
|||||||
<data name="About_PrivacyCheckboxSuffix" xml:space="preserve">
|
<data name="About_PrivacyCheckboxSuffix" xml:space="preserve">
|
||||||
<value> 中的隐私说明</value>
|
<value> 中的隐私说明</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="About_PrivacyAndTelemetry" xml:space="preserve">
|
||||||
|
<value>隐私与遥测</value>
|
||||||
|
</data>
|
||||||
|
<data name="About_PrivacyAgreement" xml:space="preserve">
|
||||||
|
<value>我已阅读并同意 privacy 中的隐私说明</value>
|
||||||
|
</data>
|
||||||
<data name="About_TelemetryLabel" xml:space="preserve">
|
<data name="About_TelemetryLabel" xml:space="preserve">
|
||||||
<value>匿名使用数据上传:</value>
|
<value>匿名使用数据上传:</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1757,7 +1853,7 @@
|
|||||||
<value>上传基础 + 可选数据</value>
|
<value>上传基础 + 可选数据</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="About_LicenseHint" xml:space="preserve">
|
<data name="About_LicenseHint" xml:space="preserve">
|
||||||
<value># 使用和分发本软件前,请您应当且务必知晓相关开源协议,且您应当知晓本软件基于 https://github.com/WXRIW/Ink-Canvas 修改而成。</value>
|
<value>使用和分发本软件前,请您应当且务必知晓相关开源协议,且您应当知晓本软件基于 https://github.com/WXRIW/Ink-Canvas 修改而成。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="About_LicenseTitle" xml:space="preserve">
|
<data name="About_LicenseTitle" xml:space="preserve">
|
||||||
<value>本软件和ICA,Ink Canvas均基于许可证开源</value>
|
<value>本软件和ICA,Ink Canvas均基于许可证开源</value>
|
||||||
@@ -1820,7 +1916,7 @@
|
|||||||
<value>手指模式 BoundsWidth</value>
|
<value>手指模式 BoundsWidth</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EdgeGestureUtilHint_Part1" xml:space="preserve">
|
<data name="Advanced_EdgeGestureUtilHint_Part1" xml:space="preserve">
|
||||||
<value># EdgeGestureUtil是icc最新引入的可以暂时阻止在使用触摸时触发边缘手势(如Windows10环境下,屏幕左边缘滑动进入任务视图,右边缘滑动弹出通知中心;Windows11环境下,底部向上滑动打开开始菜单),其原理是使用了</value>
|
<value>EdgeGestureUtil是icc最新引入的可以暂时阻止在使用触摸时触发边缘手势(如Windows10环境下,屏幕左边缘滑动进入任务视图,右边缘滑动弹出通知中心;Windows11环境下,底部向上滑动打开开始菜单),其原理是使用了 System.EdgeGesture.DisableTouchWhenFullscreen (当应用程序窗口处于活动状态且处于全屏模式 (或拥有的窗口) 处于活动状态时,防止边缘手势行为。)来实现的。如果有异常,请关闭该选项,该选项应该能够实时生效。(Win7和Win8用户该选项无法使用)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_EdgeGestureUtilHint_Part2" xml:space="preserve">
|
<data name="Advanced_EdgeGestureUtilHint_Part2" xml:space="preserve">
|
||||||
<value>(当应用程序窗口处于活动状态且处于全屏模式 (或拥有的窗口) 处于活动状态时,防止边缘手势行为。)来实现的。如果有异常,请关闭该选项,该选项应该能够实时生效。(Win7和Win8用户该选项无法使用)</value>
|
<value>(当应用程序窗口处于活动状态且处于全屏模式 (或拥有的窗口) 处于活动状态时,防止边缘手势行为。)来实现的。如果有异常,请关闭该选项,该选项应该能够实时生效。(Win7和Win8用户该选项无法使用)</value>
|
||||||
@@ -1829,19 +1925,19 @@
|
|||||||
<value>启用ForceFullScreen</value>
|
<value>启用ForceFullScreen</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ForceFullScreenHint" xml:space="preserve">
|
<data name="Advanced_ForceFullScreenHint" xml:space="preserve">
|
||||||
<value># 当检测到窗口大小变化时,自动使用Win32API将本窗口的大小设置为主显示器大小(设备像素大小),不需要可以关闭,实时生效。</value>
|
<value>当检测到窗口大小变化时,自动使用Win32API将本窗口的大小设置为主显示器大小(设备像素大小),不需要可以关闭,实时生效。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_DPIChangeDetection" xml:space="preserve">
|
<data name="Advanced_DPIChangeDetection" xml:space="preserve">
|
||||||
<value>启用DPIChangeDetection</value>
|
<value>启用DPIChangeDetection</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_DPIChangeDetectionHint" xml:space="preserve">
|
<data name="Advanced_DPIChangeDetectionHint" xml:space="preserve">
|
||||||
<value># 当检测到系统DPI变化时,会尝试检测FloatingBar是否在屏幕内显示,如果不在屏幕内显示将会尝试移动到屏幕内可见区域(DPI调大会触发,如果DPI调小是不会触发工具栏位置移动的,请您手动调整)。</value>
|
<value>当检测到系统DPI变化时,会尝试检测FloatingBar是否在屏幕内显示,如果不在屏幕内显示将会尝试移动到屏幕内可见区域(DPI调大会触发,如果DPI调小是不会触发工具栏位置移动的,请您手动调整)。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ResolutionChangeDetection" xml:space="preserve">
|
<data name="Advanced_ResolutionChangeDetection" xml:space="preserve">
|
||||||
<value>启用ResolutionChangeDetection</value>
|
<value>启用ResolutionChangeDetection</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Advanced_ResolutionChangeDetectionHint" xml:space="preserve">
|
<data name="Advanced_ResolutionChangeDetectionHint" xml:space="preserve">
|
||||||
<value># 当检测到系统分辨率变化时,会尝试检测FloatingBar是否在屏幕内显示,如果不在屏幕内显示将会尝试移动到屏幕内可见区域(分辨率调小可能会触发,如果在屏幕内不会自动调整位置,请手动挡)。</value>
|
<value>当检测到系统分辨率变化时,会尝试检测FloatingBar是否在屏幕内显示,如果不在屏幕内显示将会尝试移动到屏幕内可见区域(分辨率调小可能会触发,如果在屏幕内不会自动调整位置,请手动挡)。</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FloatingInterceptor_App_SeewoBoard3" xml:space="preserve">
|
<data name="FloatingInterceptor_App_SeewoBoard3" xml:space="preserve">
|
||||||
<value>希沃白板3</value>
|
<value>希沃白板3</value>
|
||||||
@@ -2131,6 +2227,9 @@
|
|||||||
<data name="FloatingBar_GestureButton" xml:space="preserve">
|
<data name="FloatingBar_GestureButton" xml:space="preserve">
|
||||||
<value>手势</value>
|
<value>手势</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FloatingBar_ExitButton" xml:space="preserve">
|
||||||
|
<value>退出</value>
|
||||||
|
</data>
|
||||||
<data name="FloatingBar_GesturePanelTitle" xml:space="preserve">
|
<data name="FloatingBar_GesturePanelTitle" xml:space="preserve">
|
||||||
<value>手势选项</value>
|
<value>手势选项</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2626,4 +2725,187 @@
|
|||||||
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve">
|
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve">
|
||||||
<value>开启后,点击白板工具栏「展台」将打开希沃视频展台(需已安装);关闭则使用内置展台。</value>
|
<value>开启后,点击白板工具栏「展台」将打开希沃视频展台(需已安装);关闭则使用内置展台。</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Storage_Title" xml:space="preserve">
|
||||||
|
<value>存储管理</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_TotalUsage" xml:space="preserve">
|
||||||
|
<value>ICC CE 总占用</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_DiskPercent" xml:space="preserve">
|
||||||
|
<value>占磁盘比例</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Calculating" xml:space="preserve">
|
||||||
|
<value>计算中…</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_CalculateFailed" xml:space="preserve">
|
||||||
|
<value>计算失败</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Refresh" xml:space="preserve">
|
||||||
|
<value>刷新</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_OpenAppFolder" xml:space="preserve">
|
||||||
|
<value>打开应用目录</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_CategoryDetails" xml:space="preserve">
|
||||||
|
<value>分类详情</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Clean" xml:space="preserve">
|
||||||
|
<value>清理</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Core" xml:space="preserve">
|
||||||
|
<value>核心文件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Logs" xml:space="preserve">
|
||||||
|
<value>日志</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Ink" xml:space="preserve">
|
||||||
|
<value>墨迹</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Backups" xml:space="preserve">
|
||||||
|
<value>备份</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Custom" xml:space="preserve">
|
||||||
|
<value>自定义</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Plugins" xml:space="preserve">
|
||||||
|
<value>插件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Update" xml:space="preserve">
|
||||||
|
<value>自动更新</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Legend_Other" xml:space="preserve">
|
||||||
|
<value>其他</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Core_Header" xml:space="preserve">
|
||||||
|
<value>核心文件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Core_Desc" xml:space="preserve">
|
||||||
|
<value>包含 .json 配置文件、.enc 使用统计、.exe 主程序、.dll 文件、.dat 数据文件等</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Core_NotCleanable" xml:space="preserve">
|
||||||
|
<value>核心文件不可清理,以免影响应用正常运行。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Logs_Header" xml:space="preserve">
|
||||||
|
<value>日志</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Logs_Desc" xml:space="preserve">
|
||||||
|
<value>位于 Logs / Crashs 目录下的 .txt 日志与崩溃报告,可清理</value>
|
||||||
|
</data>
|
||||||
|
<data name="Hotkey_NotSet" xml:space="preserve">
|
||||||
|
<value>未设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Security" xml:space="preserve">
|
||||||
|
<value>安全</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Security_Tooltip" xml:space="preserve">
|
||||||
|
<value>安全密码与进程保护</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Hotkey" xml:space="preserve">
|
||||||
|
<value>快捷键</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Nav_Hotkey_Tooltip" xml:space="preserve">
|
||||||
|
<value>快捷键设置</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Ink_Header" xml:space="preserve">
|
||||||
|
<value>墨迹</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Ink_Desc" xml:space="preserve">
|
||||||
|
<value>位于 Saves 目录下的 .icstk / .xml / 截图 .png 等墨迹文件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Backups_Header" xml:space="preserve">
|
||||||
|
<value>备份</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Backups_Desc" xml:space="preserve">
|
||||||
|
<value>位于 Backups 目录的设置 / 配置备份(.json / .zip)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Custom_Header" xml:space="preserve">
|
||||||
|
<value>自定义文件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Custom_Desc" xml:space="preserve">
|
||||||
|
<value>自定义图标与点名背景图(icons / backgrounds 等 .png)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Custom_Hint" xml:space="preserve">
|
||||||
|
<value>自定义文件由用户手动添加,不会自动清理,如需删除请前往对应管理界面。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Plugins_Header" xml:space="preserve">
|
||||||
|
<value>插件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Plugins_Desc" xml:space="preserve">
|
||||||
|
<value>位于 plugins 目录的 .iccpp 插件包</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Plugins_Hint" xml:space="preserve">
|
||||||
|
<value>插件请前往插件管理页面进行卸载。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Update_Header" xml:space="preserve">
|
||||||
|
<value>自动更新</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Update_Desc" xml:space="preserve">
|
||||||
|
<value>AutoUpdate 目录中的安装包与下载缓存,每次更新完成后会被清理</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Other_Header" xml:space="preserve">
|
||||||
|
<value>其他</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Other_Desc" xml:space="preserve">
|
||||||
|
<value>未归类至上述项目的其他文件</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Title" xml:space="preserve">
|
||||||
|
<value>清理确认</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Body" xml:space="preserve">
|
||||||
|
<value>将永久删除「{0}」分类下的所有文件,操作不可恢复。\n是否继续?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Second_Title" xml:space="preserve">
|
||||||
|
<value>二次确认</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_Confirm_Second_Body" xml:space="preserve">
|
||||||
|
<value>再次确认:确定要删除「{0}」吗?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_CleanFailed" xml:space="preserve">
|
||||||
|
<value>清理失败:{0}</value>
|
||||||
|
</data>
|
||||||
|
<data name="Storage_NavTooltip" xml:space="preserve">
|
||||||
|
<value>查看与清理 ICC CE 占用的存储空间</value>
|
||||||
|
</data>
|
||||||
|
<data name="Debug_ShowConsole_Header" xml:space="preserve">
|
||||||
|
<value>显示调试窗口</value>
|
||||||
|
</data>
|
||||||
|
<data name="Debug_ShowConsole_Desc" xml:space="preserve">
|
||||||
|
<value>显示一个独立的控制台窗口,用于实时输出日志(开启后立即生效;关闭设置中的“启用日志记录”将不会输出内容)。</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_UseCustomSaveFileName_Header" xml:space="preserve">
|
||||||
|
<value>使用自定义保存文件名</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_UseCustomSaveFileName_Desc" xml:space="preserve">
|
||||||
|
<value>开启后可选择保存文件的命名方式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileNameFormat" xml:space="preserve">
|
||||||
|
<value>文件名格式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_Timestamp" xml:space="preserve">
|
||||||
|
<value>时间戳(默认)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_Date" xml:space="preserve">
|
||||||
|
<value>日期</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateTime" xml:space="preserve">
|
||||||
|
<value>日期 + 时间</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateMode" xml:space="preserve">
|
||||||
|
<value>日期 + 模式</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateModePage" xml:space="preserve">
|
||||||
|
<value>日期 + 模式 + 页码</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_DateModePageCount" xml:space="preserve">
|
||||||
|
<value>日期 + 模式 + 页码 + 笔画数</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_Custom" xml:space="preserve">
|
||||||
|
<value>自定义...</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_CustomTemplate_Header" xml:space="preserve">
|
||||||
|
<value>自定义模板</value>
|
||||||
|
</data>
|
||||||
|
<data name="Automation_SaveFileName_CustomTemplate_Desc" xml:space="preserve">
|
||||||
|
<value>可用占位符:{date} {time} {datetime} {mode} {page} {count} {type}</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user