Compare commits
164 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab7905696d | |||
| b50049c822 | |||
| c1d584e3e7 | |||
| f776dac04d | |||
| 6e7a0e36f4 | |||
| 585b712c4c | |||
| 2ca7d2a337 | |||
| ea03e8e7c7 | |||
| 778894482f | |||
| 1035f7ef92 | |||
| 6e4f8bc982 | |||
| 36e47cec43 | |||
| 8b2bc352a6 | |||
| 057fb35d00 | |||
| bcbece5bd6 | |||
| 5aad206e0d | |||
| 74344c4782 | |||
| f280358f56 | |||
| 243201502b | |||
| 8c090218d1 | |||
| 6ca1c598d4 | |||
| 0c078ef863 | |||
| fea6576dfb | |||
| 24bc2cf138 | |||
| e6b707ea51 | |||
| 6183011952 | |||
| d80a59556e | |||
| ebbe018bae | |||
| bad05f77b5 | |||
| ea375a9ce6 | |||
| d3382d7856 | |||
| acf0c17d7a | |||
| 83568c8b33 | |||
| cc80529498 | |||
| 002970921d | |||
| ea74592e89 | |||
| fa38d3d664 | |||
| cffedb8cb7 | |||
| c77beb662e | |||
| 77dd83c2d6 | |||
| ceb99cea2b | |||
| 04a8224484 | |||
| 2f54e9d7b3 | |||
| 1165e5bbf2 | |||
| 24c6ca60a3 | |||
| 8f3b65c6d4 | |||
| 4835e3db50 | |||
| 7a30c93ff5 | |||
| 1fca17d557 | |||
| 069a478559 | |||
| c70d8e1c4e | |||
| 25dc6a00d3 | |||
| eb2f65e6e5 | |||
| 16c86cd02d | |||
| f5a657d5c3 | |||
| dfc23b4428 | |||
| b62055e705 | |||
| 3190211f9a | |||
| e138f600a1 | |||
| 2173b82fb4 | |||
| ceeb9bffba | |||
| ba70d67c89 | |||
| 74341cc162 | |||
| fc4a3a1194 | |||
| c33ac03255 | |||
| 66afe271c5 | |||
| 9279783fc3 | |||
| 3cf1ea438b | |||
| 277e46030d | |||
| af19ffb736 | |||
| 1f00962c47 | |||
| 742a62b6ff | |||
| 140e92eeda | |||
| 34c2dab82a | |||
| fa2366c373 | |||
| e2d898df14 | |||
| cacc67b11d | |||
| aef860a8cc | |||
| c890f95092 | |||
| 7c9a5f8265 | |||
| 7eafcb02cb | |||
| d4517d3c53 | |||
| 62a9a097aa | |||
| e7fa1caf6c | |||
| e347443a0a | |||
| b77e540863 | |||
| df4168e8fa | |||
| 576f86ce48 | |||
| c3335c0c24 | |||
| 26f79da2f9 | |||
| 11cb1815ad | |||
| a6fdb07e8b | |||
| ffdb3cd431 | |||
| 5f9b01ef04 | |||
| ca53624149 | |||
| 6484450ad3 | |||
| 7c97b683ea | |||
| 378b3e73f2 | |||
| a2f21357b6 | |||
| 79e03dc0d5 | |||
| e0f35450e1 | |||
| cc9f58fb6a | |||
| e8be85141b | |||
| 1bf57ea2f5 | |||
| 18b737b22b | |||
| 4ec3332808 | |||
| b2290ecf65 | |||
| 55c336daab | |||
| f2ed3f619c | |||
| 114f400bd9 | |||
| 506d1118b2 | |||
| 20441543f0 | |||
| 2dbc47ac3c | |||
| bbe99649cd | |||
| 04806d2004 | |||
| 759a635026 | |||
| e68bd9286f | |||
| a6b31e82c0 | |||
| bc3e37e541 | |||
| 1124bb6bfa | |||
| b19c19d73c | |||
| feb3fad4da | |||
| 3db745d684 | |||
| b4089ae62e | |||
| 97bdf78b08 | |||
| dc9fb26260 | |||
| 97b0972fdf | |||
| ea23145349 | |||
| f7013196f7 | |||
| 9d0baa0799 | |||
| 91c2fa4eee | |||
| 837311b2b7 | |||
| bc53eab669 | |||
| 55eb811193 | |||
| f7aa107a62 | |||
| 56a65af9a7 | |||
| de3f5d16a2 | |||
| d325a58f17 | |||
| fd137ae787 | |||
| bb1b893961 | |||
| 01c247ac29 | |||
| 1fa75640ee | |||
| 6e0191af6b | |||
| 9ac4070e4e | |||
| 2590ea5bdb | |||
| 290e031f77 | |||
| 9bc9af5eec | |||
| 8faffe9d4e | |||
| 95dfad64ce | |||
| c9dd98fa8d | |||
| 3ae99c82cc | |||
| 016abafee4 | |||
| dca606cf5f | |||
| e7c77b173d | |||
| 68b588ee7b | |||
| b4c753dfb4 | |||
| 037bcf6007 | |||
| b2e0e7b9e2 | |||
| 03c1399768 | |||
| 9b3a59f089 | |||
| e8f824a046 | |||
| 4772b9cce7 | |||
| 74b64bd21f | |||
| 3934270ed2 |
+8
-4
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://www.schemastore.org/all-contributors.json",
|
||||
"projectName": "community",
|
||||
"projectOwner": "InkCanvasForClass",
|
||||
"files": [
|
||||
@@ -6,7 +7,7 @@
|
||||
],
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
"contributorsPerLine": 5,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "CJKmkp",
|
||||
@@ -100,7 +101,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
|
||||
"profile": "https://github.com/Tayasui-rainnya",
|
||||
"contributions": [
|
||||
"design"
|
||||
"design",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -110,7 +112,8 @@
|
||||
"profile": "https://github.com/doudou0720",
|
||||
"contributions": [
|
||||
"code",
|
||||
"blog"
|
||||
"blog",
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -141,5 +144,6 @@
|
||||
"blog"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"repoType": "github"
|
||||
}
|
||||
|
||||
@@ -3,11 +3,6 @@ name: .NET Build & Package
|
||||
on:
|
||||
push:
|
||||
branches: [ main, beta ]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened ]
|
||||
branches: [ main, beta ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -21,6 +16,10 @@ jobs:
|
||||
build-and-package:
|
||||
name: Build & Package
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture: [AnyCPU, x86]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
@@ -28,7 +27,7 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
@@ -42,12 +41,12 @@ jobs:
|
||||
- name: Build the Solution
|
||||
env:
|
||||
DLASS_SENTRY_DSN: ${{ secrets.DLASS_SENTRY_DSN }}
|
||||
run: msbuild /p:platform="AnyCPU" /p:configuration="Debug" /p:GitFlow="$GITFLOW" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
run: msbuild /p:platform="${{ matrix.architecture }}" /p:configuration="Debug" /p:GitFlow="$GITFLOW" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas\bin\Debug\net472\InkCanvasForClass.exe"
|
||||
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
@@ -74,8 +73,8 @@ jobs:
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: InkCanvasForClass.CE.debug
|
||||
path: "Ink Canvas/bin/Debug/net472/*"
|
||||
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
||||
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net472/*"
|
||||
|
||||
- name: Create Summary
|
||||
if: always()
|
||||
@@ -90,15 +89,17 @@ jobs:
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ steps.create-archive.outputs.archive_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Download Artifact](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Nightly.link Download](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.zip) \([GhProxy Fastly Mirror](https://cdn.gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.zip) / [GhProxy Mirror](https://gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.zip)\)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Nightly.link Download](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) \([GhProxy Fastly Mirror](https://cdn.gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) / [GhProxy Mirror](https://gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip)\)" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## ❌ Build Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
name: PR Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
branches: [ main, beta ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-package:
|
||||
name: Build & Package
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture: [AnyCPU, x86]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
|
||||
- name: Restore Package
|
||||
run: dotnet restore "Ink Canvas.sln" --locked-mode
|
||||
|
||||
- name: Build the Solution
|
||||
run: msbuild /p:platform="${{ matrix.architecture }}" /p:configuration="Debug" /p:GitFlow="$GITFLOW" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "build_success=false" >> $env:GITHUB_OUTPUT
|
||||
|
||||
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
- name: Create Package (if build succeeded)
|
||||
id: create-archive
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
env:
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_RUN_NUMBER: ${{ github.run_number }}
|
||||
run: |
|
||||
$shortSha = $env:GITHUB_SHA.Substring(0, 7)
|
||||
$version = "debug-$shortSha-$env:GITHUB_RUN_NUMBER"
|
||||
echo "archive_name=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload Artifact (if build succeeded)
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: InkCanvasForClass.CE.debug.${{ matrix.architecture }}
|
||||
path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net472/*"
|
||||
|
||||
|
||||
- name: Create Summary
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
echo "# Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ steps.check-exe.outputs.build_success }}" = "true" ]; then
|
||||
echo "## ✅ Build Successful" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ steps.create-archive.outputs.archive_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Download Artifact](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[Nightly.link Download](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) \([GhProxy Fastly Mirror](https://cdn.gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip) / [GhProxy Mirror](https://gh-proxy.com/nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/InkCanvasForClass.CE.debug.${{ matrix.architecture }}.zip)\)" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## ❌ Build Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Check build logs for details." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -200,6 +200,10 @@ jobs:
|
||||
needs: prepare
|
||||
if: success()
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
architecture: [AnyCPU, x86]
|
||||
outputs:
|
||||
archive_name: ${{ steps.create_archive.outputs.archive_name }}
|
||||
zip_size: ${{ steps.calculate_size.outputs.zip_size }}
|
||||
@@ -212,7 +216,7 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
uses: microsoft/setup-msbuild@v3
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
@@ -227,12 +231,12 @@ jobs:
|
||||
env:
|
||||
DLASS_SENTRY_DSN: ${{ secrets.DLASS_SENTRY_DSN }}
|
||||
run: |
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
msbuild /p:platform="${{ matrix.architecture }}" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal -maxcpucount /p:RunAnalyzers=false
|
||||
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas/bin/Release/net472/InkCanvasForClass.exe"
|
||||
$exePath = "Ink Canvas\bin\Release\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
@@ -265,13 +269,22 @@ jobs:
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.zip"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$archiveName = "InkCanvasForClass.CE.$version$suffix.zip"
|
||||
|
||||
# 创建发布目录
|
||||
New-Item -ItemType Directory -Path "release" -Force
|
||||
|
||||
# 复制发布文件
|
||||
Copy-Item "Ink Canvas/bin/Release/net472/*" "release/" -Recurse -Force
|
||||
# 复制发布文件(使用架构特定的路径)
|
||||
Copy-Item "Ink Canvas\bin\Release\$architecture\net472\*" "release/" -Recurse -Force
|
||||
|
||||
# 创建压缩包
|
||||
Compress-Archive -Path "release/*" -DestinationPath $archiveName -Force
|
||||
@@ -282,6 +295,7 @@ jobs:
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 更新 ISS 文件中的版本信息
|
||||
$issPath = "build/InkCanvasForClass CE.iss"
|
||||
@@ -317,8 +331,17 @@ jobs:
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$setupFile = "InkCanvasForClass CE Setup.exe"
|
||||
$newSetupName = "InkCanvasForClass.CE.$version.Setup.exe"
|
||||
$newSetupName = "InkCanvasForClass.CE.$version$suffix.Setup.exe"
|
||||
|
||||
if (Test-Path $setupFile) {
|
||||
Rename-Item -Path $setupFile -NewName $newSetupName
|
||||
@@ -331,7 +354,16 @@ jobs:
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.zip"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$archiveName = "InkCanvasForClass.CE.$version$suffix.zip"
|
||||
|
||||
# 获取文件大小(字节)
|
||||
$fileSize = (Get-Item $archiveName).Length
|
||||
@@ -343,7 +375,16 @@ jobs:
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$installerName = "InkCanvasForClass.CE.$version.Setup.exe"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$installerName = "InkCanvasForClass.CE.$version$suffix.Setup.exe"
|
||||
|
||||
if (Test-Path $installerName) {
|
||||
# 获取文件大小(字节)
|
||||
@@ -354,14 +395,34 @@ jobs:
|
||||
Write-Error "Installer file not found: $installerName"
|
||||
}
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$architecture = "${{ matrix.architecture }}"
|
||||
|
||||
# 根据架构生成文件名后缀
|
||||
if ($architecture -eq "AnyCPU") {
|
||||
$suffix = "-x64"
|
||||
} else {
|
||||
$suffix = ""
|
||||
}
|
||||
|
||||
$zipFile = "InkCanvasForClass.CE.$version$suffix.zip"
|
||||
$setupFile = "InkCanvasForClass.CE.$version$suffix.Setup.exe"
|
||||
|
||||
echo "zip_file=$zipFile" >> $env:GITHUB_OUTPUT
|
||||
echo "setup_file=$setupFile" >> $env:GITHUB_OUTPUT
|
||||
id: get_file_names
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
name: build-files-${{ needs.prepare.outputs.version }}-${{ matrix.architecture }}
|
||||
path: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
${{ steps.get_file_names.outputs.zip_file }}
|
||||
${{ steps.get_file_names.outputs.setup_file }}
|
||||
|
||||
- name: Create Build Summary
|
||||
if: always()
|
||||
@@ -376,6 +437,7 @@ jobs:
|
||||
echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tag:** \`${{ needs.prepare.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Release Type:** ${{ needs.prepare.outputs.is_prerelease == 'true' && 'Pre-release' || 'Release' }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -393,6 +455,7 @@ jobs:
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tag:** \`${{ needs.prepare.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Architecture:** ${{ matrix.architecture }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Event:** ${{ github.event_name }} (${{ github.event.action || 'N/A' }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Commit:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Run:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -411,7 +474,8 @@ jobs:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
pattern: build-files-${{ needs.prepare.outputs.version }}-*
|
||||
merge-multiple: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -419,11 +483,13 @@ jobs:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Sign release artifacts with sigstore-python
|
||||
uses: sigstore/gh-action-sigstore-python@v3.2.0
|
||||
uses: sigstore/gh-action-sigstore-python@v3.3.0
|
||||
with:
|
||||
inputs: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
build-files-${{ needs.prepare.outputs.version }}-AnyCPU/*.zip
|
||||
build-files-${{ needs.prepare.outputs.version }}-AnyCPU/*.exe
|
||||
build-files-${{ needs.prepare.outputs.version }}-x86/*.zip
|
||||
build-files-${{ needs.prepare.outputs.version }}-x86/*.exe
|
||||
release-signing-artifacts: true
|
||||
upload-signing-artifacts: true
|
||||
env:
|
||||
@@ -434,8 +500,8 @@ jobs:
|
||||
with:
|
||||
name: signed-files-${{ needs.prepare.outputs.version }}
|
||||
path: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe.sigstore.json
|
||||
build-files-${{ needs.prepare.outputs.version }}-AnyCPU/*.sigstore.json
|
||||
build-files-${{ needs.prepare.outputs.version }}-x86/*.sigstore.json
|
||||
|
||||
release:
|
||||
needs: [prepare, build, sign]
|
||||
@@ -450,7 +516,8 @@ jobs:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
pattern: build-files-${{ needs.prepare.outputs.version }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download Signed Artifacts (if exists)
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -461,13 +528,13 @@ jobs:
|
||||
- name: Create enhanced changelog with file table
|
||||
id: enhanced_changelog
|
||||
run: |
|
||||
version="${{ needs.prepare.outputs.version }}"
|
||||
version='${{ needs.prepare.outputs.version }}'
|
||||
|
||||
# 读取git-cliff生成的changelog内容
|
||||
originalChangelog="${{ needs.prepare.outputs.changelog }}"
|
||||
originalChangelog='${{ needs.prepare.outputs.changelog }}'
|
||||
|
||||
# 检查是否为预发布版本,如果是则添加警告提示
|
||||
if [ "${{ needs.prepare.outputs.is_prerelease }}" = "true" ]; then
|
||||
if [ '${{ needs.prepare.outputs.is_prerelease }}' = "true" ]; then
|
||||
warningText=$'\n> [!CAUTION]\n'
|
||||
warningText+=$'> **注意:此版本为预览或测试版**\n'
|
||||
warningText+=$'> \n'
|
||||
@@ -480,25 +547,45 @@ jobs:
|
||||
fileTable+=$'| 文件名 | 大小 |\n'
|
||||
fileTable+=$'|--------|------|\n'
|
||||
|
||||
# ZIP 文件信息
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"
|
||||
fileTable+=$'.zip | ${{ needs.build.outputs.zip_size }} bytes |\n'
|
||||
# AnyCPU (x64) 架构文件
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.zip" ]; then
|
||||
zipSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.zip")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.zip | '"$zipSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
# 安装包文件信息
|
||||
installerSize="${{ needs.build.outputs.installer_size }}"
|
||||
if [ -n "$installerSize" ]; then
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.Setup.exe" ]; then
|
||||
installerSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.Setup.exe")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.Setup.exe | '"$installerSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.zip.sigstore.json" ]; then
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.zip.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.zip.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version-x64.Setup.exe.sigstore.json" ]; then
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version-x64.Setup.exe.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'-x64.Setup.exe.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
# x86 架构文件
|
||||
if [ -f "InkCanvasForClass.CE.$version.zip" ]; then
|
||||
zipSize=$(wc -c < "InkCanvasForClass.CE.$version.zip")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.zip | '"$zipSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
if [ -f "InkCanvasForClass.CE.$version.Setup.exe" ]; then
|
||||
installerSize=$(wc -c < "InkCanvasForClass.CE.$version.Setup.exe")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe | '"$installerSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
# 检查是否有签名文件
|
||||
if [ -f "InkCanvasForClass.CE.$version.zip.sigstore.json" ]; then
|
||||
sigstoreSize=$(stat -c%s "InkCanvasForClass.CE.$version.zip.sigstore.json")
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version.zip.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.zip.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
# 检查安装程序签名文件
|
||||
if [ -f "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json" ]; then
|
||||
sigstoreSize=$(stat -c%s "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json")
|
||||
sigstoreSize=$(wc -c < "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json")
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe.sigstore.json | '"$sigstoreSize"' bytes |\n'
|
||||
fi
|
||||
|
||||
@@ -531,6 +618,10 @@ jobs:
|
||||
draft: ${{ github.event.inputs.draft || false }}
|
||||
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.Setup.exe
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.zip.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.Setup.exe.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json
|
||||
@@ -541,7 +632,7 @@ jobs:
|
||||
|
||||
post_release:
|
||||
needs: [prepare, release]
|
||||
if: success()
|
||||
if: success() && github.event.inputs.draft != 'true'
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -550,7 +641,8 @@ jobs:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
pattern: build-files-${{ needs.prepare.outputs.version }}-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Get beta token
|
||||
uses: octo-sts/action@main
|
||||
@@ -580,18 +672,32 @@ jobs:
|
||||
cd $REPO_DIR
|
||||
IS_PRERELEASE="${{ needs.prepare.outputs.is_prerelease }}"
|
||||
VERSION="${{ needs.prepare.outputs.version }}"
|
||||
ZIP_FILE="$GITHUB_WORKSPACE/InkCanvasForClass.CE.$VERSION.zip"
|
||||
X64_ZIP_FILE="$GITHUB_WORKSPACE/InkCanvasForClass.CE.$VERSION-x64.zip"
|
||||
X86_ZIP_FILE="$GITHUB_WORKSPACE/InkCanvasForClass.CE.$VERSION.zip"
|
||||
|
||||
if [ "$IS_PRERELEASE" == "true" ]; then
|
||||
mkdir -p Beta
|
||||
cp "$ZIP_FILE" Beta/
|
||||
git add Beta/InkCanvasForClass.CE.$VERSION.zip
|
||||
if [ -f "$X64_ZIP_FILE" ]; then
|
||||
cp "$X64_ZIP_FILE" Beta/
|
||||
git add Beta/InkCanvasForClass.CE.$VERSION-x64.zip
|
||||
fi
|
||||
if [ -f "$X86_ZIP_FILE" ]; then
|
||||
cp "$X86_ZIP_FILE" Beta/
|
||||
git add Beta/InkCanvasForClass.CE.$VERSION.zip
|
||||
fi
|
||||
git commit -m "Add $VERSION PreRelease"
|
||||
else
|
||||
mkdir -p Release Beta
|
||||
cp "$ZIP_FILE" Release/
|
||||
cp "$ZIP_FILE" Beta/
|
||||
git add Release/InkCanvasForClass.CE.$VERSION.zip Beta/InkCanvasForClass.CE.$VERSION.zip
|
||||
if [ -f "$X64_ZIP_FILE" ]; then
|
||||
cp "$X64_ZIP_FILE" Release/
|
||||
cp "$X64_ZIP_FILE" Beta/
|
||||
git add Release/InkCanvasForClass.CE.$VERSION-x64.zip Beta/InkCanvasForClass.CE.$VERSION-x64.zip
|
||||
fi
|
||||
if [ -f "$X86_ZIP_FILE" ]; then
|
||||
cp "$X86_ZIP_FILE" Release/
|
||||
cp "$X86_ZIP_FILE" Beta/
|
||||
git add Release/InkCanvasForClass.CE.$VERSION.zip Beta/InkCanvasForClass.CE.$VERSION.zip
|
||||
fi
|
||||
git commit -m "Add $VERSION Release"
|
||||
fi
|
||||
git push origin main
|
||||
@@ -622,9 +728,11 @@ jobs:
|
||||
draft: false
|
||||
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}-x64.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
fail_on_unmatched_files: false
|
||||
repository: "InkCanvasForClass/community-beta"
|
||||
token: ${{ steps.octo-sts-beta.outputs.token }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.octo-sts-beta.outputs.token }}
|
||||
|
||||
|
||||
+10
-10
@@ -23,22 +23,22 @@ Global
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.Build.0 = Debug|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.Build.0 = Debug|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.ActiveCfg = Release|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
+34
-3
@@ -1,8 +1,9 @@
|
||||
<Application x:Class="Ink_Canvas.App"
|
||||
<Application x:Class="Ink_Canvas.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:props="clr-namespace:Ink_Canvas.Properties"
|
||||
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
>
|
||||
@@ -32,6 +33,36 @@
|
||||
</Image>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="TempShowMainWindowTrayIconMenuItem" Click="TempShowMainWindowTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_TempShowMainWindow}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#27272a" Geometry="F0 M24,24z M0,0z M5,6C4.73478,6 4.48043,6.10536 4.29289,6.29289 4.10536,6.48043 4,6.73478 4,7L4,17C4,17.2652 4.10536,17.5196 4.29289,17.7071 4.48043,17.8946 4.73478,18 5,18L19,18C19.2652,18 19.5196,17.8946 19.7071,17.7071 19.8946,17.5196 20,17.2652 20,17L20,7C20,6.73478 19.8946,6.48043 19.7071,6.29289 19.5196,6.10536 19.2652,6 19,6L5,6z M2.87868,4.87868C3.44129,4.31607,4.20435,4,5,4L19,4C19.7957,4 20.5587,4.31607 21.1213,4.87868 21.6839,5.44129 22,6.20435 22,7L22,17C22,17.7957 21.6839,18.5587 21.1213,19.1213 20.5587,19.6839 19.7957,20 19,20L5,20C4.20435,20 3.44129,19.6839 2.87868,19.1213 2.31607,18.5587 2,17.7956 2,17L2,7C2,6.20435,2.31607,5.44129,2.87868,4.87868z M5,8C5,7.44772,5.44772,7,6,7L6.01,7C6.56228,7 7.01,7.44772 7.01,8 7.01,8.55228 6.56228,9 6.01,9L6,9C5.44772,9,5,8.55228,5,8z M9,7C8.44772,7 8,7.44772 8,8 8,8.55228 8.44772,9 9,9L9.01,9C9.56228,9 10.01,8.55228 10.01,8 10.01,7.44772 9.56228,7 9.01,7L9,7z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="OpenSettingsTrayIconMenuItem" Click="OpenSettingsTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_OpenSettings}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2" Source="/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator Margin="0,3" />
|
||||
<MenuItem Name="DisableAllHotkeysMenuItem" Click="DisableAllHotkeysMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
@@ -232,7 +263,7 @@
|
||||
ToolTipText="InkCanvasForClass"
|
||||
ContextMenu="{StaticResource SysTrayMenu}"
|
||||
IconSource="/Resources/icc.ico"/>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources/>
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Properties;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using Sentry;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -18,10 +20,8 @@ using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using Application = System.Windows.Application;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using Ink_Canvas.Properties;
|
||||
using SplashScreen = Ink_Canvas.Windows.SplashScreen;
|
||||
using Timer = System.Threading.Timer;
|
||||
using Sentry;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -1061,6 +1061,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
_taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon");
|
||||
_taskbar.ForceCreate();
|
||||
|
||||
StartArgs = e.Args;
|
||||
|
||||
@@ -1088,7 +1089,7 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"启动完成心跳已记录");
|
||||
}
|
||||
LogHelper.WriteLogToFile($"启动时长: {(startupCompleteHeartbeat - appStartupStartTime).TotalSeconds:F2}秒");
|
||||
|
||||
|
||||
if (_isSplashScreenShown)
|
||||
{
|
||||
SetSplashMessage("完成初始化...");
|
||||
@@ -1223,8 +1224,8 @@ namespace Ink_Canvas
|
||||
|
||||
if (!isStartupComplete && appStartupStartTime != DateTime.MinValue)
|
||||
{
|
||||
DateTime startTime = _isSplashScreenShown && splashScreenStartTime != DateTime.MinValue
|
||||
? splashScreenStartTime
|
||||
DateTime startTime = _isSplashScreenShown && splashScreenStartTime != DateTime.MinValue
|
||||
? splashScreenStartTime
|
||||
: appStartupStartTime;
|
||||
TimeSpan elapsedSinceStart = DateTime.Now - startTime;
|
||||
if (elapsedSinceStart.TotalMinutes >= 2)
|
||||
@@ -1405,7 +1406,7 @@ namespace Ink_Canvas
|
||||
|
||||
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
|
||||
string currentDir = Path.GetDirectoryName(assemblyLocation);
|
||||
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
string dsnFilePath = Path.Combine(currentDir, "telemetry_dsn.txt");
|
||||
@@ -1417,7 +1418,7 @@ namespace Ink_Canvas
|
||||
return dsn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
DirectoryInfo parentDir = Directory.GetParent(currentDir);
|
||||
if (parentDir == null)
|
||||
{
|
||||
|
||||
@@ -43,5 +43,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.18.8")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.8")]
|
||||
[assembly: AssemblyVersion("1.7.18.9")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<UserControl x:Class="Ink_Canvas.Controls.CopyButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern">
|
||||
<Button x:Name="CopyButtonControl" Padding="6" Click="CopyButton_Click"
|
||||
ToolTipService.ToolTip="Copy">
|
||||
<Grid>
|
||||
<ui:FontIcon x:Name="FontIcon_Copy" FontSize="16"
|
||||
Icon="{x:Static ui:SegoeFluentIcons.Copy}" RenderTransformOrigin="0.5 0.5">
|
||||
<FrameworkElement.RenderTransform>
|
||||
<ScaleTransform x:Name="ScaleTransform_Copy"
|
||||
ScaleX="1" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
|
||||
</FrameworkElement.RenderTransform>
|
||||
</ui:FontIcon>
|
||||
<ui:FontIcon x:Name="FontIcon_Success" FontSize="16"
|
||||
Icon="{x:Static ui:SegoeFluentIcons.CheckMark}" RenderTransformOrigin="0.5 0.5">
|
||||
<FrameworkElement.RenderTransform>
|
||||
<ScaleTransform x:Name="ScaleTransform_Success"
|
||||
ScaleX="0" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
|
||||
</FrameworkElement.RenderTransform>
|
||||
</ui:FontIcon>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
public partial class CopyButton : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
nameof(Text), typeof(string), typeof(CopyButton), new PropertyMetadata(string.Empty));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler Click;
|
||||
|
||||
public CopyButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void CopyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Text))
|
||||
{
|
||||
Clipboard.SetText(Text);
|
||||
}
|
||||
|
||||
ShowSuccessAnimation();
|
||||
Click?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.ToString(), "Unable to Perform Copy", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ShowSuccessAnimation()
|
||||
{
|
||||
var copyScaleAnim = new DoubleAnimation
|
||||
{
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromMilliseconds(150)
|
||||
};
|
||||
ScaleTransform_Copy.BeginAnimation(ScaleTransform.ScaleXProperty, copyScaleAnim);
|
||||
|
||||
var copyOpacityAnim = new DoubleAnimation
|
||||
{
|
||||
To = 0,
|
||||
BeginTime = TimeSpan.FromMilliseconds(100),
|
||||
Duration = TimeSpan.FromMilliseconds(10)
|
||||
};
|
||||
FontIcon_Copy.BeginAnimation(UIElement.OpacityProperty, copyOpacityAnim);
|
||||
|
||||
await Task.Delay(150);
|
||||
var successScaleAnim = new DoubleAnimation
|
||||
{
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromMilliseconds(150),
|
||||
EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.2 }
|
||||
};
|
||||
ScaleTransform_Success.BeginAnimation(ScaleTransform.ScaleXProperty, successScaleAnim);
|
||||
|
||||
var successOpacityAnim = new DoubleAnimation
|
||||
{
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromMilliseconds(15)
|
||||
};
|
||||
FontIcon_Success.BeginAnimation(UIElement.OpacityProperty, successOpacityAnim);
|
||||
|
||||
await Task.Delay(1000);
|
||||
ShowCopyAnimation();
|
||||
}
|
||||
|
||||
private async void ShowCopyAnimation()
|
||||
{
|
||||
var successOpacityAnim = new DoubleAnimation
|
||||
{
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromMilliseconds(150)
|
||||
};
|
||||
FontIcon_Success.BeginAnimation(UIElement.OpacityProperty, successOpacityAnim);
|
||||
|
||||
await Task.Delay(150);
|
||||
var copyScaleAnim = new DoubleAnimation
|
||||
{
|
||||
To = 1,
|
||||
Duration = TimeSpan.Zero
|
||||
};
|
||||
ScaleTransform_Copy.BeginAnimation(ScaleTransform.ScaleXProperty, copyScaleAnim);
|
||||
|
||||
var copyOpacityAnim = new DoubleAnimation
|
||||
{
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromMilliseconds(150)
|
||||
};
|
||||
FontIcon_Copy.BeginAnimation(UIElement.OpacityProperty, copyOpacityAnim);
|
||||
|
||||
var successScaleAnim = new DoubleAnimation
|
||||
{
|
||||
To = 0,
|
||||
Duration = TimeSpan.Zero
|
||||
};
|
||||
ScaleTransform_Success.BeginAnimation(ScaleTransform.ScaleXProperty, successScaleAnim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 画布上的多页 PDF:仅显示当前页;翻页与页码由主窗口 PDF 侧栏控制(无 XAML 文件)。
|
||||
/// </summary>
|
||||
public class PdfEmbeddedView : UserControl
|
||||
{
|
||||
private readonly Image _pageImage;
|
||||
|
||||
private string _pdfPath;
|
||||
private uint _pageCount;
|
||||
private uint _currentIndex;
|
||||
private bool _compressLargePictures;
|
||||
private bool _isPagingBusy;
|
||||
private bool _layoutSizeCommitted;
|
||||
|
||||
/// <summary>页码或可翻页状态变化(用于更新侧栏)。</summary>
|
||||
public event EventHandler PageNavigationStateChanged;
|
||||
|
||||
public PdfEmbeddedView()
|
||||
{
|
||||
MinWidth = 80;
|
||||
MinHeight = 60;
|
||||
|
||||
var grid = new Grid { ClipToBounds = true };
|
||||
_pageImage = new Image
|
||||
{
|
||||
Stretch = Stretch.Uniform,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
grid.Children.Add(_pageImage);
|
||||
Content = grid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化并显示指定页;由 MainWindow 在 UI 线程创建后调用。
|
||||
/// </summary>
|
||||
/// <param name="initialPageIndex">从 0 开始的页码,超出范围时夹紧到合法区间。</param>
|
||||
public async Task InitializeAsync(string pdfFilePath, uint pageCount, bool compressLargePictures, uint initialPageIndex = 0)
|
||||
{
|
||||
_pdfPath = pdfFilePath ?? throw new ArgumentNullException(nameof(pdfFilePath));
|
||||
_pageCount = pageCount;
|
||||
_compressLargePictures = compressLargePictures;
|
||||
if (_pageCount == 0)
|
||||
_currentIndex = 0;
|
||||
else
|
||||
_currentIndex = initialPageIndex >= _pageCount ? _pageCount - 1 : initialPageIndex;
|
||||
|
||||
await ShowPageAsync(_currentIndex);
|
||||
}
|
||||
|
||||
public string PdfPath => _pdfPath;
|
||||
|
||||
public uint PageCount => _pageCount;
|
||||
|
||||
public uint CurrentPageIndex => _currentIndex;
|
||||
|
||||
public string PageLabelText => _pageCount == 0 ? "" : $"{_currentIndex + 1} / {_pageCount}";
|
||||
|
||||
public bool CanGoPrevious => !_isPagingBusy && _pageCount > 1 && _currentIndex > 0;
|
||||
|
||||
public bool CanGoNext => !_isPagingBusy && _pageCount > 1 && _currentIndex < _pageCount - 1;
|
||||
|
||||
public async Task GoToPreviousPageAsync()
|
||||
{
|
||||
await GoRelativeAsync(-1);
|
||||
}
|
||||
|
||||
public async Task GoToNextPageAsync()
|
||||
{
|
||||
await GoRelativeAsync(1);
|
||||
}
|
||||
|
||||
private void NotifyPageNavigationStateChanged()
|
||||
{
|
||||
PageNavigationStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task GoRelativeAsync(int delta)
|
||||
{
|
||||
if (_isPagingBusy || _pageCount <= 1)
|
||||
return;
|
||||
int next = (int)_currentIndex + delta;
|
||||
if (next < 0 || next >= _pageCount)
|
||||
return;
|
||||
_currentIndex = (uint)next;
|
||||
await ShowPageAsync(_currentIndex);
|
||||
}
|
||||
|
||||
private async Task ShowPageAsync(uint pageIndex)
|
||||
{
|
||||
_isPagingBusy = true;
|
||||
NotifyPageNavigationStateChanged();
|
||||
try
|
||||
{
|
||||
BitmapSource raw = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
|
||||
if (raw == null)
|
||||
return;
|
||||
|
||||
BitmapSource display = ApplyCompressionIfNeeded(raw);
|
||||
_pageImage.Source = display;
|
||||
if (!_layoutSizeCommitted)
|
||||
{
|
||||
bool callerSized = !double.IsNaN(Width) && Width > 0 && !double.IsNaN(Height) && Height > 0;
|
||||
if (!callerSized)
|
||||
{
|
||||
Width = display.PixelWidth;
|
||||
Height = display.PixelHeight;
|
||||
}
|
||||
|
||||
_layoutSizeCommitted = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPagingBusy = false;
|
||||
NotifyPageNavigationStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapSource ApplyCompressionIfNeeded(BitmapSource rendered)
|
||||
{
|
||||
int width = rendered.PixelWidth;
|
||||
int height = rendered.PixelHeight;
|
||||
if (_compressLargePictures && (width > 1920 || height > 1080))
|
||||
{
|
||||
double scaleX = 1920.0 / width;
|
||||
double scaleY = 1080.0 / height;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
return new TransformedBitmap(rendered, new ScaleTransform(scale, scale));
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,87 @@ namespace Ink_Canvas.Helpers
|
||||
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
private static string statusFilePath;
|
||||
|
||||
public static bool IsX64UpdatePackageSelected()
|
||||
{
|
||||
try
|
||||
{
|
||||
return MainWindow.Settings?.Startup?.UpdatePackageArchitecture == UpdatePackageArchitecture.X64;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeVersionForUpdate(string version)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(version)) return version;
|
||||
return version.Trim().TrimStart('v', 'V');
|
||||
}
|
||||
|
||||
public static string AppendX64SuffixBeforeZipExtension(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url) || !IsX64UpdatePackageSelected()) return url;
|
||||
int query = url.IndexOf('?');
|
||||
string pathPart = query >= 0 ? url.Substring(0, query) : url;
|
||||
string qs = query >= 0 ? url.Substring(query) : "";
|
||||
const string ext = ".zip";
|
||||
int idx = pathPart.LastIndexOf(ext, StringComparison.OrdinalIgnoreCase);
|
||||
if (idx < 0) return url;
|
||||
var basePart = pathPart.Substring(0, idx);
|
||||
if (basePart.EndsWith("-x64", StringComparison.OrdinalIgnoreCase)) return url;
|
||||
return basePart + "-x64" + ext + qs;
|
||||
}
|
||||
|
||||
public static string GetUpdateZipFileName(string version)
|
||||
{
|
||||
var v = NormalizeVersionForUpdate(version);
|
||||
return IsX64UpdatePackageSelected()
|
||||
? $"InkCanvasForClass.CE.{v}-x64.zip"
|
||||
: $"InkCanvasForClass.CE.{v}.zip";
|
||||
}
|
||||
|
||||
public static string GetUpdateDownloadStatusFilePath(string version)
|
||||
{
|
||||
var v = NormalizeVersionForUpdate(version);
|
||||
string name = IsX64UpdatePackageSelected()
|
||||
? $"DownloadV{v}_x64Status.txt"
|
||||
: $"DownloadV{v}Status.txt";
|
||||
return Path.Combine(updatesFolderPath, name);
|
||||
}
|
||||
|
||||
public static string GetLocalUpdateZipFilePath(string version)
|
||||
{
|
||||
return Path.Combine(updatesFolderPath, GetUpdateZipFileName(version));
|
||||
}
|
||||
|
||||
private static string PickBrowserDownloadUrlFromAssets(JToken assets)
|
||||
{
|
||||
if (assets == null || !assets.Any()) return null;
|
||||
bool wantX64 = IsX64UpdatePackageSelected();
|
||||
string anyZip = null;
|
||||
string x64Zip = null;
|
||||
string nonX64Zip = null;
|
||||
foreach (JToken a in assets)
|
||||
{
|
||||
string name = a["name"]?.ToString();
|
||||
string url = a["browser_download_url"]?.ToString();
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(url)) continue;
|
||||
if (!name.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
if (anyZip == null) anyZip = url;
|
||||
if (name.EndsWith("-x64.zip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (x64Zip == null) x64Zip = url;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nonX64Zip == null) nonX64Zip = url;
|
||||
}
|
||||
}
|
||||
if (wantX64)
|
||||
return x64Zip ?? anyZip;
|
||||
return nonX64Zip ?? anyZip;
|
||||
}
|
||||
|
||||
// 线路组结构体(包含版本、下载、日志地址)
|
||||
public class UpdateLineGroup
|
||||
@@ -317,6 +398,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (!string.IsNullOrEmpty(group.DownloadUrlFormat))
|
||||
{
|
||||
testUrl = group.DownloadUrlFormat.Replace("{0}", "test");
|
||||
testUrl = AppendX64SuffixBeforeZipExtension(testUrl);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -608,7 +690,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (version == targetVersion || version == $"v{targetVersion}" || version == $"V{targetVersion}")
|
||||
{
|
||||
string releaseNotes = release["body"]?.ToString();
|
||||
string downloadUrl = release["assets"]?.First?["browser_download_url"]?.ToString();
|
||||
string downloadUrl = PickBrowserDownloadUrlFromAssets(release["assets"]);
|
||||
|
||||
// 解析发布时间
|
||||
DateTime? releaseTime = null;
|
||||
@@ -649,7 +731,7 @@ namespace Ink_Canvas.Helpers
|
||||
var latestRelease = releases[0];
|
||||
string version = latestRelease["tag_name"]?.ToString();
|
||||
string releaseNotes = latestRelease["body"]?.ToString();
|
||||
string downloadUrl = latestRelease["assets"]?.First?["browser_download_url"]?.ToString();
|
||||
string downloadUrl = PickBrowserDownloadUrlFromAssets(latestRelease["assets"]);
|
||||
|
||||
DateTime? releaseTime = null;
|
||||
if (latestRelease["published_at"] != null && DateTime.TryParse(latestRelease["published_at"].ToString(), out DateTime parsedTime))
|
||||
@@ -675,7 +757,7 @@ namespace Ink_Canvas.Helpers
|
||||
var json = JObject.Parse(response);
|
||||
string version = json["tag_name"]?.ToString();
|
||||
string releaseNotes = json["body"]?.ToString();
|
||||
string downloadUrl = json["assets"]?.First?["browser_download_url"]?.ToString();
|
||||
string downloadUrl = PickBrowserDownloadUrlFromAssets(json["assets"]);
|
||||
|
||||
DateTime? releaseTime = null;
|
||||
if (json["published_at"] != null && DateTime.TryParse(json["published_at"].ToString(), out DateTime parsedTime))
|
||||
@@ -865,7 +947,8 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{version}Status.txt");
|
||||
version = NormalizeVersionForUpdate(version);
|
||||
statusFilePath = GetUpdateDownloadStatusFilePath(version);
|
||||
|
||||
if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true")
|
||||
{
|
||||
@@ -881,7 +964,7 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 创建更新目录: {updatesFolderPath}");
|
||||
}
|
||||
|
||||
string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip");
|
||||
string zipFilePath = GetLocalUpdateZipFilePath(version);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 目标文件路径: {zipFilePath}");
|
||||
|
||||
SaveDownloadStatus(false);
|
||||
@@ -899,6 +982,7 @@ namespace Ink_Canvas.Helpers
|
||||
foreach (var group in groups)
|
||||
{
|
||||
string url = string.Format(group.DownloadUrlFormat, version);
|
||||
url = AppendX64SuffixBeforeZipExtension(url);
|
||||
// 智教联盟需要先获取真实下载地址
|
||||
if (group.GroupName == "智教联盟")
|
||||
{
|
||||
@@ -1352,7 +1436,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法会临时将 App.IsUpdateInstalling 置为 true、尝试关闭进程保护(并在结束时还原)、在必要时备份当前设置、解压更新 ZIP、启动解压后的新可执行文件(以更新模式传递旧进程 ID、解压路径和目标路径等参数),并在新进程启动后关闭当前进程。方法会记录日志并在遇到错误时安全退出相应步骤,但不会抛出异常给调用方以外的上下文。</remarks>
|
||||
/// <param name="version">要安装的版本号,用于定位更新包文件名(例如 InkCanvasForClass.CE.{version}.zip)。</param>
|
||||
/// <param name="version">要安装的版本号,用于定位更新包文件名(与 <see cref="GetLocalUpdateZipFilePath"/> 一致;选择 x64 包时为 InkCanvasForClass.CE.{version}-x64.zip)。</param>
|
||||
/// <param name="isInSilence">指示是否以静默模式启动新版本(影响传递给新进程的参数和可能的用户提示)。</param>
|
||||
public static void InstallNewVersionApp(string version, bool isInSilence)
|
||||
{
|
||||
@@ -1403,7 +1487,7 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"更新前自动备份设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip");
|
||||
string zipFilePath = GetLocalUpdateZipFilePath(version);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 检查ZIP文件: {zipFilePath}");
|
||||
|
||||
if (!File.Exists(zipFilePath))
|
||||
@@ -2170,7 +2254,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
string version = item["tag_name"]?.ToString();
|
||||
string releaseNotes = item["body"]?.ToString();
|
||||
string downloadUrl = item["assets"]?.First?["browser_download_url"]?.ToString();
|
||||
string downloadUrl = PickBrowserDownloadUrlFromAssets(item["assets"]);
|
||||
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl))
|
||||
result.Add((version, downloadUrl, releaseNotes));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public abstract class BasePPTLinkManager : IPPTLinkManager
|
||||
{
|
||||
#region IPPTLinkManager 事件
|
||||
public event Action<object> SlideShowBegin;
|
||||
public event Action<object> SlideShowNextSlide;
|
||||
public event Action<object> SlideShowEnd;
|
||||
public event Action<object> PresentationOpen;
|
||||
public event Action<object> PresentationClose;
|
||||
public event Action<bool> PPTConnectionChanged;
|
||||
public event Action<bool> SlideShowStateChanged;
|
||||
#endregion
|
||||
|
||||
#region IPPTLinkManager 属性(默认实现)
|
||||
public virtual bool IsConnected => PPTApplication != null;
|
||||
|
||||
public virtual bool IsInSlideShow { get; protected set; }
|
||||
|
||||
public virtual bool IsSupportWPS { get; set; }
|
||||
|
||||
public virtual bool SkipAnimationsWhenNavigating { get; set; }
|
||||
|
||||
public virtual int SlidesCount { get; protected set; }
|
||||
|
||||
public abstract object PPTApplication { get; protected set; }
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
protected BasePPTLinkManager()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 生命周期管理(抽象方法,由子类实现)
|
||||
public abstract void StartMonitoring();
|
||||
|
||||
public abstract void StopMonitoring();
|
||||
|
||||
public virtual void ReloadConnection()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"{GetType().Name} 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
|
||||
StopMonitoring();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 放映控制(抽象方法,由子类实现)
|
||||
public abstract bool TryStartSlideShow();
|
||||
|
||||
public abstract bool TryEndSlideShow();
|
||||
#endregion
|
||||
|
||||
#region 导航控制(抽象方法,由子类实现)
|
||||
public abstract bool TryNavigateToSlide(int slideNumber);
|
||||
|
||||
public abstract bool TryNavigateNext();
|
||||
|
||||
public abstract bool TryNavigatePrevious();
|
||||
#endregion
|
||||
|
||||
#region 查询(抽象方法,由子类实现)
|
||||
public abstract int GetCurrentSlideNumber();
|
||||
|
||||
public abstract string GetPresentationName();
|
||||
|
||||
public abstract bool TryShowSlideNavigation();
|
||||
|
||||
public abstract object GetCurrentActivePresentation();
|
||||
#endregion
|
||||
|
||||
#region 事件触发辅助方法
|
||||
protected virtual void OnSlideShowBegin(object slideShowWindow)
|
||||
{
|
||||
SlideShowBegin?.Invoke(slideShowWindow);
|
||||
}
|
||||
|
||||
protected virtual void OnSlideShowNextSlide(object slideShowWindow)
|
||||
{
|
||||
SlideShowNextSlide?.Invoke(slideShowWindow);
|
||||
}
|
||||
|
||||
protected virtual void OnSlideShowEnd(object presentation)
|
||||
{
|
||||
SlideShowEnd?.Invoke(presentation);
|
||||
}
|
||||
|
||||
protected virtual void OnPresentationOpen(object presentation)
|
||||
{
|
||||
PresentationOpen?.Invoke(presentation);
|
||||
}
|
||||
|
||||
protected virtual void OnPresentationClose(object presentation)
|
||||
{
|
||||
PresentationClose?.Invoke(presentation);
|
||||
}
|
||||
|
||||
protected virtual void OnPPTConnectionChanged(bool isConnected)
|
||||
{
|
||||
PPTConnectionChanged?.Invoke(isConnected);
|
||||
}
|
||||
|
||||
protected virtual void OnSlideShowStateChanged(bool isInSlideShow)
|
||||
{
|
||||
SlideShowStateChanged?.Invoke(isInSlideShow);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public virtual void Dispose()
|
||||
{
|
||||
StopMonitoring();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class ComPPTLinkManager : IPPTLinkManager
|
||||
public class ComPPTLinkManager : BasePPTLinkManager
|
||||
{
|
||||
private readonly PPTManager _inner;
|
||||
|
||||
@@ -10,65 +10,47 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
_inner = new PPTManager();
|
||||
|
||||
_inner.SlideShowBegin += wn => SlideShowBegin?.Invoke(wn);
|
||||
_inner.SlideShowNextSlide += wn => SlideShowNextSlide?.Invoke(wn);
|
||||
_inner.SlideShowEnd += pres => SlideShowEnd?.Invoke(pres);
|
||||
_inner.PresentationOpen += pres => PresentationOpen?.Invoke(pres);
|
||||
_inner.PresentationClose += pres => PresentationClose?.Invoke(pres);
|
||||
_inner.PPTConnectionChanged += connected => PPTConnectionChanged?.Invoke(connected);
|
||||
_inner.SlideShowStateChanged += inSlideShow => SlideShowStateChanged?.Invoke(inSlideShow);
|
||||
_inner.SlideShowBegin += wn => OnSlideShowBegin(wn);
|
||||
_inner.SlideShowNextSlide += wn => OnSlideShowNextSlide(wn);
|
||||
_inner.SlideShowEnd += pres => OnSlideShowEnd(pres);
|
||||
_inner.PresentationOpen += pres => OnPresentationOpen(pres);
|
||||
_inner.PresentationClose += pres => OnPresentationClose(pres);
|
||||
_inner.PPTConnectionChanged += connected => OnPPTConnectionChanged(connected);
|
||||
_inner.SlideShowStateChanged += inSlideShow => OnSlideShowStateChanged(inSlideShow);
|
||||
}
|
||||
|
||||
#region IPPTLinkManager 事件
|
||||
public event Action<object> SlideShowBegin;
|
||||
public event Action<object> SlideShowNextSlide;
|
||||
public event Action<object> SlideShowEnd;
|
||||
public event Action<object> PresentationOpen;
|
||||
public event Action<object> PresentationClose;
|
||||
public event Action<bool> PPTConnectionChanged;
|
||||
public event Action<bool> SlideShowStateChanged;
|
||||
#endregion
|
||||
#region BasePPTLinkManager 属性重写
|
||||
public override bool IsConnected => _inner.IsConnected;
|
||||
|
||||
#region IPPTLinkManager 属性
|
||||
public bool IsConnected => _inner.IsConnected;
|
||||
public override bool IsInSlideShow => _inner.IsInSlideShow;
|
||||
|
||||
public bool IsInSlideShow => _inner.IsInSlideShow;
|
||||
|
||||
public bool IsSupportWPS
|
||||
public override bool IsSupportWPS
|
||||
{
|
||||
get => _inner.IsSupportWPS;
|
||||
set => _inner.IsSupportWPS = value;
|
||||
}
|
||||
|
||||
public bool SkipAnimationsWhenNavigating
|
||||
public override bool SkipAnimationsWhenNavigating
|
||||
{
|
||||
get => _inner.SkipAnimationsWhenNavigating;
|
||||
set => _inner.SkipAnimationsWhenNavigating = value;
|
||||
}
|
||||
|
||||
public int SlidesCount => _inner.SlidesCount;
|
||||
public override int SlidesCount => _inner.SlidesCount;
|
||||
|
||||
public object PPTApplication => _inner.PPTApplication;
|
||||
public override object PPTApplication
|
||||
{
|
||||
get => _inner.PPTApplication;
|
||||
protected set { }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 生命周期管理
|
||||
/// <summary>
|
||||
/// 开始监控本地 PowerPoint 的连接与运行状态,并在状态变化时触发相应事件。
|
||||
/// </summary>
|
||||
public void StartMonitoring() => _inner.StartMonitoring();
|
||||
public override void StartMonitoring() => _inner.StartMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 停止对 PowerPoint 的监控,断开当前连接并停止触发相关事件。
|
||||
/// </summary>
|
||||
public void StopMonitoring() => _inner.StopMonitoring();
|
||||
public override void StopMonitoring() => _inner.StopMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 强制断开当前 COM PPT 连接并停止对其监控,同时写入事件日志。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会向日志记录一条事件信息并调用内部管理器停止监控;该方法不会重新启动监控或重新初始化内部管理器实例。
|
||||
/// </remarks>
|
||||
public void ReloadConnection()
|
||||
public override void ReloadConnection()
|
||||
{
|
||||
LogHelper.WriteLogToFile("COM PPT 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
|
||||
_inner.StopMonitoring();
|
||||
@@ -76,31 +58,31 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region 放映控制
|
||||
public bool TryStartSlideShow() => _inner.TryStartSlideShow();
|
||||
public override bool TryStartSlideShow() => _inner.TryStartSlideShow();
|
||||
|
||||
public bool TryEndSlideShow() => _inner.TryEndSlideShow();
|
||||
public override bool TryEndSlideShow() => _inner.TryEndSlideShow();
|
||||
#endregion
|
||||
|
||||
#region 导航控制
|
||||
public bool TryNavigateToSlide(int slideNumber) => _inner.TryNavigateToSlide(slideNumber);
|
||||
public override bool TryNavigateToSlide(int slideNumber) => _inner.TryNavigateToSlide(slideNumber);
|
||||
|
||||
public bool TryNavigateNext() => _inner.TryNavigateNext();
|
||||
public override bool TryNavigateNext() => _inner.TryNavigateNext();
|
||||
|
||||
public bool TryNavigatePrevious() => _inner.TryNavigatePrevious();
|
||||
public override bool TryNavigatePrevious() => _inner.TryNavigatePrevious();
|
||||
#endregion
|
||||
|
||||
#region 查询
|
||||
public int GetCurrentSlideNumber() => _inner.GetCurrentSlideNumber();
|
||||
public override int GetCurrentSlideNumber() => _inner.GetCurrentSlideNumber();
|
||||
|
||||
public string GetPresentationName() => _inner.GetPresentationName();
|
||||
public override string GetPresentationName() => _inner.GetPresentationName();
|
||||
|
||||
public bool TryShowSlideNavigation() => _inner.TryShowSlideNavigation();
|
||||
public override bool TryShowSlideNavigation() => _inner.TryShowSlideNavigation();
|
||||
|
||||
public object GetCurrentActivePresentation() => _inner.GetCurrentActivePresentation();
|
||||
public override object GetCurrentActivePresentation() => _inner.GetCurrentActivePresentation();
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
public override void Dispose()
|
||||
{
|
||||
_inner?.Dispose();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class ExternalCallerLauncher
|
||||
{
|
||||
private static readonly string[] ClassIslandProtocols =
|
||||
{
|
||||
"classisland://plugins/IslandCaller/Simple/1",
|
||||
"classisland://plugins/IslandCaller/Simple",
|
||||
"classisland://plugins/IslandCaller/Run"
|
||||
};
|
||||
|
||||
public static string[] GetProtocolsByType(int externalCallerType)
|
||||
{
|
||||
switch (externalCallerType)
|
||||
{
|
||||
case 0:
|
||||
return ClassIslandProtocols;
|
||||
case 1:
|
||||
return new[]
|
||||
{
|
||||
"secrandom://roll_call/quick_draw",
|
||||
"secrandom://direct_extraction"
|
||||
};
|
||||
case 2:
|
||||
return new[] { "namepicker://" };
|
||||
default:
|
||||
return ClassIslandProtocols;
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] GetProtocolsByName(string externalCallerName)
|
||||
{
|
||||
switch (externalCallerName)
|
||||
{
|
||||
case "ClassIsland":
|
||||
return ClassIslandProtocols;
|
||||
case "SecRandom":
|
||||
return new[]
|
||||
{
|
||||
"secrandom://roll_call/quick_draw",
|
||||
"secrandom://direct_extraction"
|
||||
};
|
||||
case "NamePicker":
|
||||
return new[] { "namepicker://" };
|
||||
default:
|
||||
return ClassIslandProtocols;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryLaunch(IEnumerable<string> protocols, out Exception lastException)
|
||||
{
|
||||
lastException = null;
|
||||
if (protocols == null) return false;
|
||||
|
||||
foreach (var protocol in protocols)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(protocol)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = protocol,
|
||||
UseShellExecute = true
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,6 +285,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 功能快捷键
|
||||
RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.BtnDrawLine_Click(null, null));
|
||||
RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
|
||||
RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, () => _mainWindow.OpenQuickDrawFromHotkey());
|
||||
RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
|
||||
|
||||
// 退出快捷键
|
||||
@@ -1033,6 +1034,7 @@ namespace Ink_Canvas.Helpers
|
||||
new HotkeyConfigItem { Name = "Pen5", Key = Key.D5, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "DrawLine", Key = Key.L, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Screenshot", Key = Key.C, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "QuickDraw", Key = Key.K, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
|
||||
});
|
||||
@@ -1111,6 +1113,14 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// 旧版 HotkeyConfig.json 无「快抽」项时补注册默认组合,避免升级后无快捷键
|
||||
if (successCount > 0 && !IsHotkeyRegistered("QuickDraw"))
|
||||
{
|
||||
var quickDrawAction = GetActionByName("QuickDraw");
|
||||
if (quickDrawAction != null && RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, quickDrawAction))
|
||||
successCount++;
|
||||
}
|
||||
|
||||
if (successCount > 0)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
@@ -1221,6 +1231,8 @@ namespace Ink_Canvas.Helpers
|
||||
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
||||
case "Screenshot":
|
||||
return () => _mainWindow.SaveScreenShotToDesktop();
|
||||
case "QuickDraw":
|
||||
return () => _mainWindow.OpenQuickDrawFromHotkey();
|
||||
case "Hide":
|
||||
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
||||
case "Exit":
|
||||
|
||||
@@ -264,7 +264,6 @@ namespace Ink_Canvas.Helpers
|
||||
public void Enable()
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile("墨迹渐隐功能已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -273,7 +272,6 @@ namespace Ink_Canvas.Helpers
|
||||
public void Disable()
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile("墨迹渐隐功能已禁用");
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -894,4 +892,4 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public sealed class InkRecognitionManager
|
||||
{
|
||||
private static InkRecognitionManager _instance;
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
private ModernInkProcessor _modernProcessor;
|
||||
private ModernInkAnalyzer _modernAnalyzer;
|
||||
private bool _isModernSystemAvailable;
|
||||
private bool _isInitialized;
|
||||
|
||||
public static InkRecognitionManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_instance == null)
|
||||
_instance = new InkRecognitionManager();
|
||||
}
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private InkRecognitionManager()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tryModern = WinRtInkShapeRecognizer.IsApiAvailable && Environment.Is64BitProcess;
|
||||
|
||||
_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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("墨迹识别管理器初始化失败: " + ex.Message, LogHelper.LogType.Error);
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<InkShapeRecognitionResult> RecognizeShapeAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
||||
return Task.FromResult(InkShapeRecognitionResult.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode)
|
||||
&& WinRtInkShapeRecognizer.IsApiAvailable)
|
||||
{
|
||||
return RecognizeShapeWinRtOnDispatcherContext(strokes);
|
||||
}
|
||||
|
||||
var legacy = InkRecognizeHelper.RecognizeShapeIACore(strokes);
|
||||
return Task.FromResult(InkRecognizeHelper.FromIACoreOrEmpty(legacy));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("墨迹形状识别失败: " + ex.Message, LogHelper.LogType.Error);
|
||||
return Task.FromResult(InkShapeRecognitionResult.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<InkShapeRecognitionResult> RecognizeShapeWinRtOnDispatcherContext(
|
||||
StrokeCollection strokes)
|
||||
{
|
||||
return await WinRtInkShapeRecognizer.RecognizeShapeAsync(strokes).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <param name="applyHandwritingBeautify">为 true 且走 WinRT 时,将识别成功的词替换为手写风格字体的轮廓墨迹(见设置中的字体列表)。</param>
|
||||
/// <param name="handwritingFontFamilyList">逗号分隔的字体回退列表(WPF FontFamily);null 时使用内置默认。</param>
|
||||
public Task<StrokeCollection> CorrectInkAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode,
|
||||
bool applyHandwritingBeautify = false,
|
||||
string handwritingFontFamilyList = null)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] CorrectInkAsync 跳过:InkRecognitionManager 未初始化。", LogHelper.LogType.Info);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] CorrectInkAsync 跳过:无笔画。", LogHelper.LogType.Info);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var useWinRt = ShapeRecognitionRouter.ResolveUseWinRt(mode);
|
||||
if (!applyHandwritingBeautify)
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 跳过:未开启「识别转手写体字形」(applyHandwritingBeautify=false)。笔画数=" +
|
||||
strokes.Count,
|
||||
LogHelper.LogType.Info);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
if (!useWinRt)
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 跳过:当前引擎非 WinRT(模式=" + mode + ")。笔画数=" + strokes.Count,
|
||||
LogHelper.LogType.Info);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
if (!Environment.Is64BitProcess)
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 跳过:非 64 位进程,WinRT 手写体替换不可用。笔画数=" + strokes.Count,
|
||||
LogHelper.LogType.Info);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
if (_modernAnalyzer == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 跳过:ModernInkAnalyzer 未就绪(WinRT 初始化失败?)。笔画数=" +
|
||||
strokes.Count,
|
||||
LogHelper.LogType.Warning);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count +
|
||||
",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()),
|
||||
LogHelper.LogType.Info);
|
||||
return _modernAnalyzer.AnalyzeAndCorrectAsync(strokes, handwritingFontFamilyList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("墨迹纠正失败: " + ex.Message, LogHelper.LogType.Error);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WinRT 手写体识别(需 64 位进程、Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。
|
||||
/// </summary>
|
||||
public Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
if (!Environment.Is64BitProcess
|
||||
|| !ShapeRecognitionRouter.ResolveUseWinRt(mode)
|
||||
|| !WinRtHandwritingRecognizer.IsApiAvailable)
|
||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||
|
||||
return WinRtHandwritingRecognizer.RecognizeHandwritingAsync(strokes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("手写识别失败: " + ex.Message, LogHelper.LogType.Error);
|
||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValidShapeType(string shapeName)
|
||||
{
|
||||
return !string.IsNullOrEmpty(shapeName)
|
||||
&& (shapeName.Contains("Triangle") || shapeName.Contains("Circle")
|
||||
|| shapeName.Contains("Rectangle") || shapeName.Contains("Diamond")
|
||||
|| shapeName.Contains("Parallelogram") || shapeName.Contains("Square")
|
||||
|| shapeName.Contains("Ellipse") || shapeName.Contains("Line")
|
||||
|| shapeName.Contains("Arrow"));
|
||||
}
|
||||
|
||||
public string GetSystemInfo()
|
||||
{
|
||||
return _isModernSystemAvailable
|
||||
? $"现代化64位墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}"
|
||||
: $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_modernProcessor?.Dispose();
|
||||
_modernAnalyzer?.Dispose();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ModernInkProcessor : IDisposable
|
||||
{
|
||||
public ModernInkProcessor()
|
||||
{
|
||||
if (!WinRtInkShapeRecognizer.IsApiAvailable)
|
||||
throw new InvalidOperationException("WinRT 墨迹分析需要 Windows 10 及以上。");
|
||||
}
|
||||
|
||||
public Task<InkShapeRecognitionResult> RecognizeShapeAsync(StrokeCollection strokes)
|
||||
{
|
||||
return WinRtInkShapeRecognizer.RecognizeShapeAsync(strokes);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ModernInkAnalyzer : IDisposable
|
||||
{
|
||||
public Task<StrokeCollection> AnalyzeAndCorrectAsync(
|
||||
StrokeCollection strokes,
|
||||
string handwritingFontFamilyList)
|
||||
{
|
||||
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
|
||||
strokes,
|
||||
handwritingFontFamilyList);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
@@ -7,8 +8,8 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class InkRecognizeHelper
|
||||
{
|
||||
//识别形状
|
||||
public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes)
|
||||
/// <summary>IACore / IAWinFX 形状识别(典型用于 32 位进程)。</summary>
|
||||
public static ShapeRecognizeResult RecognizeShapeIACore(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return default;
|
||||
@@ -25,33 +26,151 @@ namespace Ink_Canvas.Helpers
|
||||
var alternates = analyzer.GetAlternates();
|
||||
if (alternates.Count > 0)
|
||||
{
|
||||
while ((!alternates[0].Strokes.Contains(strokes.Last()) ||
|
||||
!IsContainShapeType(((InkDrawingNode)alternates[0].AlternateNodes[0]).GetShapeName()))
|
||||
&& strokesCount >= 2)
|
||||
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;
|
||||
}
|
||||
analysisAlternate = alternates[0];
|
||||
}
|
||||
}
|
||||
|
||||
analyzer.Dispose();
|
||||
|
||||
if (analysisAlternate != null && analysisAlternate.AlternateNodes.Count > 0)
|
||||
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(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var legacy = RecognizeShapeIACore(strokes);
|
||||
return FromIACoreOrEmpty(legacy);
|
||||
}
|
||||
|
||||
/// <summary>与 CE 反编译版 <c>InkRecognitionManager.RecognizeShapeAsync</c> 对齐的统一入口。</summary>
|
||||
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return Task.FromResult(InkShapeRecognitionResult.Empty);
|
||||
|
||||
return InkRecognitionManager.Instance.RecognizeShapeAsync(strokes, mode);
|
||||
}
|
||||
|
||||
public static void WarmupShapeRecognition(ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = InkRecognitionManager.Instance;
|
||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||
{
|
||||
WinRtInkShapeRecognizer.Warmup();
|
||||
WinRtHandwritingRecognizer.Warmup();
|
||||
}
|
||||
else
|
||||
RecognizeShapeIACore(new StrokeCollection());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 预热失败不影响启动
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>WinRT 手写识别(64 位 + Windows 10+)。</summary>
|
||||
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode) =>
|
||||
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
|
||||
|
||||
/// <summary>WinRT 下将识别成功的词替换为手写体字形墨迹;是否应用由设置「WinRT 识别转手写体字形」控制。</summary>
|
||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode) =>
|
||||
InkRecognitionManager.Instance.CorrectInkAsync(
|
||||
strokes,
|
||||
mode,
|
||||
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
|
||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||
|
||||
/// <summary>显式指定是否应用手写体字形替换(忽略开关);字体仍从设置读取。</summary>
|
||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode,
|
||||
bool applyHandwritingBeautify) =>
|
||||
InkRecognitionManager.Instance.CorrectInkAsync(
|
||||
strokes,
|
||||
mode,
|
||||
applyHandwritingBeautify,
|
||||
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)
|
||||
{
|
||||
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using OSVersionExtension;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>墨迹形状识别后端:自动 / IACore / WinRT。</summary>
|
||||
public enum ShapeRecognitionEngineMode
|
||||
{
|
||||
Auto = 0,
|
||||
IACore = 1,
|
||||
WinRT = 2,
|
||||
}
|
||||
|
||||
public static class ShapeRecognitionRouter
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动模式:按当前进程位数选择——<c>64</c> 位进程用 WinRT,<c>32</c> 位进程(含 x86 目标在 WOW64 下运行)用 IACore。
|
||||
/// </summary>
|
||||
public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (mode == ShapeRecognitionEngineMode.WinRT) return true;
|
||||
if (mode == ShapeRecognitionEngineMode.IACore) return false;
|
||||
return Environment.Is64BitProcess;
|
||||
}
|
||||
|
||||
public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (!inkToShapeEnabled) return false;
|
||||
if (ResolveUseWinRt(mode))
|
||||
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||
return !Environment.Is64BitProcess;
|
||||
}
|
||||
|
||||
public static ShapeRecognitionEngineMode FromSettingsInt(int value)
|
||||
{
|
||||
if (value == (int)ShapeRecognitionEngineMode.IACore) return ShapeRecognitionEngineMode.IACore;
|
||||
if (value == (int)ShapeRecognitionEngineMode.WinRT) return ShapeRecognitionEngineMode.WinRT;
|
||||
return ShapeRecognitionEngineMode.Auto;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>与具体识别后端无关的形状识别结果,供统一纠正模块消费。</summary>
|
||||
public sealed class InkShapeRecognitionResult
|
||||
{
|
||||
public static readonly InkShapeRecognitionResult Empty = new InkShapeRecognitionResult();
|
||||
|
||||
private InkShapeRecognitionResult()
|
||||
{
|
||||
IsSuccess = false;
|
||||
ShapeName = string.Empty;
|
||||
Centroid = new Point();
|
||||
HotPoints = new PointCollection();
|
||||
StrokesToRemove = new StrokeCollection();
|
||||
}
|
||||
|
||||
public InkShapeRecognitionResult(
|
||||
string shapeName,
|
||||
Point centroid,
|
||||
PointCollection hotPoints,
|
||||
double shapeWidth,
|
||||
double shapeHeight,
|
||||
StrokeCollection strokesToRemove)
|
||||
{
|
||||
ShapeName = shapeName ?? string.Empty;
|
||||
Centroid = centroid;
|
||||
HotPoints = hotPoints ?? new PointCollection();
|
||||
ShapeWidth = shapeWidth;
|
||||
ShapeHeight = shapeHeight;
|
||||
StrokesToRemove = strokesToRemove ?? new StrokeCollection();
|
||||
IsSuccess = StrokesToRemove.Count > 0
|
||||
&& !string.IsNullOrEmpty(ShapeName)
|
||||
&& ShapeName != "Drawing";
|
||||
}
|
||||
|
||||
public bool IsSuccess { get; }
|
||||
public string ShapeName { get; }
|
||||
public Point Centroid { get; set; }
|
||||
public PointCollection HotPoints { get; }
|
||||
public double ShapeWidth { get; }
|
||||
public double ShapeHeight { get; }
|
||||
public StrokeCollection StrokesToRemove { get; }
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,14 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 绘制点段到新的DrawingVisual
|
||||
/// </summary>
|
||||
private static double PressureToVisualScale(float pressureFactor, bool ignorePressure)
|
||||
{
|
||||
if (ignorePressure)
|
||||
return 1.0;
|
||||
// 与 WPF 墨迹观感接近:0.5 为标称,压低变细、抬高变粗(预览此前固定 Pen 宽,等同忽略压感)
|
||||
return Math.Max(0.22, Math.Min(2.1, 0.42 + 1.16 * pressureFactor));
|
||||
}
|
||||
|
||||
private void DrawSegmentToNewVisual(int startIndex, int endIndex)
|
||||
{
|
||||
if (Stroke == null || Stroke.StylusPoints.Count == 0 || _visualCanvas == null) return;
|
||||
@@ -118,6 +126,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
var points = Stroke.StylusPoints;
|
||||
var drawingAttributes = Stroke.DrawingAttributes;
|
||||
var ignorePressure = drawingAttributes.IgnorePressure;
|
||||
|
||||
// 创建新的DrawingVisual用于绘制这个点段
|
||||
var segmentVisual = new DrawingVisual();
|
||||
@@ -128,11 +137,6 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
using (var dc = segmentVisual.RenderOpen())
|
||||
{
|
||||
var pen = new Pen(new SolidColorBrush(drawingAttributes.Color), drawingAttributes.Width);
|
||||
pen.StartLineCap = PenLineCap.Round;
|
||||
pen.EndLineCap = PenLineCap.Round;
|
||||
pen.LineJoin = PenLineJoin.Round;
|
||||
|
||||
// 绘制指定范围内的点段
|
||||
if (endIndex - startIndex >= 2)
|
||||
{
|
||||
@@ -141,6 +145,15 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
var startPoint = new Point(points[i].X, points[i].Y);
|
||||
var endPoint = new Point(points[i + 1].X, points[i + 1].Y);
|
||||
var s0 = PressureToVisualScale(points[i].PressureFactor, ignorePressure);
|
||||
var s1 = PressureToVisualScale(points[i + 1].PressureFactor, ignorePressure);
|
||||
var thickness = Math.Max(0.35, (drawingAttributes.Width * s0 + drawingAttributes.Width * s1) / 2.0);
|
||||
var pen = new Pen(new SolidColorBrush(drawingAttributes.Color), thickness)
|
||||
{
|
||||
StartLineCap = PenLineCap.Round,
|
||||
EndLineCap = PenLineCap.Round,
|
||||
LineJoin = PenLineJoin.Round
|
||||
};
|
||||
dc.DrawLine(pen, startPoint, endPoint);
|
||||
}
|
||||
}
|
||||
@@ -149,8 +162,9 @@ namespace Ink_Canvas.Helpers
|
||||
// 只有一个点,绘制圆点
|
||||
var brush = new SolidColorBrush(drawingAttributes.Color);
|
||||
var point = points[startIndex];
|
||||
var s = PressureToVisualScale(point.PressureFactor, ignorePressure);
|
||||
dc.DrawEllipse(brush, null, new Point(point.X, point.Y),
|
||||
drawingAttributes.Width / 2, drawingAttributes.Height / 2);
|
||||
drawingAttributes.Width * s / 2, drawingAttributes.Height * s / 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace Ink_Canvas.Helpers
|
||||
private readonly object _lockObject = new object();
|
||||
private bool _disposed;
|
||||
private static bool IsPptBusyHResult(uint hr) => hr == 0x80010001 || hr == 0x8001010A;
|
||||
private static bool IsNoActivePresentationHResult(uint hr) => hr == 0x80048240;
|
||||
private const int ConnectionCheckIntervalTicks = 3;
|
||||
private const int SlideShowCheckIntervalTicks = 2;
|
||||
private const int WpsCheckIntervalTicks = 6;
|
||||
@@ -150,13 +151,13 @@ namespace Ink_Canvas.Helpers
|
||||
private void CheckAndConnectToPPT()
|
||||
{
|
||||
if (_isModuleUnloading) return;
|
||||
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isModuleUnloading) return;
|
||||
|
||||
|
||||
// 尝试连接到PowerPoint
|
||||
var pptApp = TryConnectToPowerPoint();
|
||||
if (pptApp == null && IsSupportWPS)
|
||||
@@ -328,7 +329,7 @@ namespace Ink_Canvas.Helpers
|
||||
_cachedIsConnected = true;
|
||||
|
||||
// 在主线程中注册事件,确保COM对象在正确的线程中
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -343,9 +344,8 @@ namespace Ink_Canvas.Helpers
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PPT事件注册失败: {ex}", LogHelper.LogType.Error);
|
||||
throw; // 重新抛出异常,让外层处理
|
||||
}
|
||||
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(2));
|
||||
}), DispatcherPriority.Normal);
|
||||
|
||||
// 获取当前演示文稿信息
|
||||
UpdateCurrentPresentationInfo();
|
||||
@@ -386,15 +386,19 @@ namespace Ink_Canvas.Helpers
|
||||
if (Marshal.IsComObject(PPTApplication))
|
||||
{
|
||||
// 尝试在主线程中取消事件注册
|
||||
Application.Current?.Dispatcher?.Invoke(() =>
|
||||
Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
PPTApplication.PresentationOpen -= OnPresentationOpen;
|
||||
PPTApplication.PresentationClose -= OnPresentationClose;
|
||||
PPTApplication.SlideShowBegin -= OnSlideShowBegin;
|
||||
PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
|
||||
PPTApplication.SlideShowEnd -= OnSlideShowEnd;
|
||||
// 再次检查PPTApplication是否为null,因为可能在异步操作期间被修改
|
||||
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
|
||||
{
|
||||
PPTApplication.PresentationOpen -= OnPresentationOpen;
|
||||
PPTApplication.PresentationClose -= OnPresentationClose;
|
||||
PPTApplication.SlideShowBegin -= OnSlideShowBegin;
|
||||
PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
|
||||
PPTApplication.SlideShowEnd -= OnSlideShowEnd;
|
||||
}
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
@@ -410,7 +414,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1));
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -421,7 +425,7 @@ namespace Ink_Canvas.Helpers
|
||||
SafeReleaseComObject(CurrentSlide, "CurrentSlide");
|
||||
SafeReleaseComObject(CurrentSlides, "CurrentSlides");
|
||||
SafeReleaseComObject(CurrentPresentation, "CurrentPresentation");
|
||||
|
||||
|
||||
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
|
||||
{
|
||||
try
|
||||
@@ -474,13 +478,13 @@ namespace Ink_Canvas.Helpers
|
||||
_isModuleUnloading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
|
||||
|
||||
Thread.Sleep(1000);
|
||||
|
||||
|
||||
_isModuleUnloading = false;
|
||||
|
||||
try
|
||||
@@ -494,7 +498,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPT联动模块重载时计时器已释放,跳过重启", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile("PPT联动模块已重新加载", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -555,7 +559,7 @@ namespace Ink_Canvas.Helpers
|
||||
object view = null;
|
||||
object selection = null;
|
||||
object slideRange = null;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
|
||||
@@ -605,7 +609,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (view != null)
|
||||
{
|
||||
dynamic viewObj = view;
|
||||
CurrentSlide = viewObj.Slide as Slide;
|
||||
CurrentSlide = viewObj.Slide as Slide;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -632,7 +636,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (CurrentSlide == null && SlidesCount > 0)
|
||||
{
|
||||
CurrentSlide = CurrentSlides[1];
|
||||
@@ -642,7 +646,7 @@ namespace Ink_Canvas.Helpers
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr != 0x8001010E && hr != 0x80004005)
|
||||
if (hr != 0x8001010E && hr != 0x80004005 && !IsNoActivePresentationHResult(hr))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取当前幻灯片失败: {comEx.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
@@ -664,7 +668,7 @@ namespace Ink_Canvas.Helpers
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5 || hr == 0x80048240)
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5 || IsNoActivePresentationHResult(hr))
|
||||
{
|
||||
CurrentPresentation = null;
|
||||
CurrentSlides = null;
|
||||
@@ -1076,7 +1080,7 @@ namespace Ink_Canvas.Helpers
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x80048240 || IsPptBusyHResult(hr))
|
||||
if (IsNoActivePresentationHResult(hr) || IsPptBusyHResult(hr))
|
||||
return null;
|
||||
throw;
|
||||
}
|
||||
@@ -1094,7 +1098,7 @@ namespace Ink_Canvas.Helpers
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (!IsPptBusyHResult(hr) && (hr == 0x8001010E || hr == 0x80004005))
|
||||
DisconnectFromPPT();
|
||||
if (!IsPptBusyHResult(hr) && hr != 0x80048240)
|
||||
if (!IsPptBusyHResult(hr) && !IsNoActivePresentationHResult(hr))
|
||||
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning);
|
||||
return CurrentPresentation;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Windows.Data.Pdf;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 Windows.Data.Pdf(WinRT)将 PDF 页渲染为 WPF 可用的位图。
|
||||
/// </summary>
|
||||
internal static class PdfWinRtHelper
|
||||
{
|
||||
public static async Task<uint> GetPageCountAsync(string pdfPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
|
||||
return 0;
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
|
||||
var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
|
||||
if (doc.IsPasswordProtected)
|
||||
return 0;
|
||||
return doc.PageCount;
|
||||
}
|
||||
|
||||
public static async Task<BitmapSource> RenderPageToBitmapSourceAsync(string pdfPath, uint pageIndex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
|
||||
return null;
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
|
||||
var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
|
||||
if (doc.IsPasswordProtected)
|
||||
return null;
|
||||
if (pageIndex >= doc.PageCount)
|
||||
return null;
|
||||
|
||||
var page = doc.GetPage(pageIndex);
|
||||
try
|
||||
{
|
||||
using (var ras = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await page.RenderToStreamAsync(ras).AsTask();
|
||||
ras.Seek(0);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
using (var netStream = ras.AsStreamForRead())
|
||||
netStream.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
try
|
||||
{
|
||||
return await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var bi = new BitmapImage();
|
||||
bi.BeginInit();
|
||||
bi.StreamSource = ms;
|
||||
bi.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bi.EndInit();
|
||||
bi.Freeze();
|
||||
return (BitmapSource)bi;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
ms.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
(page as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
using iNKORE.UI.WPF.Controls;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮控件
|
||||
/// </summary>
|
||||
public class LauncherButton
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 实际按钮控件
|
||||
/// </summary>
|
||||
private readonly SimpleStackPanel _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 获取按钮UI元素
|
||||
/// </summary>
|
||||
public UIElement Element => _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherButton(SuperLauncherPlugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
_plugin = plugin;
|
||||
LogHelper.WriteLogToFile("开始创建启动台按钮");
|
||||
|
||||
// 创建SimpleStackPanel
|
||||
_panel = new SimpleStackPanel
|
||||
{
|
||||
Name = "Launcher_Icon",
|
||||
Orientation = Orientation.Vertical,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = 28,
|
||||
Margin = new Thickness(0, -2, 0, 0),
|
||||
Background = Brushes.Transparent
|
||||
};
|
||||
|
||||
LogHelper.WriteLogToFile("创建SimpleStackPanel完成");
|
||||
|
||||
// 添加图标
|
||||
var image = CreateIconImage();
|
||||
_panel.Children.Add(image);
|
||||
|
||||
// 添加文本
|
||||
TextBlock textBlock = new TextBlock
|
||||
{
|
||||
Text = "启动台",
|
||||
Foreground = Brushes.Black,
|
||||
FontSize = 8,
|
||||
Margin = new Thickness(0, 1, 0, 0),
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
_panel.Children.Add(textBlock);
|
||||
|
||||
// 设置鼠标事件
|
||||
_panel.MouseDown += Panel_MouseDown;
|
||||
_panel.MouseUp += Panel_MouseUp;
|
||||
_panel.MouseLeave += Panel_MouseLeave;
|
||||
|
||||
// 右键菜单支持
|
||||
_panel.ContextMenu = CreateContextMenu();
|
||||
|
||||
// 设置工具提示
|
||||
_panel.ToolTip = "启动台";
|
||||
|
||||
LogHelper.WriteLogToFile("启动台按钮创建完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建右键菜单
|
||||
/// </summary>
|
||||
private ContextMenu CreateContextMenu()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建菜单
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
// 创建位置切换菜单项
|
||||
MenuItem positionMenuItem = new MenuItem();
|
||||
positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
"移至右侧" : "移至左侧";
|
||||
positionMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 切换位置
|
||||
_plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
LauncherButtonPosition.Right : LauncherButtonPosition.Left;
|
||||
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}");
|
||||
};
|
||||
menu.Items.Add(positionMenuItem);
|
||||
|
||||
// 添加设置菜单项
|
||||
MenuItem settingsMenuItem = new MenuItem();
|
||||
settingsMenuItem.Header = "打开设置";
|
||||
settingsMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 打开插件设置窗口
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用反射调用主窗口的ShowPluginSettings方法
|
||||
var method = mainWindow.GetType().GetMethod("ShowPluginSettings");
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(mainWindow, null);
|
||||
LogHelper.WriteLogToFile("已打开插件设置窗口");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
menu.Items.Add(settingsMenuItem);
|
||||
|
||||
return menu;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实际的UI元素
|
||||
/// </summary>
|
||||
[Obsolete("使用Element属性代替")]
|
||||
public UIElement GetUIElement()
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建图标图像
|
||||
/// </summary>
|
||||
private Image CreateIconImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建图像
|
||||
Image image = new Image
|
||||
{
|
||||
Height = 17,
|
||||
Margin = new Thickness(0, 3, 0, 0)
|
||||
};
|
||||
|
||||
// 设置位图缩放模式
|
||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
||||
|
||||
// 创建绘图图像
|
||||
DrawingImage drawingImage = new DrawingImage();
|
||||
DrawingGroup drawingGroup = new DrawingGroup();
|
||||
drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z");
|
||||
|
||||
// 使用提供的应用网格图标
|
||||
GeometryDrawing geometryDrawing = new GeometryDrawing
|
||||
{
|
||||
Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)),
|
||||
Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z")
|
||||
};
|
||||
|
||||
drawingGroup.Children.Add(geometryDrawing);
|
||||
|
||||
// 设置图像源
|
||||
drawingImage.Drawing = drawingGroup;
|
||||
image.Source = drawingImage;
|
||||
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
|
||||
// 返回一个空图像
|
||||
return new Image();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下事件
|
||||
/// </summary>
|
||||
private void Panel_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 提供反馈
|
||||
_panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标按下");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标抬起事件
|
||||
/// </summary>
|
||||
private void Panel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有左键点击才显示启动台窗口
|
||||
if (e.ChangedButton != MouseButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口");
|
||||
|
||||
// 获取按钮在屏幕上的位置
|
||||
Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0));
|
||||
|
||||
// 显示启动台窗口
|
||||
_plugin.ShowLauncherWindow(buttonPosition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标离开事件
|
||||
/// </summary>
|
||||
private void Panel_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public enum LauncherButtonPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// 左侧
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// 右侧
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public class LauncherConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public List<LauncherItem> Items { get; set; } = new List<LauncherItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用项
|
||||
/// </summary>
|
||||
public class LauncherItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可见
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 在启动台中的位置(0-39)
|
||||
/// </summary>
|
||||
public int Position { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已固定位置
|
||||
/// </summary>
|
||||
public bool IsPositionFixed { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 图标缓存
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
private ImageSource _iconCache;
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用程序图标
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ImageSource Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconCache != null)
|
||||
{
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(Path))
|
||||
{
|
||||
// 从文件中获取图标
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从注册表中获取文件类型关联图标
|
||||
string extension = System.IO.Path.GetExtension(Path);
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(fileType))
|
||||
{
|
||||
string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
string[] parts = iconPath.Split(',');
|
||||
string iconFile = parts[0].Trim('"');
|
||||
int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0;
|
||||
|
||||
if (File.Exists(iconFile))
|
||||
{
|
||||
Icon icon = IconExtractor.Extract(iconFile, iconIndex, true);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 返回默认图标
|
||||
return GetDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认图标
|
||||
/// </summary>
|
||||
private ImageSource GetDefaultIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 对于资源管理器,使用特定图标
|
||||
if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 直接从C:\Windows\explorer.exe获取图标
|
||||
string explorerPath = @"C:\Windows\explorer.exe";
|
||||
if (File.Exists(explorerPath))
|
||||
{
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(explorerPath);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取资源管理器图标时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
// 如果获取Windows图标失败,回退到默认图标
|
||||
}
|
||||
|
||||
// 回退到备用图标
|
||||
string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png");
|
||||
if (File.Exists(explorerIconPath))
|
||||
{
|
||||
Uri uri = new Uri(explorerIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回一个简单的默认图标
|
||||
string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png");
|
||||
if (File.Exists(iconPath))
|
||||
{
|
||||
Uri uri = new Uri(iconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
// 如果还是没有找到,尝试使用应用程序图标
|
||||
string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png");
|
||||
if (File.Exists(appIconPath))
|
||||
{
|
||||
Uri uri = new Uri(appIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动应用程序
|
||||
/// </summary>
|
||||
public void Launch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(Path) && !Path.Contains(":\\"))
|
||||
{
|
||||
// 可能是系统命令,如explorer.exe
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用Process.Start启动应用程序
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已启动应用程序: {Path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图标提取工具类
|
||||
/// </summary>
|
||||
public static class IconExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// 从文件中提取图标
|
||||
/// </summary>
|
||||
/// <param name="file">文件路径</param>
|
||||
/// <param name="index">图标索引</param>
|
||||
/// <param name="largeIcon">是否提取大图标</param>
|
||||
/// <returns>提取的图标</returns>
|
||||
public static Icon Extract(string file, int index, bool largeIcon)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr large;
|
||||
IntPtr small;
|
||||
ExtractIconEx(file, index, out large, out small, 1);
|
||||
|
||||
try
|
||||
{
|
||||
return Icon.FromHandle(largeIcon ? large : small);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (large != IntPtr.Zero)
|
||||
DestroyIcon(large);
|
||||
|
||||
if (small != IntPtr.Zero)
|
||||
DestroyIcon(small);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")]
|
||||
private static extern int ExtractIconEx(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string lpszFile,
|
||||
int nIconIndex,
|
||||
out IntPtr phiconLarge,
|
||||
out IntPtr phiconSmall,
|
||||
int nIcons);
|
||||
|
||||
[DllImport("User32.dll")]
|
||||
private static extern int DestroyIcon(IntPtr hIcon);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
<UserControl x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherSettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500" d:DesignWidth="600">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 自定义按钮样式 -->
|
||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.6"/>
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
||||
<Setter Property="Cursor" Value="Arrow"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15" Foreground="Black"/>
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,15">
|
||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 按钮位置 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center" Foreground="Black"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="0,5">
|
||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 应用管理 -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="#CCCCCC" CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 应用列表 -->
|
||||
<DataGrid Grid.Row="0" x:Name="DgApps" AutoGenerateColumns="False" Margin="5"
|
||||
CanUserAddRows="False" CanUserDeleteRows="False"
|
||||
HeadersVisibility="Column" SelectionMode="Single"
|
||||
SelectionChanged="DgApps_SelectionChanged">
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Header="显示" Binding="{Binding IsVisible}" Width="50"/>
|
||||
<DataGridTextColumn Header="名称" Binding="{Binding Name}" Width="150"/>
|
||||
<DataGridTextColumn Header="路径" Binding="{Binding Path}" Width="*"/>
|
||||
<DataGridTextColumn Header="位置" Binding="{Binding Position}" Width="50"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"
|
||||
Background="#FF007ACC" Foreground="White" BorderBrush="#FF005A9B" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"
|
||||
Background="#FF6C757D" Foreground="White" BorderBrush="#FF5A6268" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"
|
||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"
|
||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,396 +0,0 @@
|
||||
using Ink_Canvas.Windows;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherSettingsControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherSettingsControl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherSettingsControl(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 设置按钮位置
|
||||
RbtnLeft.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left;
|
||||
RbtnRight.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Right;
|
||||
|
||||
// 绑定应用列表
|
||||
DgApps.ItemsSource = _plugin.LauncherItems;
|
||||
|
||||
// 初始化按钮状态
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新按钮状态
|
||||
/// </summary>
|
||||
private void UpdateButtonStates()
|
||||
{
|
||||
bool hasSelection = DgApps.SelectedItem != null;
|
||||
BtnEdit.IsEnabled = hasSelection;
|
||||
BtnDelete.IsEnabled = hasSelection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置单选按钮选择事件
|
||||
/// </summary>
|
||||
private void RbtnPosition_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
|
||||
LauncherButtonPosition oldPosition = _plugin.Config.ButtonPosition;
|
||||
|
||||
if (sender == RbtnLeft)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Left;
|
||||
}
|
||||
else if (sender == RbtnRight)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Right;
|
||||
}
|
||||
|
||||
// 如果位置发生变化,更新按钮位置
|
||||
if (oldPosition != _plugin.Config.ButtonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更改为: {_plugin.Config.ButtonPosition}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"更新启动台按钮位置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建新的启动项
|
||||
LauncherItem item = new LauncherItem
|
||||
{
|
||||
Name = "",
|
||||
Path = "",
|
||||
IsVisible = true,
|
||||
Position = -1 // 让插件管理器分配位置
|
||||
};
|
||||
|
||||
// 直接显示编辑对话框
|
||||
EditLauncherItem(item, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"添加启动项时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
EditLauncherItem(item, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
// 确认删除
|
||||
MessageBoxResult result = MessageBox.Show(
|
||||
$"确定要删除 {item.Name} 吗?",
|
||||
"删除确认",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
// 从集合中移除
|
||||
_plugin.LauncherItems.Remove(item);
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 如果插件已启用,重新加载启动台按钮
|
||||
if (_plugin.IsEnabled)
|
||||
{
|
||||
_plugin.Disable();
|
||||
_plugin.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果插件未启用,则启用它
|
||||
_plugin.Enable();
|
||||
|
||||
// 通知PluginSettingsWindow刷新插件列表
|
||||
var window = Window.GetWindow(this);
|
||||
if (window is PluginSettingsWindow pluginSettingsWindow)
|
||||
{
|
||||
// 触发刷新
|
||||
pluginSettingsWindow.RefreshPluginList();
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox.Show("设置已保存并应用!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用项选择变更事件
|
||||
/// </summary>
|
||||
private void DgApps_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑启动项
|
||||
/// </summary>
|
||||
/// <param name="item">启动项</param>
|
||||
/// <param name="isNew">是否为新建</param>
|
||||
private void EditLauncherItem(LauncherItem item, bool isNew)
|
||||
{
|
||||
// 创建简单的编辑窗口
|
||||
Window editWindow = new Window
|
||||
{
|
||||
Title = isNew ? "添加" : "编辑应用",
|
||||
Width = 400,
|
||||
Height = 200,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
ResizeMode = ResizeMode.NoResize
|
||||
};
|
||||
|
||||
// 创建编辑表单
|
||||
Grid grid = new Grid
|
||||
{
|
||||
Margin = new Thickness(20)
|
||||
};
|
||||
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(80) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
// 名称输入框
|
||||
TextBlock nameLabel = new TextBlock
|
||||
{
|
||||
Text = "名称:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
TextBox nameTextBox = new TextBox
|
||||
{
|
||||
Text = item.Name,
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
Grid.SetRow(nameLabel, 0);
|
||||
Grid.SetColumn(nameLabel, 0);
|
||||
Grid.SetRow(nameTextBox, 0);
|
||||
Grid.SetColumn(nameTextBox, 1);
|
||||
|
||||
grid.Children.Add(nameLabel);
|
||||
grid.Children.Add(nameTextBox);
|
||||
|
||||
// 路径输入框
|
||||
TextBlock pathLabel = new TextBlock
|
||||
{
|
||||
Text = "路径:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid pathGrid = new Grid();
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength() });
|
||||
|
||||
TextBox pathTextBox = new TextBox
|
||||
{
|
||||
Text = item.Path,
|
||||
Margin = new Thickness(0, 5, 5, 5)
|
||||
};
|
||||
Button browseButton = new Button
|
||||
{
|
||||
Content = "浏览",
|
||||
Padding = new Thickness(5, 0, 5, 0),
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
browseButton.Click += (s, e) =>
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择应用程序",
|
||||
Filter = "应用程序 (*.exe)|*.exe|所有文件 (*.*)|*.*",
|
||||
Multiselect = false,
|
||||
FileName = pathTextBox.Text
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
pathTextBox.Text = dialog.FileName;
|
||||
|
||||
// 如果选择的是.exe文件,自动获取文件名填入名称字段
|
||||
if (Path.GetExtension(dialog.FileName).ToLower() == ".exe")
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(dialog.FileName);
|
||||
// 只有在名称字段为空或者是新建项目时才自动填入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text) || isNew)
|
||||
{
|
||||
nameTextBox.Text = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Grid.SetColumn(pathTextBox, 0);
|
||||
Grid.SetColumn(browseButton, 1);
|
||||
pathGrid.Children.Add(pathTextBox);
|
||||
pathGrid.Children.Add(browseButton);
|
||||
|
||||
Grid.SetRow(pathLabel, 1);
|
||||
Grid.SetColumn(pathLabel, 0);
|
||||
Grid.SetRow(pathGrid, 1);
|
||||
Grid.SetColumn(pathGrid, 1);
|
||||
|
||||
grid.Children.Add(pathLabel);
|
||||
grid.Children.Add(pathGrid);
|
||||
|
||||
// 确认和取消按钮
|
||||
StackPanel buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
Button okButton = new Button
|
||||
{
|
||||
Content = "确定",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
IsDefault = true
|
||||
};
|
||||
|
||||
Button cancelButton = new Button
|
||||
{
|
||||
Content = "取消",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
IsCancel = true
|
||||
};
|
||||
|
||||
okButton.Click += (s, e) =>
|
||||
{
|
||||
// 验证输入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用名称!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pathTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用路径!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新项目
|
||||
item.Name = nameTextBox.Text;
|
||||
item.Path = pathTextBox.Text;
|
||||
|
||||
// 如果是新建,添加到集合
|
||||
if (isNew)
|
||||
{
|
||||
_plugin.AddLauncherItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 触发属性变更通知,刷新DataGrid
|
||||
if (DgApps.ItemsSource is ICollectionView view)
|
||||
{
|
||||
view.Refresh();
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
editWindow.DialogResult = true;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
cancelButton.Click += (s, e) =>
|
||||
{
|
||||
editWindow.DialogResult = false;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
Grid.SetRow(buttonPanel, 2);
|
||||
Grid.SetColumnSpan(buttonPanel, 2);
|
||||
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
// 设置窗口内容
|
||||
editWindow.Content = grid;
|
||||
|
||||
// 显示窗口
|
||||
editWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
<Window x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
Title="启动台"
|
||||
Width="400"
|
||||
Height="300"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="#80000000"
|
||||
ResizeMode="NoResize"
|
||||
Topmost="True"
|
||||
Deactivated="Window_Deactivated"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 应用项样式 -->
|
||||
<Style x:Key="LauncherItemStyle" TargetType="Button">
|
||||
<Setter Property="Width" Value="80"/>
|
||||
<Setter Property="Height" Value="80"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="Background" Value="#40FFFFFF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0" Source="{Binding Icon}" Width="32" Height="32" Margin="0,10,0,5"/>
|
||||
<TextBlock Grid.Row="1" Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center"
|
||||
Margin="2,0,2,8" FontSize="11" Foreground="White"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#80FFFFFF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#C0FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border CornerRadius="15" Background="#80000000" BorderThickness="1" BorderBrush="#40FFFFFF">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Grid Grid.Row="0" Height="40">
|
||||
<TextBlock Text="启动台" Foreground="White" FontSize="18" FontWeight="Bold"
|
||||
VerticalAlignment="Center" Margin="15,0,0,0"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
|
||||
<Button x:Name="BtnFixMode" Click="BtnFixMode_Click" Width="30" Height="30"
|
||||
Margin="5,0" Background="Transparent" BorderThickness="0"
|
||||
ToolTip="切换固定模式">
|
||||
<Path x:Name="FixModeIcon" Data="M7,2V13H10V22L17,10H13L17,2H7Z" Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
<Button x:Name="BtnClose" Click="BtnClose_Click" Width="30" Height="30"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
ToolTip="关闭">
|
||||
<Path Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
|
||||
Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 应用网格 -->
|
||||
<ScrollViewer Grid.Row="1" Margin="10" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<WrapPanel x:Name="AppPanel" Orientation="Horizontal" HorizontalAlignment="Center"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,466 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于固定模式
|
||||
/// </summary>
|
||||
private bool _isFixMode;
|
||||
|
||||
/// <summary>
|
||||
/// 应用项按钮列表
|
||||
/// </summary>
|
||||
private readonly Dictionary<Button, LauncherItem> _appButtons = new Dictionary<Button, LauncherItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽中的按钮
|
||||
/// </summary>
|
||||
private Button _draggingButton;
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽开始位置
|
||||
/// </summary>
|
||||
private Point _dragStartPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public LauncherWindow(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 添加鼠标按下事件(用于拖动窗口)
|
||||
MouseDown += (s, e) =>
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
};
|
||||
|
||||
// 根据应用数量调整窗口大小
|
||||
AdjustWindowSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载启动台应用项
|
||||
/// </summary>
|
||||
private void LoadLauncherItems()
|
||||
{
|
||||
// 清空现有应用项
|
||||
AppPanel.Children.Clear();
|
||||
_appButtons.Clear();
|
||||
|
||||
// 获取显示的应用项
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(item => item.IsVisible)
|
||||
.OrderBy(item => item.Position)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in visibleItems)
|
||||
{
|
||||
// 创建应用按钮
|
||||
Button appButton = new Button
|
||||
{
|
||||
Style = (Style)FindResource("LauncherItemStyle"),
|
||||
DataContext = item,
|
||||
Tag = item.Position
|
||||
};
|
||||
|
||||
// 添加点击事件
|
||||
appButton.Click += AppButton_Click;
|
||||
|
||||
// 在固定模式下,添加拖拽事件
|
||||
appButton.PreviewMouseDown += AppButton_PreviewMouseDown;
|
||||
appButton.PreviewMouseMove += AppButton_PreviewMouseMove;
|
||||
appButton.PreviewMouseUp += AppButton_PreviewMouseUp;
|
||||
|
||||
// 记录按钮和项目的对应关系
|
||||
_appButtons.Add(appButton, item);
|
||||
|
||||
// 添加到面板
|
||||
AppPanel.Children.Add(appButton);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据应用数量调整窗口大小
|
||||
/// </summary>
|
||||
private void AdjustWindowSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 每行最多显示4个应用
|
||||
const int appsPerRow = 4;
|
||||
|
||||
// 计算行数
|
||||
int visibleCount = _appButtons.Count;
|
||||
int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow);
|
||||
|
||||
// 设置窗口宽度(每个应用90像素宽 = 80 + 5*2)
|
||||
Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400
|
||||
|
||||
// 设置窗口高度(每个应用90像素高 = 80 + 5*2)
|
||||
Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮点击事件
|
||||
/// </summary>
|
||||
private void AppButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isFixMode) return; // 在固定模式下,不响应点击事件
|
||||
|
||||
if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item))
|
||||
{
|
||||
// 获取应用路径和名称,用于后续启动
|
||||
string appPath = item.Path;
|
||||
string appName = item.Name;
|
||||
|
||||
LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}");
|
||||
|
||||
// 首先标记窗口正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 创建一个应用启动任务
|
||||
var launchTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待一段时间,确保窗口关闭流程已经开始
|
||||
Thread.Sleep(200);
|
||||
|
||||
// 使用UI线程启动应用
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查应用路径是否存在
|
||||
if (File.Exists(appPath) || !appPath.Contains(":\\"))
|
||||
{
|
||||
// 创建进程启动信息
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = appPath,
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
// 启动应用程序
|
||||
var process = Process.Start(psi);
|
||||
LogHelper.WriteLogToFile($"应用程序 {appName} 已启动");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭窗口
|
||||
try
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try { Close(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
|
||||
// 启动应用程序任务
|
||||
launchTask.Start();
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 如果无法通过UI关闭窗口,直接启动任务
|
||||
launchTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
try { IsClosing = true; Close(); } catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
||||
}
|
||||
}
|
||||
|
||||
#region 固定模式拖拽事件
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标按下事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode) return;
|
||||
|
||||
if (e.ChangedButton == MouseButton.Left && sender is Button button)
|
||||
{
|
||||
_draggingButton = button;
|
||||
_dragStartPoint = e.GetPosition(AppPanel);
|
||||
button.CaptureMouse();
|
||||
button.Opacity = 0.7;
|
||||
|
||||
// 阻止事件冒泡,以避免触发按钮点击
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标移动事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point currentPosition = e.GetPosition(AppPanel);
|
||||
|
||||
// 移动按钮
|
||||
System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2);
|
||||
System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2);
|
||||
|
||||
// 将按钮移到最上层
|
||||
Panel.SetZIndex(_draggingButton, 100);
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标释放事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
// 释放鼠标捕获
|
||||
_draggingButton.ReleaseMouseCapture();
|
||||
|
||||
// 计算新位置
|
||||
Point releasePoint = e.GetPosition(AppPanel);
|
||||
int newPosition = CalculateGridPosition(releasePoint);
|
||||
|
||||
// 获取当前项目
|
||||
LauncherItem currentItem = _appButtons[_draggingButton];
|
||||
|
||||
// 重新排序
|
||||
ReorderItems(currentItem, newPosition);
|
||||
|
||||
// 重新加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 清除拖拽状态
|
||||
_draggingButton.Opacity = 1;
|
||||
Panel.SetZIndex(_draggingButton, 0);
|
||||
_draggingButton = null;
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算网格位置
|
||||
/// </summary>
|
||||
private int CalculateGridPosition(Point point)
|
||||
{
|
||||
// 计算行和列
|
||||
int columnCount = 4; // 每行最多4个应用
|
||||
int columnWidth = 90; // 应用宽度(包括边距)
|
||||
int rowHeight = 90; // 应用高度(包括边距)
|
||||
|
||||
int column = (int)(point.X / columnWidth);
|
||||
int row = (int)(point.Y / rowHeight);
|
||||
|
||||
// 确保在有效范围内
|
||||
column = Math.Max(0, Math.Min(column, columnCount - 1));
|
||||
row = Math.Max(0, row);
|
||||
|
||||
// 计算位置索引
|
||||
return row * columnCount + column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新排序应用项
|
||||
/// </summary>
|
||||
private void ReorderItems(LauncherItem item, int newPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 设置项目为固定位置
|
||||
item.IsPositionFixed = true;
|
||||
|
||||
// 如果位置相同,无需调整
|
||||
if (item.Position == newPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有可见项目
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(i => i.IsVisible)
|
||||
.OrderBy(i => i.Position)
|
||||
.ToList();
|
||||
|
||||
// 移除当前项目
|
||||
visibleItems.Remove(item);
|
||||
|
||||
// 查找插入位置
|
||||
int insertIndex = 0;
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
if (visibleItems[i].Position >= newPosition)
|
||||
{
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
insertIndex = i + 1;
|
||||
}
|
||||
|
||||
// 插入项目
|
||||
visibleItems.Insert(insertIndex, item);
|
||||
|
||||
// 重新分配位置
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
visibleItems[i].Position = i;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 窗口失去焦点事件
|
||||
/// </summary>
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口
|
||||
if (!_isFixMode && IsLoaded && !IsClosing)
|
||||
{
|
||||
// 标记为正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次检查窗口状态
|
||||
if (IsLoaded && !IsClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口是否正在关闭
|
||||
/// </summary>
|
||||
private bool IsClosing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重写OnClosing方法,标记窗口正在关闭
|
||||
/// </summary>
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
IsClosing = true;
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 固定模式按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnFixMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 切换固定模式
|
||||
_isFixMode = !_isFixMode;
|
||||
|
||||
// 更新固定模式按钮图标颜色
|
||||
FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White;
|
||||
|
||||
// 显示提示
|
||||
if (_isFixMode)
|
||||
{
|
||||
MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,589 +0,0 @@
|
||||
using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn
|
||||
{
|
||||
/// <summary>
|
||||
/// 超级启动台插件
|
||||
/// </summary>
|
||||
public class SuperLauncherPlugin : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
public override string Name => "超级启动台";
|
||||
|
||||
public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。";
|
||||
|
||||
public override Version Version => new Version(1, 0, 1);
|
||||
|
||||
public override string Author => "ICC CE 团队";
|
||||
|
||||
public override bool IsBuiltIn => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件属性和字段
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public LauncherConfig Config { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public ObservableCollection<LauncherItem> LauncherItems { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台按钮
|
||||
/// </summary>
|
||||
private LauncherButton _launcherButton;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台窗口
|
||||
/// </summary>
|
||||
private LauncherWindow _launcherWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件路径
|
||||
/// </summary>
|
||||
private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json");
|
||||
|
||||
/// <summary>
|
||||
/// 标记是否已添加到浮动栏
|
||||
/// </summary>
|
||||
private bool _isAddedToFloatingBar;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// 创建配置目录
|
||||
string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
if (!Directory.Exists(configDir))
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
LoadConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已初始化");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsEnabled) return; // 防止重复启用
|
||||
|
||||
// 创建启动台按钮
|
||||
if (_launcherButton == null)
|
||||
{
|
||||
_launcherButton = new LauncherButton(this);
|
||||
LogHelper.WriteLogToFile("超级启动台按钮已创建");
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
AddLauncherButtonToFloatingBar();
|
||||
|
||||
// 设置启用状态
|
||||
base.Enable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsEnabled) return; // 防止重复禁用
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
// 设置禁用状态
|
||||
base.Disable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
return new LauncherSettingsControl(this);
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件设置
|
||||
/// </summary>
|
||||
public override void SavePluginSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保配置已加载
|
||||
if (Config == null)
|
||||
{
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
// 更新其他设置,但不更改插件启用状态
|
||||
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件设置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 配置管理
|
||||
|
||||
/// <summary>
|
||||
/// 加载配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
string json = File.ReadAllText(_configPath);
|
||||
Config = JsonConvert.DeserializeObject<LauncherConfig>(json) ?? CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items ?? new List<LauncherItem>());
|
||||
|
||||
// 注意:不再根据配置更改插件启用状态
|
||||
// 插件状态由PluginManager统一管理
|
||||
}
|
||||
else
|
||||
{
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置
|
||||
/// </summary>
|
||||
public void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 同步LauncherItems到Config
|
||||
Config.Items = new List<LauncherItem>(LauncherItems);
|
||||
|
||||
// 序列化并保存配置
|
||||
string json = JsonConvert.SerializeObject(Config, Formatting.Indented);
|
||||
File.WriteAllText(_configPath, json);
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台配置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认配置
|
||||
/// </summary>
|
||||
private LauncherConfig CreateDefaultConfig()
|
||||
{
|
||||
var config = new LauncherConfig
|
||||
{
|
||||
ButtonPosition = LauncherButtonPosition.Right,
|
||||
// 不再使用IsEnabled,插件状态由PluginManager管理
|
||||
Items = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem
|
||||
{
|
||||
Name = "资源管理器",
|
||||
Path = @"C:\Windows\explorer.exe",
|
||||
IsVisible = true,
|
||||
Position = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台按钮管理
|
||||
|
||||
/// <summary>
|
||||
/// 将启动台按钮添加到浮动栏
|
||||
/// </summary>
|
||||
private void AddLauncherButtonToFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果已经添加,先移除
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建启动台按钮
|
||||
_launcherButton = new LauncherButton(this);
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
if (Config.ButtonPosition == LauncherButtonPosition.Left)
|
||||
{
|
||||
floatingBar.Children.Insert(0, buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧");
|
||||
}
|
||||
else
|
||||
{
|
||||
floatingBar.Children.Add(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归查找StackPanelFloatingBar
|
||||
/// </summary>
|
||||
private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result)
|
||||
{
|
||||
if (parent == null || result != null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 检查当前对象是否为我们要找的面板
|
||||
if (parent is Panel panel && panel.Name == "StackPanelFloatingBar")
|
||||
{
|
||||
result = panel;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取子元素数量
|
||||
int childCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
|
||||
// 遍历所有子元素
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
||||
FindStackPanelFloatingBar(child, ref result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从浮动栏移除启动台按钮
|
||||
/// </summary>
|
||||
private void RemoveLauncherButtonFromFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isAddedToFloatingBar || _launcherButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取按钮元素
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
if (floatingBar.Children.Contains(buttonElement))
|
||||
{
|
||||
floatingBar.Children.Remove(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新启动台按钮位置
|
||||
/// </summary>
|
||||
public void UpdateButtonPosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果按钮已添加到浮动栏,重新添加以更新位置
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
AddLauncherButtonToFloatingBar();
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台功能
|
||||
|
||||
/// <summary>
|
||||
/// 显示启动台窗口
|
||||
/// </summary>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
public void ShowLauncherWindow(Point buttonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果窗口已存在,关闭它
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新的启动台窗口
|
||||
_launcherWindow = new LauncherWindow(this);
|
||||
|
||||
// 计算窗口位置,使其位于按钮上方
|
||||
PositionLauncherWindow(_launcherWindow, buttonPosition);
|
||||
|
||||
// 显示窗口
|
||||
_launcherWindow.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置启动台窗口位置
|
||||
/// </summary>
|
||||
/// <param name="window">启动台窗口</param>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition)
|
||||
{
|
||||
// 确保窗口已加载
|
||||
if (window.ActualWidth == 0 || window.ActualHeight == 0)
|
||||
{
|
||||
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
|
||||
// 设置窗口加载完成后的位置
|
||||
window.Loaded += (s, e) =>
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用到启动台
|
||||
/// </summary>
|
||||
/// <param name="item">启动台项</param>
|
||||
public void AddLauncherItem(LauncherItem item)
|
||||
{
|
||||
// 如果项目数量已达上限,则不添加
|
||||
if (LauncherItems.Count >= 40)
|
||||
{
|
||||
MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 寻找合适的位置
|
||||
if (item.Position < 0)
|
||||
{
|
||||
item.Position = FindNextAvailablePosition();
|
||||
}
|
||||
|
||||
// 添加项目并保存配置
|
||||
LauncherItems.Add(item);
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找下一个可用位置
|
||||
/// </summary>
|
||||
private int FindNextAvailablePosition()
|
||||
{
|
||||
// 获取已使用的位置列表
|
||||
var usedPositions = new HashSet<int>();
|
||||
foreach (var item in LauncherItems)
|
||||
{
|
||||
usedPositions.Add(item.Position);
|
||||
}
|
||||
|
||||
// 查找第一个可用位置
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
if (!usedPositions.Contains(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有位置都已使用,则返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类,提供对插件服务的访问和基本实现
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBase : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务实例
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBase()
|
||||
{
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写初始化方法,调用OnStartup
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
OnStartup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写清理方法,调用OnShutdown
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
OnShutdown();
|
||||
base.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类 V2,提供对三个专门服务接口的访问
|
||||
/// 插件开发者可以根据需要选择性地使用这些服务
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBaseV2 : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取服务实例
|
||||
/// </summary>
|
||||
public IGetService GetService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 窗口服务实例
|
||||
/// </summary>
|
||||
public IWindowService WindowService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作服务实例
|
||||
/// </summary>
|
||||
public IActionService ActionService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件服务实例(兼容性)
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBaseV2()
|
||||
{
|
||||
// 初始化所有服务实例
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
GetService = PluginServiceManager.Instance;
|
||||
WindowService = PluginServiceManager.Instance;
|
||||
ActionService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
#region 便捷方法
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
protected void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
WindowService.ShowNotification(message, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
protected bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
return WindowService.ShowConfirmDialog(message, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
protected string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
return WindowService.ShowInputDialog(message, title, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
protected T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
return GetService.GetSetting(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
protected void SetSetting<T>(string key, T value)
|
||||
{
|
||||
ActionService.SetSetting(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置
|
||||
/// </summary>
|
||||
protected void SaveSettings()
|
||||
{
|
||||
ActionService.SaveSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
protected void ClearCanvas()
|
||||
{
|
||||
ActionService.ClearCanvas();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
protected void Undo()
|
||||
{
|
||||
ActionService.Undo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
protected void Redo()
|
||||
{
|
||||
ActionService.Redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以撤销
|
||||
/// </summary>
|
||||
protected bool CanUndo => GetService.CanUndo;
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以重做
|
||||
/// </summary>
|
||||
protected bool CanRedo => GetService.CanRedo;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
protected int CurrentDrawingMode => GetService.CurrentDrawingMode;
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
protected void SetDrawingMode(int mode)
|
||||
{
|
||||
ActionService.SetDrawingMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
protected void RegisterEventHandler(string eventName, System.EventHandler handler)
|
||||
{
|
||||
ActionService.RegisterEventHandler(eventName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
protected void UnregisterEventHandler(string eventName, System.EventHandler handler)
|
||||
{
|
||||
ActionService.UnregisterEventHandler(eventName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
protected void TriggerEvent(string eventName, object sender, System.EventArgs args)
|
||||
{
|
||||
ActionService.TriggerEvent(eventName, sender, args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作服务接口,统一所有执行操作相关的方法
|
||||
/// </summary>
|
||||
public interface IActionService
|
||||
{
|
||||
#region 画布操作
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
void ClearCanvas();
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有画布
|
||||
/// </summary>
|
||||
void ClearAllCanvases();
|
||||
|
||||
/// <summary>
|
||||
/// 添加新页面
|
||||
/// </summary>
|
||||
void AddNewPage();
|
||||
|
||||
/// <summary>
|
||||
/// 删除当前页面
|
||||
/// </summary>
|
||||
void DeleteCurrentPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页面索引</param>
|
||||
void SwitchToPage(int pageIndex);
|
||||
|
||||
/// <summary>
|
||||
/// 切换到下一页
|
||||
/// </summary>
|
||||
void NextPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到上一页
|
||||
/// </summary>
|
||||
void PreviousPage();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
void SetDrawingMode(int mode);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetInkWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetInkColor(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// 设置高亮笔宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetHighlighterWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦大小
|
||||
/// </summary>
|
||||
/// <param name="size">大小</param>
|
||||
void SetEraserSize(int size);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
void SetEraserType(int type);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦形状
|
||||
/// </summary>
|
||||
/// <param name="shape">形状</param>
|
||||
void SetEraserShape(int shape);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触透明度
|
||||
/// </summary>
|
||||
/// <param name="alpha">透明度</param>
|
||||
void SetInkAlpha(double alpha);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触样式
|
||||
/// </summary>
|
||||
/// <param name="style">样式</param>
|
||||
void SetInkStyle(int style);
|
||||
|
||||
/// <summary>
|
||||
/// 设置背景颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetBackgroundColor(string color);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void SaveCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 加载画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void LoadCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为图片
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="format">图片格式</param>
|
||||
void ExportAsImage(string filePath, string format);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为PDF
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void ExportAsPDF(string filePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做操作
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
void Undo();
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
void Redo();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择操作
|
||||
|
||||
/// <summary>
|
||||
/// 全选
|
||||
/// </summary>
|
||||
void SelectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 取消选择
|
||||
/// </summary>
|
||||
void DeselectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 删除选中内容
|
||||
/// </summary>
|
||||
void DeleteSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 复制选中内容
|
||||
/// </summary>
|
||||
void CopySelected();
|
||||
|
||||
/// <summary>
|
||||
/// 剪切选中内容
|
||||
/// </summary>
|
||||
void CutSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 粘贴内容
|
||||
/// </summary>
|
||||
void Paste();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统设置操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
void SetSetting<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置到文件
|
||||
/// </summary>
|
||||
void SaveSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载设置
|
||||
/// </summary>
|
||||
void LoadSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 重置设置为默认值
|
||||
/// </summary>
|
||||
void ResetSettings();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件管理操作
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void EnablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void DisablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void UnloadPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件系统操作
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void RegisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void UnregisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
void TriggerEvent(string eventName, object sender, EventArgs args);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用程序操作
|
||||
|
||||
/// <summary>
|
||||
/// 重启应用程序
|
||||
/// </summary>
|
||||
void RestartApplication();
|
||||
|
||||
/// <summary>
|
||||
/// 退出应用程序
|
||||
/// </summary>
|
||||
void ExitApplication();
|
||||
|
||||
/// <summary>
|
||||
/// 检查更新
|
||||
/// </summary>
|
||||
void CheckForUpdates();
|
||||
|
||||
/// <summary>
|
||||
/// 打开帮助文档
|
||||
/// </summary>
|
||||
void OpenHelpDocument();
|
||||
|
||||
/// <summary>
|
||||
/// 打开关于页面
|
||||
/// </summary>
|
||||
void OpenAboutPage();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// ICCPP 插件适配器,用于加载和管理 .iccpp 格式的插件
|
||||
/// </summary>
|
||||
public class ICCPPPluginAdapter : PluginBase
|
||||
{
|
||||
private readonly byte[] _pluginData;
|
||||
private readonly string _pluginPath;
|
||||
private readonly string _pluginName;
|
||||
private readonly Version _pluginVersion;
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 创建 ICCPP 插件适配器
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">插件文件路径</param>
|
||||
/// <param name="pluginData">插件文件数据</param>
|
||||
public ICCPPPluginAdapter(string pluginPath, byte[] pluginData)
|
||||
{
|
||||
_pluginPath = pluginPath;
|
||||
_pluginData = pluginData;
|
||||
PluginPath = pluginPath;
|
||||
|
||||
// 从文件名获取插件名称
|
||||
_pluginName = Path.GetFileNameWithoutExtension(pluginPath);
|
||||
_pluginVersion = new Version(1, 0, 0); // 默认版本
|
||||
|
||||
// 尝试从插件数据中读取更多信息
|
||||
TryReadPluginMetadata();
|
||||
}
|
||||
|
||||
public ICCPPPluginAdapter()
|
||||
{
|
||||
_pluginPath = string.Empty;
|
||||
_pluginData = new byte[0];
|
||||
PluginPath = string.Empty;
|
||||
_pluginName = "ICCPPPlugin";
|
||||
_pluginVersion = new Version(1, 0, 0);
|
||||
// 可选:初始化其他字段
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从插件数据中读取元数据
|
||||
/// </summary>
|
||||
private void TryReadPluginMetadata()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以根据 .iccpp 文件的实际格式解析元数据
|
||||
// 例如,如果文件有特定的头部结构,可以在这里解析
|
||||
|
||||
// 示例:如果前100字节包含元数据
|
||||
if (_pluginData.Length > 100)
|
||||
{
|
||||
// 解析元数据的代码...
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"解析插件 {_pluginName} 元数据时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlugin 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => _pluginName;
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => $"{_pluginName} (ICCPP 格式插件)";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => _pluginVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "未知";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的初始化逻辑
|
||||
// 例如,根据文件格式加载特定资源
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已初始化");
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
if (IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的启用逻辑
|
||||
// 例如,加载动态库、注册事件等
|
||||
|
||||
base.Enable(); // 设置启用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的禁用逻辑
|
||||
// 例如,卸载动态库、注销事件等
|
||||
|
||||
base.Disable(); // 设置禁用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理插件资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的清理逻辑
|
||||
// 例如,释放资源等
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已清理资源");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理 ICCPP 插件 {Name} 资源时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件接口,提供对插件服务的访问
|
||||
/// </summary>
|
||||
public interface IEnhancedPlugin : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取插件服务实例
|
||||
/// </summary>
|
||||
IPluginService PluginService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
void OnStartup();
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
void OnShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
MenuItem[] GetMenuItems();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
Button[] GetToolbarButtons();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
string GetStatusBarInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
void OnConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取服务接口,统一所有获取类的方法
|
||||
/// </summary>
|
||||
public interface IGetService
|
||||
{
|
||||
#region 窗口和UI获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取主窗口引用
|
||||
/// </summary>
|
||||
Window MainWindow { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前画布
|
||||
/// </summary>
|
||||
InkCanvas CurrentCanvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有画布页面
|
||||
/// </summary>
|
||||
List<Canvas> AllCanvasPages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面索引
|
||||
/// </summary>
|
||||
int CurrentPageIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面数量
|
||||
/// </summary>
|
||||
int TotalPageCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取浮动工具栏
|
||||
/// </summary>
|
||||
FrameworkElement FloatingToolBar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取左侧面板
|
||||
/// </summary>
|
||||
FrameworkElement LeftPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取右侧面板
|
||||
/// </summary>
|
||||
FrameworkElement RightPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶部面板
|
||||
/// </summary>
|
||||
FrameworkElement TopPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取底部面板
|
||||
/// </summary>
|
||||
FrameworkElement BottomPanel { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
int CurrentDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触宽度
|
||||
/// </summary>
|
||||
double CurrentInkWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触颜色
|
||||
/// </summary>
|
||||
Color CurrentInkColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前高亮笔宽度
|
||||
/// </summary>
|
||||
double CurrentHighlighterWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦大小
|
||||
/// </summary>
|
||||
int CurrentEraserSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦类型
|
||||
/// </summary>
|
||||
int CurrentEraserType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦形状
|
||||
/// </summary>
|
||||
int CurrentEraserShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触透明度
|
||||
/// </summary>
|
||||
double CurrentInkAlpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触样式
|
||||
/// </summary>
|
||||
int CurrentInkStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前背景颜色
|
||||
/// </summary>
|
||||
string CurrentBackgroundColor { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前主题模式
|
||||
/// </summary>
|
||||
bool IsDarkTheme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为白板模式
|
||||
/// </summary>
|
||||
bool IsWhiteboardMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为PPT模式
|
||||
/// </summary>
|
||||
bool IsPPTMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为全屏模式
|
||||
/// </summary>
|
||||
bool IsFullScreenMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为画板模式
|
||||
/// </summary>
|
||||
bool IsCanvasMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为选择模式
|
||||
/// </summary>
|
||||
bool IsSelectionMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为擦除模式
|
||||
/// </summary>
|
||||
bool IsEraserMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为形状绘制模式
|
||||
/// </summary>
|
||||
bool IsShapeDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为高亮模式
|
||||
/// </summary>
|
||||
bool IsHighlighterMode { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否可以撤销
|
||||
/// </summary>
|
||||
bool CanUndo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否可以重做
|
||||
/// </summary>
|
||||
bool CanRedo { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统设置获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
T GetSetting<T>(string key, T defaultValue = default(T));
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件信息获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载的插件
|
||||
/// </summary>
|
||||
/// <returns>插件列表</returns>
|
||||
List<IPlugin> GetAllPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>插件实例</returns>
|
||||
IPlugin GetPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义插件的基本接口
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
bool IsBuiltIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// 此方法在插件加载时被调用,用于执行一些初始化工作
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 此方法在插件被用户或系统启用时调用,激活插件功能
|
||||
/// </summary>
|
||||
void Enable();
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 此方法在插件被用户或系统禁用时调用,停用插件功能
|
||||
/// </summary>
|
||||
void Disable();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// 此方法返回插件的设置界面控件,用于展示在设置窗口
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
UserControl GetSettingsView();
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// 此方法在插件被卸载前调用,用于释放资源和执行清理
|
||||
/// </summary>
|
||||
void Cleanup();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务接口,提供对软件内部功能的访问
|
||||
/// 继承自三个专门的服务接口:获取服务、窗口服务、操作服务
|
||||
/// </summary>
|
||||
public interface IPluginService : IGetService, IWindowService, IActionService
|
||||
{
|
||||
// 这个接口现在继承自三个专门的服务接口
|
||||
// 所有方法都在子接口中定义,这里不需要重复定义
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知类型枚举
|
||||
/// </summary>
|
||||
public enum NotificationType
|
||||
{
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口服务接口,统一所有窗口操作相关的方法
|
||||
/// </summary>
|
||||
public interface IWindowService
|
||||
{
|
||||
#region 窗口显示和隐藏
|
||||
|
||||
/// <summary>
|
||||
/// 显示设置窗口
|
||||
/// </summary>
|
||||
void ShowSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏设置窗口
|
||||
/// </summary>
|
||||
void HideSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示插件设置窗口
|
||||
/// </summary>
|
||||
void ShowPluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏插件设置窗口
|
||||
/// </summary>
|
||||
void HidePluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示帮助窗口
|
||||
/// </summary>
|
||||
void ShowHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏帮助窗口
|
||||
/// </summary>
|
||||
void HideHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示关于窗口
|
||||
/// </summary>
|
||||
void ShowAboutWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏关于窗口
|
||||
/// </summary>
|
||||
void HideAboutWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 对话框和通知
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
void ShowNotification(string message, NotificationType type = NotificationType.Info);
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
bool ShowConfirmDialog(string message, string title = "确认");
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
string ShowInputDialog(string message, string title = "输入", string defaultValue = "");
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口状态控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口全屏状态
|
||||
/// </summary>
|
||||
/// <param name="isFullScreen">是否全屏</param>
|
||||
void SetFullScreen(bool isFullScreen);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口置顶状态
|
||||
/// </summary>
|
||||
/// <param name="isTopMost">是否置顶</param>
|
||||
void SetTopMost(bool isTopMost);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口可见性
|
||||
/// </summary>
|
||||
/// <param name="isVisible">是否可见</param>
|
||||
void SetWindowVisibility(bool isVisible);
|
||||
|
||||
/// <summary>
|
||||
/// 最小化窗口
|
||||
/// </summary>
|
||||
void MinimizeWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 最大化窗口
|
||||
/// </summary>
|
||||
void MaximizeWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 恢复窗口
|
||||
/// </summary>
|
||||
void RestoreWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭窗口
|
||||
/// </summary>
|
||||
void CloseWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口位置和大小
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口位置
|
||||
/// </summary>
|
||||
/// <param name="x">X坐标</param>
|
||||
/// <param name="y">Y坐标</param>
|
||||
void SetWindowPosition(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口大小
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
/// <param name="height">高度</param>
|
||||
void SetWindowSize(double width, double height);
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口位置
|
||||
/// </summary>
|
||||
/// <returns>窗口位置</returns>
|
||||
(double x, double y) GetWindowPosition();
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口大小
|
||||
/// </summary>
|
||||
/// <returns>窗口大小</returns>
|
||||
(double width, double height) GetWindowSize();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件基类,提供基本实现
|
||||
/// </summary>
|
||||
public abstract class PluginBase : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件状态(私有字段)
|
||||
/// </summary>
|
||||
private bool _isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 插件状态(公共属性)
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
protected set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnEnabledStateChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件ID
|
||||
/// </summary>
|
||||
public string Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件路径
|
||||
/// </summary>
|
||||
public string PluginPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public abstract Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public abstract string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public virtual bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件
|
||||
/// </summary>
|
||||
public event EventHandler<bool> EnabledStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
Id = GetType().FullName;
|
||||
|
||||
// 添加日志,记录插件名称
|
||||
try
|
||||
{
|
||||
string name = Name;
|
||||
LogHelper.WriteLogToFile($"初始化插件: ID={Id}, 名称={name ?? "未命名"}");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"警告: 插件 {Id} 的名称为空", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件名称时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已初始化");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public virtual void Enable()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public virtual void Disable()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public virtual UserControl GetSettingsView()
|
||||
{
|
||||
// 默认返回空设置页面
|
||||
return new UserControl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// </summary>
|
||||
public virtual void Cleanup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已卸载");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件自身的设置
|
||||
/// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态
|
||||
/// 插件启用状态由PluginManager统一管理
|
||||
/// </summary>
|
||||
public virtual void SavePluginSettings()
|
||||
{
|
||||
// 默认实现不做任何事情
|
||||
// 子类可以重写此方法,将自身设置保存到配置文件中
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发状态变更事件
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">是否启用</param>
|
||||
protected virtual void OnEnabledStateChanged(bool isEnabled)
|
||||
{
|
||||
EnabledStateChanged?.Invoke(this, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件配置管理器,允许插件管理自己的配置
|
||||
/// </summary>
|
||||
public class PluginConfigurationManager
|
||||
{
|
||||
private static readonly string PluginConfigDirectory = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
private static readonly Dictionary<string, Dictionary<string, object>> _pluginConfigs = new Dictionary<string, Dictionary<string, object>>();
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
static PluginConfigurationManager()
|
||||
{
|
||||
// 确保配置目录存在
|
||||
if (!Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(PluginConfigDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>配置值</returns>
|
||||
public static T GetConfiguration<T>(string pluginName, string key, T defaultValue = default(T))
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
// 尝试类型转换
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="value">配置值</param>
|
||||
public static void SetConfiguration<T>(string pluginName, string key, T value)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_pluginConfigs.ContainsKey(pluginName))
|
||||
{
|
||||
_pluginConfigs[pluginName] = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
_pluginConfigs[pluginName][key] = value;
|
||||
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
public static void RemoveConfiguration(string pluginName, string key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.Remove(key))
|
||||
{
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>配置字典</returns>
|
||||
public static Dictionary<string, object> GetAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
return new Dictionary<string, object>(pluginConfig);
|
||||
}
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void ClearAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.Remove(pluginName))
|
||||
{
|
||||
// 删除配置文件
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
File.Delete(configFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除插件 {pluginName} 所有配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void LoadPluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
string json = File.ReadAllText(configFile);
|
||||
var config = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_pluginConfigs[pluginName] = config ?? new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已加载插件 {pluginName} 的配置");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
private static void SavePluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> pluginConfig;
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_pluginConfigs.TryGetValue(pluginName, out pluginConfig))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
string json = JsonConvert.SerializeObject(pluginConfig, Formatting.Indented);
|
||||
File.WriteAllText(configFile, json);
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存插件 {pluginName} 的配置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有插件的配置
|
||||
/// </summary>
|
||||
public static void LoadAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
string[] configFiles = Directory.GetFiles(PluginConfigDirectory, "*.json");
|
||||
foreach (string configFile in configFiles)
|
||||
{
|
||||
string pluginName = Path.GetFileNameWithoutExtension(configFile);
|
||||
LoadPluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有插件的配置
|
||||
/// </summary>
|
||||
public static void SaveAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
foreach (string pluginName in _pluginConfigs.Keys)
|
||||
{
|
||||
SavePluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,509 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务管理器,实现IPluginService接口,提供对软件内部功能的访问
|
||||
/// </summary>
|
||||
public class PluginServiceManager : IPluginService
|
||||
{
|
||||
private static PluginServiceManager _instance;
|
||||
private MainWindow _mainWindow;
|
||||
private Dictionary<string, EventHandler> _eventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// 单例实例
|
||||
/// </summary>
|
||||
public static PluginServiceManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new PluginServiceManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private PluginServiceManager()
|
||||
{
|
||||
_eventHandlers = new Dictionary<string, EventHandler>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置主窗口引用
|
||||
/// </summary>
|
||||
/// <param name="mainWindow">主窗口实例</param>
|
||||
public void SetMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
#region 窗口和UI访问
|
||||
|
||||
public Window MainWindow => _mainWindow;
|
||||
|
||||
public InkCanvas CurrentCanvas => null; // 暂时返回null,避免访问权限问题
|
||||
|
||||
public List<Canvas> AllCanvasPages => new List<Canvas>(); // 暂时返回空列表
|
||||
|
||||
public int CurrentPageIndex => 0; // 暂时返回0
|
||||
|
||||
public int TotalPageCount => 0; // 暂时返回0
|
||||
|
||||
public FrameworkElement FloatingToolBar => _mainWindow?.ViewboxFloatingBar;
|
||||
|
||||
public FrameworkElement LeftPanel => _mainWindow?.BlackboardLeftSide;
|
||||
|
||||
public FrameworkElement RightPanel => _mainWindow?.BlackboardRightSide;
|
||||
|
||||
public FrameworkElement TopPanel => _mainWindow?.BorderTools;
|
||||
|
||||
public FrameworkElement BottomPanel => _mainWindow?.BorderSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态
|
||||
|
||||
public int CurrentDrawingMode => 0; // 暂时返回0
|
||||
|
||||
public double CurrentInkWidth => 2.5; // 暂时返回默认值
|
||||
|
||||
public Color CurrentInkColor => Colors.Black; // 暂时返回默认值
|
||||
|
||||
public double CurrentHighlighterWidth => 20.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserSize => 2; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserType => 0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserShape => 0; // 暂时返回默认值
|
||||
|
||||
public double CurrentInkAlpha => 255.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentInkStyle => 0; // 暂时返回默认值
|
||||
|
||||
public string CurrentBackgroundColor => "#162924"; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态
|
||||
|
||||
public bool IsDarkTheme => false; // 暂时返回默认值
|
||||
|
||||
public bool IsWhiteboardMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsPPTMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsFullScreenMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsCanvasMode => true; // 暂时返回默认值
|
||||
|
||||
public bool IsSelectionMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsEraserMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsShapeDrawingMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsHighlighterMode => false; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region IGetService 实现
|
||||
|
||||
public bool CanUndo => false; // 暂时返回默认值
|
||||
|
||||
public bool CanRedo => false; // 暂时返回默认值
|
||||
|
||||
public T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public List<IPlugin> GetAllPlugins()
|
||||
{
|
||||
return new List<IPlugin>(PluginManager.Instance.Plugins);
|
||||
}
|
||||
|
||||
public IPlugin GetPlugin(string pluginName)
|
||||
{
|
||||
return PluginManager.Instance.Plugins.FirstOrDefault(p => p.Name == pluginName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWindowService 实现
|
||||
|
||||
public void ShowSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowPluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HidePluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void SetFullScreen(bool isFullScreen)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetTopMost(bool isTopMost)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowVisibility(bool isVisible)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void MinimizeWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void MaximizeWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void RestoreWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CloseWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowPosition(double x, double y)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowSize(double width, double height)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public (double x, double y) GetWindowPosition()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
public (double width, double height) GetWindowSize()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return (800, 600);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IActionService 实现
|
||||
|
||||
public void ClearCanvas()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ClearAllCanvases()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void AddNewPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteCurrentPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SwitchToPage(int pageIndex)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void NextPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void PreviousPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetDrawingMode(int mode)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkColor(Color color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetHighlighterWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserSize(int size)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserType(int type)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserShape(int shape)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkAlpha(double alpha)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkStyle(int style)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetBackgroundColor(string color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsImage(string filePath, string format)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsPDF(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeselectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CopySelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CutSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Paste()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetSetting<T>(string key, T value)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void EnablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadPlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.UnloadPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (!_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventHandlers[eventName] += handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerEvent(string eventName, object sender, EventArgs args)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName]?.Invoke(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartApplication()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExitApplication()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CheckForUpdates()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void OpenHelpDocument()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void OpenAboutPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件模板,用于开发者参考
|
||||
/// 注意:实际开发时,请将此类移到单独的程序集中
|
||||
/// </summary>
|
||||
public class PluginTemplate : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => "插件模板";
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => "这是一个插件开发模板,用于开发者参考。";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => new Version(1, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "Your Name";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件(外部插件请返回false)
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
/// <summary>
|
||||
/// 插件初始化
|
||||
/// 在这里进行插件的初始化工作,如加载配置、注册事件等
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件ID和记录日志
|
||||
base.Initialize();
|
||||
|
||||
// TODO: 在这里进行插件初始化工作
|
||||
|
||||
// 示例:记录初始化信息
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 开始初始化");
|
||||
|
||||
// 示例:加载配置
|
||||
LoadConfig();
|
||||
|
||||
// 示例:注册自定义事件
|
||||
// MainWindow.Instance.SomeEvent += OnSomeEvent;
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 初始化完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 在这里激活插件功能
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Enable();
|
||||
|
||||
// TODO: 在这里启用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 在这里停用插件功能
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Disable();
|
||||
|
||||
// TODO: 在这里禁用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理资源
|
||||
/// 在插件卸载时调用,清理资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
// TODO: 在这里清理插件资源
|
||||
|
||||
// 示例:取消注册事件
|
||||
// MainWindow.Instance.SomeEvent -= OnSomeEvent;
|
||||
|
||||
// 示例:保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 最后调用基类方法
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件配置
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 从文件或其他位置加载配置
|
||||
// 示例:
|
||||
// string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json");
|
||||
// if (File.Exists(configPath))
|
||||
// {
|
||||
// string json = File.ReadAllText(configPath);
|
||||
// YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<YourConfigClass>(json);
|
||||
// }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
private void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 保存配置到文件或其他位置
|
||||
// 示例:
|
||||
// string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
// if (!Directory.Exists(configDir))
|
||||
// {
|
||||
// Directory.CreateDirectory(configDir);
|
||||
// }
|
||||
// string configPath = Path.Combine(configDir, "YourPluginName.json");
|
||||
// string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented);
|
||||
// File.WriteAllText(configPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件设置界面
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
// 创建插件设置界面
|
||||
return new PluginTemplateSettingsControl();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件功能方法
|
||||
|
||||
// TODO: 在这里添加插件的具体功能方法
|
||||
|
||||
/// <summary>
|
||||
/// 示例方法:执行一些功能
|
||||
/// </summary>
|
||||
public void DoSomething()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: 实现你的功能
|
||||
MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件设置控件
|
||||
/// </summary>
|
||||
public class PluginTemplateSettingsControl : UserControl
|
||||
{
|
||||
public PluginTemplateSettingsControl()
|
||||
{
|
||||
// 创建设置界面布局
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
// 添加标题
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "插件模板设置",
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
// 添加说明文字
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。",
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 0, 0, 15)
|
||||
});
|
||||
|
||||
// 添加示例设置选项
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = "启用某项功能",
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
panel.Children.Add(checkBox);
|
||||
|
||||
// 添加文本输入框
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "设置项:",
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
});
|
||||
|
||||
panel.Children.Add(new TextBox
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
Width = 200,
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
});
|
||||
|
||||
// 添加按钮
|
||||
var button = new Button
|
||||
{
|
||||
Content = "保存设置",
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
button.Click += (sender, e) =>
|
||||
{
|
||||
MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
};
|
||||
|
||||
panel.Children.Add(button);
|
||||
|
||||
// 设置控件内容
|
||||
Content = panel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,17 +366,21 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
/// <param name="root">要开始扫描的根目录路径。</param>
|
||||
/// <remarks>
|
||||
/// 仅处理扩展名为 `.exe`, `.dll`, `.config`, `.manifest`, `.dat`, `.enc` 的文件;会跳过被 IsExcludedPath 判定为排除的路径。遇到任何 I/O 或访问错误时会静默忽略,不会抛出异常。</remarks>
|
||||
/// 仅处理扩展名为 `.exe`, `.dll`, `.config`, `.manifest`, `.dat`, `.enc` 的文件,以及应用根目录下的点名名单 `Names.txt`;
|
||||
/// 会跳过被 IsExcludedPath 判定为排除的路径。遇到任何 I/O 或访问错误时会静默忽略,不会抛出异常。</remarks>
|
||||
private static void LockFilesRecursive(string root)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rollCallNamesPath = NormalizePath(Path.Combine(root, "Names.txt"));
|
||||
foreach (var file in Directory.GetFiles(root, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (!IsExcludedPath(file))
|
||||
{
|
||||
var ext = Path.GetExtension(file);
|
||||
if (string.Equals(ext, ".exe", StringComparison.OrdinalIgnoreCase) ||
|
||||
var normFile = NormalizePath(file);
|
||||
if (string.Equals(normFile, rollCallNamesPath, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ext, ".exe", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ext, ".dll", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ext, ".config", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(ext, ".manifest", StringComparison.OrdinalIgnoreCase) ||
|
||||
|
||||
@@ -11,25 +11,14 @@ using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class ROTPPTManager : IPPTLinkManager
|
||||
public class ROTPPTLinkManager : BasePPTLinkManager
|
||||
{
|
||||
#region Events
|
||||
public event Action<object> SlideShowBegin;
|
||||
public event Action<object> SlideShowNextSlide;
|
||||
public event Action<object> SlideShowEnd;
|
||||
public event Action<object> PresentationOpen;
|
||||
public event Action<object> PresentationClose;
|
||||
public event Action<bool> PPTConnectionChanged;
|
||||
public event Action<bool> SlideShowStateChanged;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public dynamic PPTApplication { get; private set; }
|
||||
public override dynamic PPTApplication { get; protected set; }
|
||||
public dynamic CurrentPresentation { get; private set; }
|
||||
public dynamic CurrentSlides { get; private set; }
|
||||
public dynamic CurrentSlide { get; private set; }
|
||||
public int SlidesCount { get; private set; }
|
||||
public bool IsConnected
|
||||
public override int SlidesCount { get; protected set; }
|
||||
public override bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -38,14 +27,12 @@ namespace Ink_Canvas.Helpers
|
||||
if (PPTApplication == null) return false;
|
||||
if (!Marshal.IsComObject(PPTApplication)) return false;
|
||||
|
||||
// 尝试访问一个简单的属性来验证连接是否有效
|
||||
var _ = PPTApplication.Name;
|
||||
return true;
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
// 如果COM对象已失效,返回false
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5)
|
||||
{
|
||||
return false;
|
||||
@@ -58,7 +45,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool IsInSlideShow
|
||||
public override bool IsInSlideShow
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -119,9 +106,8 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool IsSupportWPS { get; set; } = false;
|
||||
public bool SkipAnimationsWhenNavigating { get; set; } = false;
|
||||
#endregion
|
||||
public override bool IsSupportWPS { get; set; } = false;
|
||||
public override bool SkipAnimationsWhenNavigating { get; set; } = false;
|
||||
|
||||
#region Private Fields
|
||||
private Thread _monitoringThread;
|
||||
@@ -149,11 +135,11 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Constructor & Initialization
|
||||
public ROTPPTManager()
|
||||
public ROTPPTLinkManager()
|
||||
{
|
||||
}
|
||||
|
||||
public void StartMonitoring()
|
||||
public override void StartMonitoring()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -175,7 +161,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public void StopMonitoring()
|
||||
public override void StopMonitoring()
|
||||
{
|
||||
lock (_monitoringLock)
|
||||
{
|
||||
@@ -195,7 +181,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadConnection()
|
||||
public override void ReloadConnection()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
@@ -555,7 +541,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
|
||||
OnSlideShowNextSlide(_pptSlideShowWindow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -586,7 +572,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
|
||||
OnSlideShowNextSlide(_pptSlideShowWindow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -617,7 +603,7 @@ namespace Ink_Canvas.Helpers
|
||||
SlidesCount = 0;
|
||||
|
||||
_lastSlideShowState = false;
|
||||
SlideShowStateChanged?.Invoke(false);
|
||||
OnSlideShowStateChanged(false);
|
||||
|
||||
if (_pptActivePresentation != null)
|
||||
{
|
||||
@@ -964,7 +950,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace);
|
||||
SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
|
||||
OnSlideShowNextSlide(_pptSlideShowWindow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1008,7 +994,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"轮询模式检测到页码变化: {_lastPolledSlideNumber} -> {currentPage},触发事件", LogHelper.LogType.Trace);
|
||||
SlideShowNextSlide?.Invoke(_pptSlideShowWindow);
|
||||
OnSlideShowNextSlide(_pptSlideShowWindow);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1038,7 +1024,7 @@ namespace Ink_Canvas.Helpers
|
||||
SlidesCount = 0;
|
||||
|
||||
_lastSlideShowState = false;
|
||||
SlideShowStateChanged?.Invoke(false);
|
||||
OnSlideShowStateChanged(false);
|
||||
|
||||
if (_pptActivePresentation != null)
|
||||
{
|
||||
@@ -1074,7 +1060,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (currentSlideShowState != _lastSlideShowState)
|
||||
{
|
||||
_lastSlideShowState = currentSlideShowState;
|
||||
SlideShowStateChanged?.Invoke(currentSlideShowState);
|
||||
OnSlideShowStateChanged(currentSlideShowState);
|
||||
|
||||
if (!currentSlideShowState)
|
||||
{
|
||||
@@ -1252,7 +1238,7 @@ namespace Ink_Canvas.Helpers
|
||||
UpdateCurrentPresentationInfo();
|
||||
}
|
||||
|
||||
PPTConnectionChanged?.Invoke(true);
|
||||
OnPPTConnectionChanged(true);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1301,7 +1287,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (slideShowWindow == null)
|
||||
return;
|
||||
|
||||
SlideShowBegin?.Invoke(slideShowWindow);
|
||||
OnSlideShowBegin(slideShowWindow);
|
||||
LogHelper.WriteLogToFile("检测到放映已在进行中,热重载", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (COMException comEx)
|
||||
@@ -1344,10 +1330,22 @@ namespace Ink_Canvas.Helpers
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static bool IsObjectNull(object comObject)
|
||||
{
|
||||
return ReferenceEquals(comObject, null);
|
||||
}
|
||||
|
||||
private void DisconnectFromPPT()
|
||||
{
|
||||
if (PPTApplication == null && _pptActivePresentation == null && _pptSlideShowWindow == null &&
|
||||
CurrentPresentation == null && CurrentSlides == null && CurrentSlide == null)
|
||||
object pptApplication = PPTApplication;
|
||||
object activePresentation = _pptActivePresentation;
|
||||
object slideShowWindow = _pptSlideShowWindow;
|
||||
object currentPresentation = CurrentPresentation;
|
||||
object currentSlides = CurrentSlides;
|
||||
object currentSlide = CurrentSlide;
|
||||
|
||||
if (IsObjectNull(pptApplication) && IsObjectNull(activePresentation) && IsObjectNull(slideShowWindow) &&
|
||||
IsObjectNull(currentPresentation) && IsObjectNull(currentSlides) && IsObjectNull(currentSlide))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1362,7 +1360,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
PresentationClose?.Invoke(_pptActivePresentation);
|
||||
OnPresentationClose(_pptActivePresentation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1410,12 +1408,24 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPTApplication COM对象已失效,跳过释放", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (COMException comEx) when (IsIgnorableDisconnectComException(comEx))
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
$"PPTApplication COM对象在断开连接时已不可用,跳过释放 (HR: 0x{(uint)comEx.HResult:X8})",
|
||||
LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidComObjectException)
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPTApplication COM对象已失效,跳过释放", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (COMException comEx) when (IsIgnorableDisconnectComException(comEx))
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
$"释放PPTApplication时检测到COM对象已不可用,跳过释放 (HR: 0x{(uint)comEx.HResult:X8})",
|
||||
LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放PPTApplication时发生异常: {ex.Message}", LogHelper.LogType.Warning);
|
||||
@@ -1433,7 +1443,7 @@ namespace Ink_Canvas.Helpers
|
||||
_forcePolling = true;
|
||||
_bindingEvents = false;
|
||||
|
||||
PPTConnectionChanged?.Invoke(false);
|
||||
OnPPTConnectionChanged(false);
|
||||
|
||||
LogHelper.WriteLogToFile("已断开PPT连接,并显式释放所有COM对象", LogHelper.LogType.Event);
|
||||
|
||||
@@ -1500,6 +1510,13 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"COM对象 {objectName} 已失效,跳过释放", LogHelper.LogType.Trace);
|
||||
return;
|
||||
}
|
||||
catch (COMException comEx) when (IsIgnorableDisconnectComException(comEx))
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
$"COM对象 {objectName} 在释放前已不可用,跳过释放 (HR: 0x{(uint)comEx.HResult:X8})",
|
||||
LogHelper.LogType.Trace);
|
||||
return;
|
||||
}
|
||||
|
||||
int refCount = Marshal.ReleaseComObject(comObject);
|
||||
LogHelper.WriteLogToFile($"已释放COM对象 {objectName},引用计数: {refCount}", LogHelper.LogType.Trace);
|
||||
@@ -1513,7 +1530,8 @@ namespace Ink_Canvas.Helpers
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
|
||||
var logType = IsIgnorableDisconnectComException(comEx) ? LogHelper.LogType.Trace : LogHelper.LogType.Warning;
|
||||
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", logType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1521,6 +1539,15 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsIgnorableDisconnectComException(COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
return hr == 0x800706BA || // RPC server unavailable
|
||||
hr == 0x80010108 || // object disconnected from clients
|
||||
hr == 0x8001010D || // server died
|
||||
hr == 0x800706BE; // remote procedure call failed
|
||||
}
|
||||
|
||||
private void UpdateCurrentPresentationInfo()
|
||||
{
|
||||
object activeWindow = null;
|
||||
@@ -1735,7 +1762,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
UpdateCurrentPresentationInfo();
|
||||
PresentationOpen?.Invoke(pres);
|
||||
OnPresentationOpen(pres);
|
||||
LogHelper.WriteLogToFile($"演示文稿已打开: {pres?.Name}", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1780,7 +1807,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlideShowBegin(object wn)
|
||||
protected override void OnSlideShowBegin(object wn)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1813,10 +1840,10 @@ namespace Ink_Canvas.Helpers
|
||||
if (!_lastSlideShowState)
|
||||
{
|
||||
_lastSlideShowState = true;
|
||||
SlideShowStateChanged?.Invoke(true);
|
||||
OnSlideShowStateChanged(true);
|
||||
}
|
||||
|
||||
SlideShowBegin?.Invoke(wn);
|
||||
base.OnSlideShowBegin(wn);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1849,7 +1876,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
UpdateCurrentPresentationInfo();
|
||||
SlideShowNextSlide?.Invoke(wn);
|
||||
base.OnSlideShowNextSlide(wn);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1857,7 +1884,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSlideShowEnd(object pres)
|
||||
protected override void OnSlideShowEnd(object pres)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1879,10 +1906,10 @@ namespace Ink_Canvas.Helpers
|
||||
if (_lastSlideShowState)
|
||||
{
|
||||
_lastSlideShowState = false;
|
||||
SlideShowStateChanged?.Invoke(false);
|
||||
OnSlideShowStateChanged(false);
|
||||
}
|
||||
|
||||
SlideShowEnd?.Invoke(pres);
|
||||
base.OnSlideShowEnd(pres);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1897,7 +1924,7 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
public bool TryNavigateToSlide(int slideNumber)
|
||||
public override bool TryNavigateToSlide(int slideNumber)
|
||||
{
|
||||
object slideShowWindows = null;
|
||||
object slideShowWindow = null;
|
||||
@@ -1984,7 +2011,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryNavigateNext()
|
||||
public override bool TryNavigateNext()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2047,7 +2074,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryNavigatePrevious()
|
||||
public override bool TryNavigatePrevious()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2110,7 +2137,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryEndSlideShow()
|
||||
public override bool TryEndSlideShow()
|
||||
{
|
||||
object slideShowWindows = null;
|
||||
object slideShowWindow = null;
|
||||
@@ -2162,7 +2189,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryStartSlideShow()
|
||||
public override bool TryStartSlideShow()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2193,7 +2220,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 获取当前活跃的演示文稿
|
||||
/// </summary>
|
||||
public object GetCurrentActivePresentation()
|
||||
public override object GetCurrentActivePresentation()
|
||||
{
|
||||
object slideShowWindows = null;
|
||||
object slideShowWindow = null;
|
||||
@@ -2371,7 +2398,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public int GetCurrentSlideNumber()
|
||||
public override int GetCurrentSlideNumber()
|
||||
{
|
||||
object activeWindow = null;
|
||||
object selection = null;
|
||||
@@ -2442,7 +2469,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public string GetPresentationName()
|
||||
public override string GetPresentationName()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2494,7 +2521,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryShowSlideNavigation()
|
||||
public override bool TryShowSlideNavigation()
|
||||
{
|
||||
object slideShowWindows = null;
|
||||
object slideShowWindow = null;
|
||||
@@ -2979,7 +3006,7 @@ namespace Ink_Canvas.Helpers
|
||||
SlidesCount = 0;
|
||||
StopWpsProcessCheckTimer();
|
||||
|
||||
PPTConnectionChanged?.Invoke(false);
|
||||
OnPPTConnectionChanged(false);
|
||||
|
||||
LogHelper.WriteLogToFile("WPS进程结束后已清理所有COM对象并重启连接检查", LogHelper.LogType.Event);
|
||||
}
|
||||
@@ -3501,7 +3528,7 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
public void Dispose()
|
||||
public override void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
@@ -3516,4 +3543,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,16 @@ namespace Ink_Canvas.Helpers
|
||||
public static bool IsPasswordRequiredForResetConfig(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnResetConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 指示在修改或清空点名名单前是否需要输入安全密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用设置对象。</param>
|
||||
/// <returns>当已启用密码功能、已配置密码且开启了对应开关时返回 true;否则返回 false。</returns>
|
||||
public static bool IsPasswordRequiredForModifyOrClearNameList(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings)
|
||||
&& HasPasswordConfigured(settings)
|
||||
&& settings.Security.RequirePasswordOnModifyOrClearNameList;
|
||||
|
||||
/// <summary>
|
||||
/// 将提供的明文密码与 Settings 中存储的密码散列进行比对以验证密码是否正确。
|
||||
/// </summary>
|
||||
|
||||
@@ -1,72 +1,148 @@
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class SoftwareLauncher
|
||||
internal static class SoftwareLauncher
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
/// <summary>与 ICA 一致:在「程序和功能」卸载列表中按 DisplayName 匹配后启动 sweclauncher.exe。</summary>
|
||||
public static void LaunchEasiCamera(string softwareName)
|
||||
{
|
||||
string executablePath = FindEasiCameraExecutablePath(softwareName);
|
||||
|
||||
if (!string.IsNullOrEmpty(executablePath))
|
||||
if (string.IsNullOrEmpty(executablePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(executablePath);
|
||||
//Console.WriteLine(softwareName + " 启动成功!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("启动失败: " + ex.Message);
|
||||
//MessageBox.Show("启动失败: " + ex.Message);
|
||||
}
|
||||
MessageBox.Show(
|
||||
"未找到希沃视频展台安装信息(已扫描 64 位与 32 位卸载注册表)。请确认已通过官方安装包安装「希沃视频展台」。",
|
||||
"Ink Canvas",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(executablePath);
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = executablePath,
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = string.IsNullOrEmpty(directory) ? Environment.SystemDirectory : directory
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"无法启动希沃视频展台:" + ex.Message,
|
||||
"Ink Canvas",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning);
|
||||
}
|
||||
//Console.WriteLine(softwareName + " 未找到可执行文件路径。");
|
||||
}
|
||||
|
||||
private static string FindEasiCameraExecutablePath(string softwareName)
|
||||
{
|
||||
string executablePath = null;
|
||||
if (string.IsNullOrWhiteSpace(softwareName))
|
||||
return null;
|
||||
|
||||
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall"))
|
||||
// 须用 OpenBaseKey + RegistryView 显式指定视图:Registry.LocalMachine.OpenSubKey 跟随进程位数,
|
||||
// 32 位进程下无法靠拼接 WOW6432Node 路径进入 64 位视图,会找不到 64 位安装的展台。
|
||||
const string uninstallSubKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
|
||||
foreach (RegistryView view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
|
||||
{
|
||||
foreach (string subkeyName in key.GetSubKeyNames())
|
||||
using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
|
||||
using (RegistryKey key = baseKey.OpenSubKey(uninstallSubKey))
|
||||
{
|
||||
using (RegistryKey subkey = key.OpenSubKey(subkeyName))
|
||||
{
|
||||
string displayName = subkey.GetValue("DisplayName") as string;
|
||||
string installLocation = subkey.GetValue("InstallLocation") as string;
|
||||
string uninstallString = subkey.GetValue("UninstallString") as string;
|
||||
|
||||
if (!string.IsNullOrEmpty(displayName) && displayName.Contains(softwareName))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
{
|
||||
executablePath = Path.Combine(installLocation, "sweclauncher.exe");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(uninstallString))
|
||||
{
|
||||
int lastSlashIndex = uninstallString.LastIndexOf("\\");
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
string folderPath = uninstallString.Substring(0, lastSlashIndex);
|
||||
executablePath = Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (key == null) continue;
|
||||
string found = FindInUninstallKey(key, softwareName);
|
||||
if (!string.IsNullOrEmpty(found))
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return executablePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string FindInUninstallKey(RegistryKey uninstallKey, string softwareName)
|
||||
{
|
||||
foreach (string subkeyName in uninstallKey.GetSubKeyNames())
|
||||
{
|
||||
using (RegistryKey subkey = uninstallKey.OpenSubKey(subkeyName))
|
||||
{
|
||||
if (subkey == null) continue;
|
||||
|
||||
string displayName = subkey.GetValue("DisplayName") as string;
|
||||
if (string.IsNullOrEmpty(displayName) || !displayName.Contains(softwareName))
|
||||
continue;
|
||||
|
||||
string installLocation = subkey.GetValue("InstallLocation") as string;
|
||||
string uninstallString = subkey.GetValue("UninstallString") as string;
|
||||
|
||||
string resolved = TryResolveSweclauncher(installLocation, uninstallString);
|
||||
if (!string.IsNullOrEmpty(resolved) && File.Exists(resolved))
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string TryResolveSweclauncher(string installLocation, string uninstallString)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(installLocation))
|
||||
{
|
||||
string fromLoc = ResolveSweclauncherUnderInstallRoot(installLocation.Trim().TrimEnd('\\'));
|
||||
if (!string.IsNullOrEmpty(fromLoc))
|
||||
return fromLoc;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(uninstallString))
|
||||
{
|
||||
// 常见:"...\uninstall.exe" 或带引号路径
|
||||
string trimmed = uninstallString.Trim();
|
||||
if (trimmed.Length >= 2 && trimmed[0] == '"')
|
||||
{
|
||||
int end = trimmed.IndexOf('"', 1);
|
||||
if (end > 1)
|
||||
trimmed = trimmed.Substring(1, end - 1);
|
||||
}
|
||||
|
||||
int lastSlash = trimmed.LastIndexOf('\\');
|
||||
if (lastSlash < 0)
|
||||
return null;
|
||||
|
||||
string folderPath = trimmed.Substring(0, lastSlash);
|
||||
string candidate = Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
|
||||
candidate = Path.Combine(folderPath, "sweclauncher.exe");
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ResolveSweclauncherUnderInstallRoot(string installRoot)
|
||||
{
|
||||
string[] candidates =
|
||||
{
|
||||
Path.Combine(installRoot, "sweclauncher.exe"),
|
||||
Path.Combine(installRoot, "sweclauncher", "sweclauncher.exe"),
|
||||
};
|
||||
|
||||
foreach (string p in candidates)
|
||||
{
|
||||
if (File.Exists(p))
|
||||
return p;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
@@ -100,8 +100,8 @@ namespace Ink_Canvas.Helpers
|
||||
var item = _currentStrokeHistory[_currentIndex];
|
||||
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
|
||||
_currentIndex--;
|
||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
||||
OnUndoStateChanged?.Invoke(CanUndo);
|
||||
OnRedoStateChanged?.Invoke(CanRedo);
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -137,9 +137,15 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
private void NotifyUndoRedoState()
|
||||
{
|
||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
||||
OnUndoStateChanged?.Invoke(CanUndo);
|
||||
OnRedoStateChanged?.Invoke(CanRedo);
|
||||
}
|
||||
|
||||
/// <summary>当前历史是否允许撤销。</summary>
|
||||
public bool CanUndo => _currentIndex > -1;
|
||||
|
||||
/// <summary>当前历史是否允许重做。</summary>
|
||||
public bool CanRedo => _currentStrokeHistory.Count > 0 && _currentStrokeHistory.Count - _currentIndex - 1 > 0;
|
||||
}
|
||||
|
||||
public class TimeMachineHistory
|
||||
|
||||
@@ -0,0 +1,848 @@
|
||||
using OSVersionExtension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using WinAnalysis = global::Windows.UI.Input.Inking.Analysis;
|
||||
using WinRtInk = global::Windows.UI.Input.Inking;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// WinRT 手写体识别,以及将识别结果用手写风格字体轮廓转为墨迹笔画(「识别转手写体字形」)。
|
||||
/// </summary>
|
||||
internal static class WinRtHandwritingRecognizer
|
||||
{
|
||||
private static WinRtInk.InkRecognizer _preferredHandwritingRecognizer;
|
||||
private static bool _preferredHandwritingRecognizerResolved;
|
||||
|
||||
private static void LogHandwriting(string message, LogHelper.LogType logType = LogHelper.LogType.Info)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] " + message, logType);
|
||||
}
|
||||
|
||||
public static bool IsApiAvailable =>
|
||||
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||
|
||||
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>
|
||||
/// 将当前笔画集合识别为文字片段(含候选):先用墨迹分析得到分词与 <see cref="WinAnalysis.InkAnalysisInkWord.RecognizedText"/>,
|
||||
/// 再对每一分词用 <see cref="WinRtInk.InkRecognizerContainer"/> 取 <c>GetTextCandidates</c>(与当前 SDK 中部分版本的
|
||||
/// <see cref="WinRtInk.InkRecognitionResult"/> 未暴露笔画映射的局限兼容)。
|
||||
/// </summary>
|
||||
/// <param name="verboseTrace">为 false 时跳过详细识别日志(用于 <see cref="Warmup"/> 等)。</param>
|
||||
public static async Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
||||
StrokeCollection strokes,
|
||||
bool verboseTrace = true)
|
||||
{
|
||||
if (!IsApiAvailable || strokes == null || strokes.Count == 0)
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
|
||||
var traceRecognition = verboseTrace;
|
||||
|
||||
try
|
||||
{
|
||||
var recognizer = new WinRtInk.InkRecognizerContainer();
|
||||
TryApplyPreferredHandwritingRecognizer(recognizer, traceRecognition);
|
||||
|
||||
var analyzer = new WinAnalysis.InkAnalyzer();
|
||||
var idToWpf = new Dictionary<uint, Stroke>();
|
||||
|
||||
foreach (Stroke s in strokes)
|
||||
{
|
||||
var ink = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(s);
|
||||
if (ink == null) continue;
|
||||
analyzer.AddDataForStroke(ink);
|
||||
analyzer.SetStrokeDataKind(ink.Id, WinAnalysis.InkAnalysisStrokeKind.Writing);
|
||||
idToWpf[ink.Id] = s;
|
||||
}
|
||||
|
||||
if (idToWpf.Count == 0)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("识别:无有效 WinRT 笔画(全部转换失败),输入笔画数=" + strokes.Count);
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var analysisResult = await analyzer.AnalyzeAsync().AsTask().ConfigureAwait(true);
|
||||
if (analysisResult == null || analysisResult.Status != WinAnalysis.InkAnalysisStatus.Updated)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting(
|
||||
"识别:AnalyzeAsync 未得到 Updated,Status=" +
|
||||
(analysisResult == null ? "null" : analysisResult.Status.ToString()) +
|
||||
",有效笔画数=" + idToWpf.Count +
|
||||
",尝试整批 RecognizeAsync 回退。");
|
||||
return await RecognizeHandwritingWholeInkAsync(strokes, traceRecognition).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
var wordNodes = analyzer.AnalysisRoot?.FindNodes(WinAnalysis.InkAnalysisNodeKind.InkWord);
|
||||
if (wordNodes == null || wordNodes.Count == 0)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting(
|
||||
"识别:未找到 InkWord 节点(墨迹分析常将非横平笔划判为绘图),有效笔画数=" + idToWpf.Count +
|
||||
",改用整批 RecognizeAsync 回退。");
|
||||
return await RecognizeHandwritingWholeInkAsync(strokes, traceRecognition).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
var segments = new List<HandwritingWordSegment>();
|
||||
|
||||
foreach (var node in wordNodes)
|
||||
{
|
||||
if (!(node is WinAnalysis.InkAnalysisInkWord word))
|
||||
continue;
|
||||
|
||||
var ids = word.GetStrokeIds();
|
||||
if (ids == null || ids.Count == 0)
|
||||
continue;
|
||||
|
||||
var group = new List<Stroke>();
|
||||
foreach (var sid in ids)
|
||||
{
|
||||
if (idToWpf.TryGetValue(sid, out var st))
|
||||
group.Add(st);
|
||||
}
|
||||
|
||||
if (group.Count == 0)
|
||||
continue;
|
||||
|
||||
var wbr = word.BoundingRect;
|
||||
var wpfRect = new Rect(wbr.X, wbr.Y, wbr.Width, wbr.Height);
|
||||
var analysisText = word.RecognizedText ?? string.Empty;
|
||||
|
||||
IReadOnlyList<string> candList = Array.Empty<string>();
|
||||
try
|
||||
{
|
||||
if (recognizer != null)
|
||||
{
|
||||
var mini = new WinRtInk.InkStrokeContainer();
|
||||
foreach (var st in group)
|
||||
{
|
||||
var ink = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(st);
|
||||
if (ink != null)
|
||||
mini.AddStroke(ink);
|
||||
}
|
||||
|
||||
var miniStrokes = mini.GetStrokes();
|
||||
if (miniStrokes != null && miniStrokes.Count > 0)
|
||||
{
|
||||
var rr = await recognizer
|
||||
.RecognizeAsync(mini, WinRtInk.InkRecognitionTarget.All)
|
||||
.AsTask()
|
||||
.ConfigureAwait(true);
|
||||
if (rr != null && rr.Count > 0 && rr[0] != null)
|
||||
{
|
||||
var cands = rr[0].GetTextCandidates();
|
||||
if (cands != null && cands.Count > 0)
|
||||
candList = cands.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
candList = Array.Empty<string>();
|
||||
}
|
||||
|
||||
var primary = candList.Count > 0 ? candList[0] : analysisText;
|
||||
var mergedCandidates = new List<string>();
|
||||
if (candList.Count > 0)
|
||||
{
|
||||
foreach (var c in candList)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(c) && !mergedCandidates.Contains(c))
|
||||
mergedCandidates.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(analysisText) && !mergedCandidates.Contains(analysisText))
|
||||
mergedCandidates.Insert(0, analysisText);
|
||||
|
||||
if (mergedCandidates.Count == 0 && !string.IsNullOrEmpty(primary))
|
||||
mergedCandidates.Add(primary);
|
||||
|
||||
segments.Add(new HandwritingWordSegment(
|
||||
primary,
|
||||
mergedCandidates,
|
||||
wpfRect,
|
||||
group));
|
||||
}
|
||||
|
||||
if (segments.Count == 0)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("识别:分词列表为空(InkWord 无有效笔画映射)。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var hr = new HandwritingRecognitionResult(segments);
|
||||
if (traceRecognition)
|
||||
{
|
||||
var preview = hr.CombinedText;
|
||||
if (preview.Length > 120)
|
||||
preview = preview.Substring(0, 117) + "...";
|
||||
LogHandwriting(
|
||||
"识别成功:词数=" + segments.Count +
|
||||
",合并文本=\"" + preview + "\"" +
|
||||
",进程位数=" + (Environment.Is64BitProcess ? "x64" : "x86"));
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var seg = segments[i];
|
||||
var t = seg.Text ?? "";
|
||||
if (t.Length > 40)
|
||||
t = t.Substring(0, 37) + "...";
|
||||
LogHandwriting(
|
||||
" 词[" + i + "] 文本=\"" + t + "\",笔画数=" + seg.Strokes.Count +
|
||||
",候选数=" + (seg.TextCandidates?.Count ?? 0) +
|
||||
",框=(" + Math.Round(seg.BoundingRectangle.X, 1) + "," +
|
||||
Math.Round(seg.BoundingRectangle.Y, 1) + "," +
|
||||
Math.Round(seg.BoundingRectangle.Width, 1) + "×" +
|
||||
Math.Round(seg.BoundingRectangle.Height, 1) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("WinRT 手写识别失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||
if (strokes != null && strokes.Count > 0)
|
||||
LogHandwriting("识别异常:" + ex.Message, LogHelper.LogType.Warning);
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryApplyPreferredHandwritingRecognizer(
|
||||
WinRtInk.InkRecognizerContainer container,
|
||||
bool logDetail)
|
||||
{
|
||||
if (container == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (!_preferredHandwritingRecognizerResolved)
|
||||
{
|
||||
_preferredHandwritingRecognizerResolved = true;
|
||||
var all = container.GetRecognizers();
|
||||
_preferredHandwritingRecognizer = SelectBestInkRecognizer(all);
|
||||
if (logDetail)
|
||||
{
|
||||
if (_preferredHandwritingRecognizer != null)
|
||||
LogHandwriting("识别器:已选用 \"" + _preferredHandwritingRecognizer.Name + "\"。");
|
||||
else if (all != null && all.Count > 0)
|
||||
LogHandwriting("识别器:未匹配到与 UI/区域语言对应的引擎,使用系统默认(共 " + all.Count + " 个)。");
|
||||
}
|
||||
}
|
||||
|
||||
if (_preferredHandwritingRecognizer != null)
|
||||
container.SetDefaultRecognizer(_preferredHandwritingRecognizer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] 设置默认手写识别器失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private static WinRtInk.InkRecognizer SelectBestInkRecognizer(
|
||||
IReadOnlyList<WinRtInk.InkRecognizer> list)
|
||||
{
|
||||
if (list == null || list.Count == 0)
|
||||
return null;
|
||||
|
||||
var culture = PrimaryHandwritingCulture();
|
||||
var lang = (culture?.TwoLetterISOLanguageName ?? string.Empty).ToLowerInvariant();
|
||||
var name = culture?.Name ?? string.Empty;
|
||||
|
||||
bool wantZhHans = lang == "zh" &&
|
||||
(name.IndexOf("hans", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
name.Equals("zh-cn", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("zh-sg", StringComparison.OrdinalIgnoreCase) ||
|
||||
(name.IndexOf("hant", StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||
!name.Equals("zh-tw", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.Equals("zh-hk", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.Equals("zh-mo", StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
bool wantZhHant = lang == "zh" &&
|
||||
(name.IndexOf("hant", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
name.Equals("zh-tw", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("zh-hk", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("zh-mo", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
WinRtInk.InkRecognizer Pick(Func<string, bool> match)
|
||||
{
|
||||
foreach (var r in list)
|
||||
{
|
||||
var n = r?.Name;
|
||||
if (string.IsNullOrEmpty(n))
|
||||
continue;
|
||||
if (match(n))
|
||||
return r;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (wantZhHans)
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("简体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("簡體", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
(n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("简体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("簡體", StringComparison.OrdinalIgnoreCase) >= 0)) ||
|
||||
(n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("Simplified", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Hans", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("PRC", StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||
if (r != null)
|
||||
return r;
|
||||
r = Pick(n =>
|
||||
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
else if (wantZhHant)
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("繁体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("繁體", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
(n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("繁体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("繁體", StringComparison.OrdinalIgnoreCase) >= 0)) ||
|
||||
(n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("Traditional", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Hant", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Taiwan", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Hong Kong", StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||
if (r != null)
|
||||
return r;
|
||||
r = Pick(n =>
|
||||
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
else if (lang == "ja")
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("Japanese", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("日本語", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("日语", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
else if (lang == "en")
|
||||
{
|
||||
var r = Pick(n => n.IndexOf("English", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (lang == "zh")
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CultureInfo PrimaryHandwritingCulture()
|
||||
{
|
||||
var ui = CultureInfo.CurrentUICulture;
|
||||
var ct = CultureInfo.CurrentCulture;
|
||||
if (string.Equals(ui.TwoLetterISOLanguageName, "zh", StringComparison.OrdinalIgnoreCase))
|
||||
return ui;
|
||||
if (string.Equals(ct.TwoLetterISOLanguageName, "zh", StringComparison.OrdinalIgnoreCase))
|
||||
return ct;
|
||||
return ui;
|
||||
}
|
||||
|
||||
private static async Task<HandwritingRecognitionResult> RecognizeHandwritingWholeInkAsync(
|
||||
StrokeCollection strokes,
|
||||
bool traceRecognition)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
|
||||
var container = new WinRtInk.InkStrokeContainer();
|
||||
foreach (Stroke s in strokes)
|
||||
{
|
||||
var ink = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(s);
|
||||
if (ink != null)
|
||||
container.AddStroke(ink);
|
||||
}
|
||||
|
||||
var winStrokes = container.GetStrokes();
|
||||
if (winStrokes == null || winStrokes.Count == 0)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:无有效 WinRT 笔画。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var reco = new WinRtInk.InkRecognizerContainer();
|
||||
TryApplyPreferredHandwritingRecognizer(reco, false);
|
||||
|
||||
IReadOnlyList<WinRtInk.InkRecognitionResult> rr;
|
||||
try
|
||||
{
|
||||
rr = await reco
|
||||
.RecognizeAsync(container, WinRtInk.InkRecognitionTarget.All)
|
||||
.AsTask()
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:RecognizeAsync 异常:" + ex.Message);
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
if (rr == null || rr.Count == 0 || rr[0] == null)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:RecognizeAsync 无结果。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var cands = rr[0].GetTextCandidates();
|
||||
var primary = (cands != null && cands.Count > 0) ? cands[0] : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(primary))
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:候选文本为空。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var merged = new List<string>();
|
||||
if (cands != null)
|
||||
{
|
||||
foreach (var c in cands)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(c) && !merged.Contains(c))
|
||||
merged.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = UnionStrokeBounds(strokes);
|
||||
var group = new List<Stroke>();
|
||||
foreach (Stroke s in strokes)
|
||||
group.Add(s);
|
||||
|
||||
var seg = new HandwritingWordSegment(primary, merged, bounds, group);
|
||||
return new HandwritingRecognitionResult(new List<HandwritingWordSegment> { seg });
|
||||
}
|
||||
|
||||
private static Rect UnionStrokeBounds(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return Rect.Empty;
|
||||
|
||||
var r = strokes[0].GetBounds();
|
||||
for (var i = 1; i < strokes.Count; i++)
|
||||
r = Rect.Union(r, strokes[i].GetBounds());
|
||||
return r;
|
||||
}
|
||||
|
||||
private const string DefaultHandwritingFontFamilyList = "Ink Free,KaiTi,Segoe Script";
|
||||
|
||||
/// <summary>
|
||||
/// 识别手写词后,将「有识别文本」的分词替换为指定手写风格字体的字形轮廓墨迹;未识别或空文本的词保留原笔画。
|
||||
/// </summary>
|
||||
public static async Task<StrokeCollection> ConvertRecognizedTextToHandwritingInkAsync(
|
||||
StrokeCollection strokes,
|
||||
string handwritingFontFamilyList)
|
||||
{
|
||||
if (!IsApiAvailable || strokes == null || strokes.Count == 0)
|
||||
{
|
||||
if (strokes != null && strokes.Count > 0 && !IsApiAvailable)
|
||||
LogHandwriting("字形替换:跳过,IsApiAvailable=false。");
|
||||
return strokes;
|
||||
}
|
||||
|
||||
var fontList = string.IsNullOrWhiteSpace(handwritingFontFamilyList)
|
||||
? DefaultHandwritingFontFamilyList
|
||||
: handwritingFontFamilyList.Trim();
|
||||
LogHandwriting(
|
||||
"字形替换开始:输入笔画数=" + strokes.Count +
|
||||
",字体链=\"" + fontList + "\"" +
|
||||
",PixelsPerDip=" + Math.Round(GetPixelsPerDipSafe(), 3));
|
||||
|
||||
try
|
||||
{
|
||||
var reco = await RecognizeHandwritingAsync(strokes).ConfigureAwait(true);
|
||||
if (!reco.IsSuccess || reco.Words == null || reco.Words.Count == 0)
|
||||
{
|
||||
LogHandwriting(
|
||||
"字形替换中止:识别未成功(IsSuccess=" + reco.IsSuccess +
|
||||
",词数=" + (reco.Words?.Count ?? 0) + "),原样返回笔画。");
|
||||
return strokes;
|
||||
}
|
||||
|
||||
var firstStrokeToSegment = new Dictionary<Stroke, HandwritingWordSegment>();
|
||||
foreach (var w in reco.Words)
|
||||
{
|
||||
if (w?.Strokes == null || w.Strokes.Count == 0)
|
||||
continue;
|
||||
var ordered = w.Strokes.OrderBy(st => IndexOfStrokeInCollection(strokes, st)).ToList();
|
||||
var first = ordered[0];
|
||||
if (!firstStrokeToSegment.ContainsKey(first))
|
||||
firstStrokeToSegment[first] = w;
|
||||
}
|
||||
|
||||
if (firstStrokeToSegment.Count == 0)
|
||||
{
|
||||
LogHandwriting("字形替换中止:无法建立「首笔画→分词」映射,原样返回。");
|
||||
return strokes;
|
||||
}
|
||||
|
||||
var consumed = new HashSet<Stroke>();
|
||||
var result = new StrokeCollection();
|
||||
var pixelsPerDip = GetPixelsPerDipSafe();
|
||||
var replacedWordCount = 0;
|
||||
var keptOriginalWordCount = 0;
|
||||
var glyphStrokeTotal = 0;
|
||||
|
||||
foreach (Stroke s in strokes)
|
||||
{
|
||||
if (consumed.Contains(s))
|
||||
continue;
|
||||
|
||||
if (!firstStrokeToSegment.TryGetValue(s, out var seg))
|
||||
{
|
||||
result.Add(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(seg.Text))
|
||||
{
|
||||
LogHandwriting(
|
||||
" 分词:文本为空,保留原笔画,笔画数=" + seg.Strokes.Count);
|
||||
keptOriginalWordCount++;
|
||||
foreach (var z in seg.Strokes)
|
||||
{
|
||||
if (!consumed.Contains(z))
|
||||
{
|
||||
result.Add(z);
|
||||
consumed.Add(z);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var templateDa = seg.Strokes[0]?.DrawingAttributes?.Clone() ?? new DrawingAttributes();
|
||||
OutlineAttributesForGlyphInk(templateDa);
|
||||
|
||||
var glyphStrokes = CreateHandwritingGlyphStrokes(
|
||||
seg.Text.Trim(),
|
||||
seg.BoundingRectangle,
|
||||
templateDa,
|
||||
fontList,
|
||||
pixelsPerDip);
|
||||
|
||||
if (glyphStrokes == null || glyphStrokes.Count == 0)
|
||||
{
|
||||
LogHandwriting(
|
||||
" 分词:字形轮廓生成失败,保留原笔画。文本=\"" +
|
||||
(seg.Text.Length > 30 ? seg.Text.Substring(0, 27) + "..." : seg.Text) + "\"");
|
||||
keptOriginalWordCount++;
|
||||
foreach (var z in seg.Strokes)
|
||||
{
|
||||
if (!consumed.Contains(z))
|
||||
{
|
||||
result.Add(z);
|
||||
consumed.Add(z);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var nk in glyphStrokes)
|
||||
result.Add(nk);
|
||||
glyphStrokeTotal += glyphStrokes.Count;
|
||||
replacedWordCount++;
|
||||
LogHandwriting(
|
||||
" 分词:已替换为手写体字形墨迹,文本=\"" +
|
||||
(seg.Text.Length > 30 ? seg.Text.Substring(0, 27) + "..." : seg.Text) +
|
||||
"\",生成笔画数=" + glyphStrokes.Count + ",移除原笔画数=" + seg.Strokes.Count);
|
||||
|
||||
foreach (var z in seg.Strokes)
|
||||
consumed.Add(z);
|
||||
}
|
||||
|
||||
LogHandwriting(
|
||||
"字形替换结束:输出笔画数=" + result.Count +
|
||||
"(输入=" + strokes.Count + "),替换词数=" + replacedWordCount +
|
||||
",保留原迹词数=" + keptOriginalWordCount +
|
||||
",字形子笔画合计=" + glyphStrokeTotal);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("WinRT 手写体字形替换失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||
LogHandwriting("字形替换异常:" + ex, LogHelper.LogType.Warning);
|
||||
return strokes;
|
||||
}
|
||||
}
|
||||
|
||||
private static int IndexOfStrokeInCollection(StrokeCollection collection, Stroke stroke)
|
||||
{
|
||||
if (collection == null || stroke == null)
|
||||
return int.MaxValue;
|
||||
for (var i = 0; i < collection.Count; i++)
|
||||
{
|
||||
if (ReferenceEquals(collection[i], stroke))
|
||||
return i;
|
||||
}
|
||||
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
private static void OutlineAttributesForGlyphInk(DrawingAttributes da)
|
||||
{
|
||||
if (da == null) return;
|
||||
var w = Math.Max(0.8, Math.Min(da.Width, da.Height) * 0.2);
|
||||
da.Width = w;
|
||||
da.Height = w;
|
||||
da.StylusTip = StylusTip.Ellipse;
|
||||
da.IsHighlighter = false;
|
||||
}
|
||||
|
||||
private static double GetPixelsPerDipSafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Application.Current?.MainWindow is Visual v)
|
||||
return VisualTreeHelper.GetDpi(v).PixelsPerDip;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
private static Typeface ResolveHandwritingTypeface(string fontFamilyList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ff = new FontFamily(fontFamilyList ?? DefaultHandwritingFontFamilyList);
|
||||
return new Typeface(ff, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Typeface(
|
||||
SystemFonts.MessageFontFamily,
|
||||
SystemFonts.MessageFontStyle,
|
||||
SystemFonts.MessageFontWeight,
|
||||
FontStretches.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Stroke> CreateHandwritingGlyphStrokes(
|
||||
string text,
|
||||
Rect placeRect,
|
||||
DrawingAttributes templateDa,
|
||||
string fontFamilyList,
|
||||
double pixelsPerDip)
|
||||
{
|
||||
var list = new List<Stroke>();
|
||||
if (string.IsNullOrEmpty(text) || placeRect.Width < 1 || placeRect.Height < 1)
|
||||
return list;
|
||||
|
||||
var typeface = ResolveHandwritingTypeface(fontFamilyList);
|
||||
var culture = CultureInfo.CurrentCulture;
|
||||
var em = Math.Max(6.0, placeRect.Height * 0.72);
|
||||
FormattedText ft = null;
|
||||
|
||||
for (var i = 0; i < 14; i++)
|
||||
{
|
||||
ft = new FormattedText(
|
||||
text,
|
||||
culture,
|
||||
FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
em,
|
||||
Brushes.Black,
|
||||
new NumberSubstitution(NumberCultureSource.Text, culture, NumberSubstitutionMethod.Context),
|
||||
TextFormattingMode.Display,
|
||||
pixelsPerDip);
|
||||
|
||||
if (ft.Width <= placeRect.Width * 0.96 && ft.Height <= placeRect.Height * 0.96)
|
||||
break;
|
||||
|
||||
em *= 0.9;
|
||||
if (em < 4.5)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ft == null || ft.Width < 0.5 || ft.Height < 0.5)
|
||||
return list;
|
||||
|
||||
var scale = Math.Min(
|
||||
placeRect.Width * 0.94 / Math.Max(1e-6, ft.Width),
|
||||
placeRect.Height * 0.94 / Math.Max(1e-6, ft.Height));
|
||||
var tx = placeRect.Left + (placeRect.Width - ft.Width * scale) / 2.0;
|
||||
var ty = placeRect.Top + (placeRect.Height - ft.Height * scale) / 2.0;
|
||||
|
||||
Geometry geom;
|
||||
try
|
||||
{
|
||||
geom = ft.BuildGeometry(new Point(0, 0));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
if (geom == null || geom.IsEmpty())
|
||||
return list;
|
||||
|
||||
var m = new Matrix(scale, 0, 0, scale, tx, ty);
|
||||
geom.Transform = new MatrixTransform(m);
|
||||
return StrokesFromOutlinedGeometry(geom, templateDa, 0.35);
|
||||
}
|
||||
|
||||
private static List<Stroke> StrokesFromOutlinedGeometry(Geometry geometry, DrawingAttributes da, double tolerance)
|
||||
{
|
||||
var list = new List<Stroke>();
|
||||
if (geometry == null || geometry.IsEmpty() || da == null)
|
||||
return list;
|
||||
|
||||
Geometry outlined;
|
||||
try
|
||||
{
|
||||
outlined = geometry.GetOutlinedPathGeometry(tolerance, ToleranceType.Absolute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
if (outlined == null || outlined.IsEmpty())
|
||||
return list;
|
||||
|
||||
Geometry flat;
|
||||
try
|
||||
{
|
||||
flat = outlined.GetFlattenedPathGeometry(tolerance, ToleranceType.Absolute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
if (!(flat is PathGeometry pg))
|
||||
return list;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>单个手写词片段的识别结果。</summary>
|
||||
public sealed class HandwritingWordSegment
|
||||
{
|
||||
public HandwritingWordSegment(
|
||||
string text,
|
||||
IReadOnlyList<string> textCandidates,
|
||||
Rect boundingRectangle,
|
||||
IReadOnlyList<Stroke> strokes)
|
||||
{
|
||||
Text = text ?? string.Empty;
|
||||
TextCandidates = textCandidates ?? Array.Empty<string>();
|
||||
BoundingRectangle = boundingRectangle;
|
||||
Strokes = strokes ?? Array.Empty<Stroke>();
|
||||
}
|
||||
|
||||
public string Text { get; }
|
||||
public IReadOnlyList<string> TextCandidates { get; }
|
||||
public Rect BoundingRectangle { get; }
|
||||
public IReadOnlyList<Stroke> Strokes { get; }
|
||||
}
|
||||
|
||||
/// <summary>一次手写识别批次的汇总结果。</summary>
|
||||
public sealed class HandwritingRecognitionResult
|
||||
{
|
||||
public static readonly HandwritingRecognitionResult Empty = new HandwritingRecognitionResult();
|
||||
|
||||
private HandwritingRecognitionResult()
|
||||
{
|
||||
Words = Array.Empty<HandwritingWordSegment>();
|
||||
IsSuccess = false;
|
||||
CombinedText = string.Empty;
|
||||
}
|
||||
|
||||
public HandwritingRecognitionResult(IReadOnlyList<HandwritingWordSegment> words)
|
||||
{
|
||||
Words = words ?? Array.Empty<HandwritingWordSegment>();
|
||||
IsSuccess = Words.Count > 0;
|
||||
CombinedText = string.Join("", Words.Select(w => w.Text ?? string.Empty));
|
||||
}
|
||||
|
||||
public bool IsSuccess { get; }
|
||||
public IReadOnlyList<HandwritingWordSegment> Words { get; }
|
||||
public string CombinedText { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
using OSVersionExtension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using SysPoint = System.Windows.Point;
|
||||
using WinRtInkAnalyzer = global::Windows.UI.Input.Inking.Analysis.InkAnalyzer;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。</summary>
|
||||
internal static class WinRtInkShapeRecognizer
|
||||
{
|
||||
public static bool IsApiAvailable =>
|
||||
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||
|
||||
public static void Warmup()
|
||||
{
|
||||
if (!IsApiAvailable) return;
|
||||
try
|
||||
{
|
||||
var d = Application.Current?.Dispatcher;
|
||||
if (d == null) return;
|
||||
d.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 空 StrokeCollection 在 RecognizeShapeAsync 入口会直接返回,无法预热 WinRT InkAnalyzer。
|
||||
await RecognizeShapeAsync(CreateMinimalWarmupStrokeCollection()).ConfigureAwait(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>由 <see cref="ModernInkProcessor"/> / <see cref="InkRecognitionManager"/> 在 UI 上 await(勿在收笔回调中同步阻塞)。</summary>
|
||||
internal static async Task<InkShapeRecognitionResult> RecognizeShapeAsync(StrokeCollection strokes)
|
||||
{
|
||||
if (!IsApiAvailable || strokes == null || strokes.Count == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var analyzer = new WinRtInkAnalyzer();
|
||||
var added = 0;
|
||||
foreach (Stroke s in strokes)
|
||||
{
|
||||
var inkStroke = CreateInkStrokeFromWpf(s);
|
||||
if (inkStroke == null)
|
||||
continue;
|
||||
|
||||
analyzer.AddDataForStroke(inkStroke);
|
||||
analyzer.SetStrokeDataKind(
|
||||
inkStroke.Id,
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisStrokeKind.Drawing);
|
||||
added++;
|
||||
}
|
||||
|
||||
if (added == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
await analyzer.AnalyzeAsync().AsTask().ConfigureAwait(true);
|
||||
|
||||
var drawing = FindPrimaryDrawing(analyzer);
|
||||
if (drawing == null)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
if (drawing.DrawingKind == global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Drawing)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var name = MapDrawingKindToShapeName(drawing.DrawingKind);
|
||||
if (string.IsNullOrEmpty(name) || name == "Drawing")
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var winPts = CopyWinRtPoints(drawing);
|
||||
var hot = ToWpfPointCollection(winPts);
|
||||
var c = drawing.Center;
|
||||
var centroid = new SysPoint(c.X, c.Y);
|
||||
BoundsFromPoints(winPts, out double w, out double h);
|
||||
|
||||
var toRemove = new StrokeCollection();
|
||||
foreach (Stroke s in strokes)
|
||||
toRemove.Add(s);
|
||||
|
||||
return new InkShapeRecognitionResult(name, centroid, hot, w, h, toRemove);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 极短合成笔画,供 <see cref="Warmup"/> 等场景走完整 WinRT 转换与分析管线(空集合在入口处会被直接返回)。
|
||||
/// </summary>
|
||||
internal static StrokeCollection CreateMinimalWarmupStrokeCollection()
|
||||
{
|
||||
var da = new DrawingAttributes { Color = Colors.Black, Width = 2, Height = 2 };
|
||||
var pts = new StylusPointCollection
|
||||
{
|
||||
new StylusPoint(8, 8),
|
||||
new StylusPoint(14, 10),
|
||||
new StylusPoint(20, 8),
|
||||
};
|
||||
var col = new StrokeCollection();
|
||||
col.Add(new Stroke(pts, da));
|
||||
return col;
|
||||
}
|
||||
|
||||
/// <summary>供 WinRT 手写等模块复用:将 WPF <see cref="Stroke"/> 转为 WinRT <see cref="global::Windows.UI.Input.Inking.InkStroke"/>。</summary>
|
||||
internal static global::Windows.UI.Input.Inking.InkStroke CreateInkStrokeFromWpf(Stroke stroke)
|
||||
{
|
||||
if (stroke?.StylusPoints == null || stroke.StylusPoints.Count == 0)
|
||||
return null;
|
||||
|
||||
var da = stroke.DrawingAttributes;
|
||||
var wda = new global::Windows.UI.Input.Inking.InkDrawingAttributes
|
||||
{
|
||||
PenTip = global::Windows.UI.Input.Inking.PenTipShape.Circle,
|
||||
Color = global::Windows.UI.Color.FromArgb(da.Color.A, da.Color.R, da.Color.G, da.Color.B),
|
||||
Size = new global::Windows.Foundation.Size((float)da.Width, (float)da.Height)
|
||||
};
|
||||
|
||||
var builder = new global::Windows.UI.Input.Inking.InkStrokeBuilder();
|
||||
builder.SetDefaultDrawingAttributes(wda);
|
||||
|
||||
var points = new List<global::Windows.Foundation.Point>(stroke.StylusPoints.Count);
|
||||
foreach (StylusPoint sp in stroke.StylusPoints)
|
||||
{
|
||||
var pi = sp.ToPoint();
|
||||
points.Add(new global::Windows.Foundation.Point((float)pi.X, (float)pi.Y));
|
||||
}
|
||||
|
||||
if (points.Count == 0)
|
||||
return null;
|
||||
|
||||
return builder.CreateStroke(points);
|
||||
}
|
||||
|
||||
private static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing(
|
||||
WinRtInkAnalyzer analyzer)
|
||||
{
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null;
|
||||
double bestArea = -1;
|
||||
if (analyzer?.AnalysisRoot != null)
|
||||
Visit(analyzer.AnalysisRoot);
|
||||
return best;
|
||||
|
||||
void Visit(global::Windows.UI.Input.Inking.Analysis.IInkAnalysisNode node)
|
||||
{
|
||||
if (node == null) return;
|
||||
|
||||
if (node is global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing d &&
|
||||
d.DrawingKind != global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Drawing)
|
||||
{
|
||||
double area = EstimateDrawingArea(d);
|
||||
if (area > bestArea)
|
||||
{
|
||||
bestArea = area;
|
||||
best = d;
|
||||
}
|
||||
}
|
||||
|
||||
// WinRT IInkAnalysisNode.Children 可能为 null,不可直接 foreach。
|
||||
var children = node.Children;
|
||||
if (children == null) return;
|
||||
|
||||
foreach (var child in children)
|
||||
Visit(child);
|
||||
}
|
||||
}
|
||||
|
||||
private static double EstimateDrawingArea(global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing)
|
||||
{
|
||||
var pts = CopyWinRtPoints(drawing);
|
||||
BoundsFromPoints(pts, out double w, out double h);
|
||||
return w * h;
|
||||
}
|
||||
|
||||
private static global::Windows.Foundation.Point[] CopyWinRtPoints(
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing)
|
||||
{
|
||||
var src = drawing?.Points;
|
||||
if (src == null)
|
||||
return Array.Empty<global::Windows.Foundation.Point>();
|
||||
|
||||
var n = src.Count;
|
||||
if (n == 0)
|
||||
return Array.Empty<global::Windows.Foundation.Point>();
|
||||
|
||||
var arr = new global::Windows.Foundation.Point[n];
|
||||
for (var i = 0; i < n; i++)
|
||||
arr[i] = src[i];
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static void BoundsFromPoints(
|
||||
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points,
|
||||
out double w,
|
||||
out double h)
|
||||
{
|
||||
if (points == null || points.Count == 0)
|
||||
{
|
||||
w = h = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
double minX = double.MaxValue, maxX = double.MinValue, minY = double.MaxValue, maxY = double.MinValue;
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
{
|
||||
var pt = points[i];
|
||||
minX = Math.Min(minX, pt.X);
|
||||
maxX = Math.Max(maxX, pt.X);
|
||||
minY = Math.Min(minY, pt.Y);
|
||||
maxY = Math.Max(maxY, pt.Y);
|
||||
}
|
||||
|
||||
w = Math.Max(0, maxX - minX);
|
||||
h = Math.Max(0, maxY - minY);
|
||||
}
|
||||
|
||||
private static PointCollection ToWpfPointCollection(
|
||||
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points)
|
||||
{
|
||||
var hot = new PointCollection();
|
||||
if (points == null) return hot;
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
{
|
||||
var pt = points[i];
|
||||
hot.Add(new SysPoint(pt.X, pt.Y));
|
||||
}
|
||||
|
||||
return hot;
|
||||
}
|
||||
|
||||
private static string MapDrawingKindToShapeName(
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Circle:
|
||||
return "Circle";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Ellipse:
|
||||
return "Ellipse";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Triangle:
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.IsoscelesTriangle:
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.EquilateralTriangle:
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.RightTriangle:
|
||||
return "Triangle";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Rectangle:
|
||||
return "Rectangle";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Square:
|
||||
return "Square";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Diamond:
|
||||
return "Diamond";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Trapezoid:
|
||||
return "Trapezoid";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Parallelogram:
|
||||
return "Parallelogram";
|
||||
case global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Quadrilateral:
|
||||
return "Quadrilateral";
|
||||
default:
|
||||
return kind == global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Drawing
|
||||
? "Drawing"
|
||||
: kind.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using System;
|
||||
using System.Windows;
|
||||
@@ -40,10 +40,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
taskbar.Visibility = Visibility.Visible;
|
||||
|
||||
taskbar.ShowBalloonTip(
|
||||
taskbar.ShowNotification(
|
||||
"InkCanvasForClass CE",
|
||||
$"发现新版本!:{version}",
|
||||
BalloonIcon.Info);
|
||||
$"发现新版本!:{version}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -25,91 +25,78 @@
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Configurations>Debug;Release;x86 Debug</Configurations>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU;x86;x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<Prefer32Bit>True</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<Prefer32Bit>True</Prefer32Bit>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<Prefer32Bit>True</Prefer32Bit>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\icc.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|x86'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Title>InkCanvasForClass</Title>
|
||||
<Version>5.0.4</Version>
|
||||
<Authors>Dubi906w</Authors>
|
||||
<Version>1.7</Version>
|
||||
<Authors>CJK_mkp</Authors>
|
||||
<Product>InkCanvasForClass</Product>
|
||||
<Copyright>© Copyright HARKOTEK Studio 2024-now</Copyright>
|
||||
<PackageProjectUrl>https://icc.bliemhax.com</PackageProjectUrl>
|
||||
<Copyright>© Copyright CJK_mkp 2025-now</Copyright>
|
||||
<PackageProjectUrl>https://inkcanvasforclass.github.io</PackageProjectUrl>
|
||||
<FileVersion>bundled</FileVersion>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|ARM64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|x64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="IACore">
|
||||
@@ -127,6 +114,7 @@
|
||||
<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" />
|
||||
@@ -147,17 +135,18 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
|
||||
<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" Version="1.2.8" />
|
||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||
<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="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="Microsoft.International.Converters.PinYinConverter" Version="1.0.0" />
|
||||
<PackageReference Include="Sentry" Version="6.1.0" />
|
||||
<PackageReference Include="Sentry" Version="6.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
|
||||
@@ -189,15 +178,6 @@
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="VBIDE">
|
||||
<Guid>{0002E157-0000-0000-C000-000000000046}</Guid>
|
||||
<VersionMajor>5</VersionMajor>
|
||||
<VersionMinor>3</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(MSBuildRuntimeType)' != 'Full'">
|
||||
<Reference Include="Interop.IWshRuntimeLibrary">
|
||||
@@ -585,14 +565,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Remove="AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Properties\Strings.en-US.resx" />
|
||||
<EmbeddedResource Remove="**\Strings.en-US.resx" />
|
||||
<None Include="Properties\Strings.en-US.resx" />
|
||||
<EmbeddedResource Include="Properties\Strings.enUS.xml">
|
||||
<LogicalName>Ink_Canvas.Properties.Strings.enUS.xml</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="MainWindow.xaml~RF6c3144.TMP" />
|
||||
<None Remove="Resources\Cursors\Cursor.cur" />
|
||||
|
||||
+192
-67
@@ -1,4 +1,4 @@
|
||||
<Window Name="window" x:Class="Ink_Canvas.MainWindow"
|
||||
<Window Name="window" x:Class="Ink_Canvas.MainWindow"
|
||||
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"
|
||||
@@ -24,7 +24,7 @@
|
||||
Closing="Window_Closing"
|
||||
Closed="Window_Closed"
|
||||
PreviewKeyDown="Main_Grid_PreviewKeyDown"
|
||||
Height="9000" Width="1440"
|
||||
Height="16000" Width="1440"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
MouseWheel="Window_MouseWheel"
|
||||
Foreground="{DynamicResource FloatBarForeground}"
|
||||
@@ -134,6 +134,7 @@
|
||||
|
||||
<Style x:Key="AutoFitGestureOptionLabel10" TargetType="Label">
|
||||
<Setter Property="Foreground" Value="{DynamicResource FloatBarForeground}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
@@ -143,6 +144,11 @@
|
||||
<Setter Property="helpers:AutoFontSizeHelper.Step" Value="0.25" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AutoFitBoardGestureOptionLabel10" TargetType="Label" BasedOn="{StaticResource AutoFitGestureOptionLabel10}">
|
||||
<Setter Property="helpers:AutoFontSizeHelper.MinFontSize" Value="5" />
|
||||
<Setter Property="helpers:AutoFontSizeHelper.MaxFontSize" Value="10" />
|
||||
</Style>
|
||||
|
||||
<!-- Navigation Button Style -->
|
||||
<Style x:Key="NavButton" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
@@ -241,21 +247,6 @@
|
||||
<!-- 主要导航按钮 -->
|
||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel>
|
||||
<!-- Plugins -->
|
||||
<Button Width="40" Height="40" Margin="0,5,0,0" Style="{StaticResource NavButton}"
|
||||
Click="NavPlugins_Click" Tag="plugins" ToolTip="{x:Static props:Strings.Nav_Plugins}">
|
||||
<Image Width="20" Height="20">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<GeometryDrawing Brush="White"
|
||||
Geometry="M342.826667 213.333333a149.333333 149.333333 0 0 1 295.68 0H682.666667a128 128 0 0 1 128 128v44.16a149.333333 149.333333 0 0 1 0 295.68V725.333333a128 128 0 0 1-128 128h-128v-21.333333a64 64 0 0 0-128 0v21.333333H298.666667a128 128 0 0 1-128-128v-128h21.333333a64 64 0 0 0 0-128H170.666667V341.333333a128 128 0 0 1 128-128h44.16zM426.666667 234.666667V298.666667H298.666667q-17.664 0-30.165334 12.501333T256 341.333333v56.576q22.528 10.752 41.6 29.866667Q341.333333 471.466667 341.333333 533.333333q0 61.866667-43.733333 105.6-19.072 19.072-41.6 29.824V725.333333q0 17.664 12.501333 30.165334T298.666667 768h56.576q10.752-22.528 29.866666-41.6Q428.8 682.666667 490.666667 682.666667q61.866667 0 105.6 43.733333 19.072 19.072 29.824 41.6H682.666667q17.664 0 30.165333-12.501333T725.333333 725.333333v-128h64a64 64 0 0 0 0-128H725.333333V341.333333q0-17.664-12.501333-30.165333T682.666667 298.666667h-128V234.666667a64 64 0 1 0-128 0z"/>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
|
||||
<!-- Startup -->
|
||||
<Button Width="40" Height="40" Margin="0,10,0,0" Style="{StaticResource NavButton}"
|
||||
Click="NavStartup_Click" Tag="startup" ToolTip="{x:Static props:Strings.Nav_Startup}">
|
||||
@@ -652,28 +643,15 @@
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Name="GroupBoxNewSettings">
|
||||
<GroupBox Name="GroupBoxSettings">
|
||||
<GroupBox.Header>
|
||||
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_NewWindow}" FontWeight="Bold" Foreground="#fafafa"
|
||||
FontSize="26" />
|
||||
</GroupBox.Header>
|
||||
<ikw:SimpleStackPanel Spacing="6" Margin="0,10,0,0">
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" Foreground="#fafafa" Text="{i18n:I18n Key=Settings_NewWindowDesc}"/>
|
||||
<Button x:Name="BtnOpenNewSettings" Content="{i18n:I18n Key=Btn_OpenNewSettings}"
|
||||
HorizontalAlignment="Left" Click="BtnOpenNewSettings_Click"
|
||||
Padding="15,5" Margin="0,10,0,0"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Name="GroupBoxPlugins">
|
||||
<GroupBox.Header>
|
||||
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_Plugins}" FontWeight="Bold" Foreground="#fafafa"
|
||||
FontSize="26" />
|
||||
</GroupBox.Header>
|
||||
<ikw:SimpleStackPanel Spacing="6" Margin="0,10,0,0">
|
||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" Foreground="#fafafa" Text="{i18n:I18n Key=Settings_PluginsDesc}"/>
|
||||
<Button x:Name="BtnOpenPluginManager" Content="{i18n:I18n Key=Btn_OpenPluginManager}"
|
||||
HorizontalAlignment="Left" Click="BtnOpenPluginManager_Click"
|
||||
<Button x:Name="BtnOpenSettings" Content="{i18n:I18n Key=Btn_OpenNewSettings}"
|
||||
HorizontalAlignment="Left" Click="BtnOpenNewNewSettings_Click"
|
||||
Padding="15,5" Margin="0,10,0,0"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
@@ -723,6 +701,18 @@
|
||||
Toggled="ToggleSwitchIsAutoUpdateWithSilence_Toggled" />
|
||||
<TextBlock Text="{i18n:I18n Key=Startup_SilentUpdateHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
|
||||
<!-- 更新包架构 -->
|
||||
<ikw:SimpleStackPanel Spacing="8" Margin="0,8,0,0">
|
||||
<TextBlock Text="{i18n:I18n Key=Startup_UpdatePackageArchitecture}" FontSize="15" FontWeight="Bold" Foreground="#fafafa"/>
|
||||
<ui:RadioButtons x:Name="UpdatePackageArchitectureSelector" Margin="0,4,0,0">
|
||||
<RadioButton Content="{i18n:I18n Key=Update_PackageArch_X86}" GroupName="UpdatePackageArchitecture"
|
||||
Tag="X86" Checked="UpdatePackageArchitectureSelector_Checked"/>
|
||||
<RadioButton Content="{i18n:I18n Key=Update_PackageArch_X64}" GroupName="UpdatePackageArchitecture"
|
||||
Tag="X64" Checked="UpdatePackageArchitectureSelector_Checked"/>
|
||||
</ui:RadioButtons>
|
||||
<TextBlock Text="{i18n:I18n Key=Startup_UpdatePackageArchitectureHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
</ikw:SimpleStackPanel>
|
||||
|
||||
<!-- 更新通道选择 -->
|
||||
<ikw:SimpleStackPanel Spacing="8" Margin="0,8,0,0">
|
||||
<TextBlock Text="{i18n:I18n Key=Startup_UpdateChannel}" FontSize="15" FontWeight="Bold" Foreground="#fafafa"/>
|
||||
@@ -885,6 +875,15 @@
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchCompressPicturesUploaded_Toggled" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_LaunchSeewoVideoShowcaseForWhiteboardBooth}" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,8,0" Width="320"
|
||||
Style="{StaticResource AutoFitSettingsOptionLabel14}" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth"
|
||||
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth_Toggled" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<TextBlock Text="{i18n:I18n Key=Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
|
||||
StrokeThickness="1" Margin="0,4,0,4" />
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
@@ -1112,6 +1111,29 @@
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchEnableInkToShape_Toggled" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=InkRecog_ShapeEngine}" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<ComboBox Name="ComboBoxShapeRecognitionEngine" Width="160"
|
||||
SelectionChanged="ComboBoxShapeRecognitionEngine_SelectionChanged"
|
||||
FontFamily="Microsoft YaHei UI" VerticalAlignment="Center">
|
||||
<ComboBoxItem Content="{i18n:I18n Key=InkRecog_ShapeEngineAuto}" />
|
||||
<ComboBoxItem Content="{i18n:I18n Key=InkRecog_ShapeEngineIACore}" />
|
||||
<ComboBoxItem Content="{i18n:I18n Key=InkRecog_ShapeEngineWinRT}" />
|
||||
</ComboBox>
|
||||
</ikw:SimpleStackPanel>
|
||||
<TextBlock Text="{i18n:I18n Key=InkRecog_ShapeEngineHint}" TextWrapping="Wrap"
|
||||
Foreground="#a1a1aa" MaxWidth="520" HorizontalAlignment="Left" />
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=InkRecog_HandwritingBeautify}"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchEnableWinRtHandwritingStrokeBeautify"
|
||||
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchEnableWinRtHandwritingStrokeBeautify_Toggled" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<TextBlock Text="{i18n:I18n Key=InkRecog_HandwritingBeautifyHint}" TextWrapping="Wrap"
|
||||
Foreground="#a1a1aa" MaxWidth="520" HorizontalAlignment="Left" />
|
||||
<ikw:SimpleStackPanel Spacing="6"
|
||||
Visibility="{Binding ElementName=ToggleSwitchEnableInkToShape, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
@@ -1400,7 +1422,7 @@
|
||||
FontSize="14" Margin="0,0,0,6" />
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<ComboBox Name="ComboBoxChickenSoupSource" FontFamily="Microsoft YaHei UI"
|
||||
SelectedIndex="1" Width="240"
|
||||
Width="240"
|
||||
SelectionChanged="ComboBoxChickenSoupSource_SelectionChanged">
|
||||
<ComboBoxItem Content="{i18n:I18n Key=Theme_QuoteSource_OsuQuotes}" FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="{i18n:I18n Key=Theme_QuoteSource_Mottos}" FontFamily="Microsoft YaHei UI" />
|
||||
@@ -4613,7 +4635,7 @@
|
||||
StrokeCollected="inkCanvas_StrokeCollected"
|
||||
ClipToBounds="False"
|
||||
Background="Transparent" />
|
||||
|
||||
|
||||
<Canvas x:Name="EraserOverlayCanvas"
|
||||
Background="Transparent"
|
||||
IsHitTestVisible="False"
|
||||
@@ -4926,6 +4948,107 @@
|
||||
</Viewbox>
|
||||
</Border>
|
||||
|
||||
<Border Name="BorderPdfPageSidebar"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,0,0,0"
|
||||
Width="64"
|
||||
CornerRadius="8"
|
||||
Background="{DynamicResource FloatBarBackground}"
|
||||
BorderBrush="#33FFFFFF"
|
||||
BorderThickness="1"
|
||||
SnapsToDevicePixels="True"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1001"
|
||||
ui:ThemeManager.RequestedTheme="Dark">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="24"
|
||||
ShadowDepth="3"
|
||||
Direction="270"
|
||||
Opacity="0.45"
|
||||
Color="#000000" />
|
||||
</Border.Effect>
|
||||
<Border.Resources>
|
||||
<Style x:Key="PdfSidebarNavButtonStyle" TargetType="Border">
|
||||
<Setter Property="Width" Value="40" />
|
||||
<Setter Property="Height" Value="36" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="#FFFFFF" Opacity="0.08" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Resources>
|
||||
<Grid>
|
||||
<ikw:SimpleStackPanel VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Spacing="8"
|
||||
Margin="0,10,0,10">
|
||||
<TextBlock Text="PDF"
|
||||
FontSize="10"
|
||||
FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource FloatBarForeground}"
|
||||
Opacity="0.78" />
|
||||
<Border CornerRadius="4"
|
||||
Padding="8,6"
|
||||
BorderThickness="0"
|
||||
SnapsToDevicePixels="True"
|
||||
Background="#14FFFFFF">
|
||||
<TextBlock Name="TextBlockPdfSidebarPageLabel"
|
||||
Text="— / —"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
Foreground="{DynamicResource FloatBarForeground}"
|
||||
Opacity="0.55" />
|
||||
</Border>
|
||||
<Border Name="BorderPdfSidebarPagePrev"
|
||||
Style="{StaticResource PdfSidebarNavButtonStyle}"
|
||||
Opacity="0.35"
|
||||
IsHitTestVisible="False"
|
||||
ToolTip="上一页"
|
||||
MouseDown="Border_MouseDown"
|
||||
MouseUp="BorderPdfSidebarPagePrev_MouseUp">
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.ChevronLeft}"
|
||||
FontSize="14"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource FloatBarForeground}" />
|
||||
</Border>
|
||||
<Border Name="BorderPdfSidebarPageNext"
|
||||
Style="{StaticResource PdfSidebarNavButtonStyle}"
|
||||
Opacity="0.35"
|
||||
IsHitTestVisible="False"
|
||||
ToolTip="下一页"
|
||||
MouseDown="Border_MouseDown"
|
||||
MouseUp="BorderPdfSidebarPageNext_MouseUp">
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.ChevronRight}"
|
||||
FontSize="14"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource FloatBarForeground}" />
|
||||
</Border>
|
||||
<TextBlock Text="翻页"
|
||||
FontSize="9"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource FloatBarForeground}"
|
||||
Opacity="0.55" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 图片缩放选择点 -->
|
||||
<Canvas Name="ImageResizeHandlesCanvas"
|
||||
Visibility="Collapsed"
|
||||
@@ -5141,7 +5264,7 @@
|
||||
Click="WhiteBoardPageListItem_DeleteClick"
|
||||
Background="#CC000000" Foreground="White"
|
||||
BorderThickness="0" Padding="0" Cursor="Hand">
|
||||
<ui:SymbolIcon Symbol="Delete" />
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Delete}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -5259,7 +5382,7 @@
|
||||
<Border BorderBrush="#1e3a8a" BorderThickness="0,0,0,1"
|
||||
CornerRadius="8,8,0,0"
|
||||
Background="#2563eb" Margin="-1,-1,-1,1">
|
||||
<Canvas Height="36" ClipToBounds="True">
|
||||
<Canvas Height="38" ClipToBounds="True">
|
||||
<TextBlock Text="{i18n:I18n Key=Board_GestureOptions}" Canvas.Left="12" Foreground="White"
|
||||
Padding="0,7"
|
||||
FontSize="17" FontWeight="Bold"
|
||||
@@ -5282,10 +5405,10 @@
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="16"
|
||||
Width="16" />
|
||||
<Label Content="{i18n:I18n Key=Board_MultiTouchWriting}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Label Content="{i18n:I18n Key=Board_MultiTouchWriting}" FontSize="10"
|
||||
VerticalAlignment="Center" Width="62"
|
||||
Style="{StaticResource AutoFitBoardGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
x:Name="BoardToggleSwitchEnableMultiTouchMode"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
@@ -5314,10 +5437,10 @@
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="16"
|
||||
Width="16" />
|
||||
<Label Content="{i18n:I18n Key=Board_TwoFingerMove}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Label Content="{i18n:I18n Key=Board_TwoFingerMove}" FontSize="10"
|
||||
VerticalAlignment="Center" Width="62"
|
||||
Style="{StaticResource AutoFitBoardGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
x:Name="BoardToggleSwitchEnableTwoFingerTranslate"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
@@ -5346,10 +5469,10 @@
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="16"
|
||||
Width="16" />
|
||||
<Label Content="{i18n:I18n Key=Board_TwoFingerZoom}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Label Content="{i18n:I18n Key=Board_TwoFingerZoom}" FontSize="10"
|
||||
VerticalAlignment="Center" Width="62"
|
||||
Style="{StaticResource AutoFitBoardGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
IsOn="False" OnContent="" OffContent=""
|
||||
@@ -5376,10 +5499,10 @@
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="16"
|
||||
Width="16" />
|
||||
<Label Content="{i18n:I18n Key=Board_TwoFingerRotate}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Label Content="{i18n:I18n Key=Board_TwoFingerRotate}" FontSize="10"
|
||||
VerticalAlignment="Center" Width="62"
|
||||
Style="{StaticResource AutoFitBoardGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
IsOn="False" OnContent="" OffContent=""
|
||||
@@ -5608,8 +5731,10 @@
|
||||
VerticalAlignment="Center"
|
||||
Name="BoardComboBoxPenStyle"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
SelectedIndex="0"
|
||||
SelectedIndex="1"
|
||||
SelectionChanged="ComboBoxPenStyle_SelectionChanged">
|
||||
<ComboBoxItem Content="实时笔锋"
|
||||
FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="基于点集"
|
||||
FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="基于速率"
|
||||
@@ -5664,7 +5789,6 @@
|
||||
</ikw:SimpleStackPanel>
|
||||
</Controls:UniformGrid>
|
||||
|
||||
|
||||
<StackPanel Orientation="Horizontal" Height="30">
|
||||
<Label Margin="0,0,10,0" Content="粗细"
|
||||
FontWeight="Bold"
|
||||
@@ -6633,7 +6757,7 @@
|
||||
<Run Text="{i18n:I18n Key=Board_Shape}" />
|
||||
<Run Text="{i18n:I18n Key=Board_ShapeHintLongPress}" FontSize="10" />
|
||||
</TextBlock>
|
||||
<ui:SymbolIcon Margin="0,-20,8,15" Symbol="Pin"
|
||||
<ui:FontIcon Margin="0,-20,8,15" Icon="{x:Static ui:SegoeFluentIcons.Pin}"
|
||||
MouseDown="Border_MouseDown"
|
||||
MouseUp="SymbolIconPinBorderDrawShape_MouseUp"
|
||||
Foreground="{DynamicResource FloatBarForeground}"
|
||||
@@ -7318,7 +7442,7 @@
|
||||
Click="WhiteBoardPageListItem_DeleteClick"
|
||||
Background="#CC000000" Foreground="White"
|
||||
BorderThickness="0" Padding="0" Cursor="Hand">
|
||||
<ui:SymbolIcon Symbol="Delete" />
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Delete}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -7370,7 +7494,7 @@
|
||||
Width="50" Height="48" FontSize="26" Click="BtnWhiteBoardAdd_Click"
|
||||
Foreground="{Binding ElementName=BtnExit, Path=Foreground}"
|
||||
Background="{Binding ElementName=BtnExit, Path=Background}">
|
||||
<ui:SymbolIcon Symbol="Add" />
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Add}" />
|
||||
</Button>
|
||||
<Button Name="BtnWhiteBoardSwitchPrevious" FontFamily="Symbol"
|
||||
Width="50" Height="48" FontSize="25" Click="BtnWhiteBoardSwitchPrevious_Click"
|
||||
@@ -7443,7 +7567,7 @@
|
||||
Foreground="{Binding ElementName=BtnExit, Path=Foreground}"
|
||||
Background="{Binding ElementName=BtnExit, Path=Background}"
|
||||
IsEnabled="False">
|
||||
<ui:SymbolIcon Symbol="Delete" />
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Delete}" />
|
||||
</Button>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
@@ -7557,7 +7681,7 @@
|
||||
Background="{Binding ElementName=BtnExit, Path=Background}"
|
||||
IsEnabled="False" Visibility="Collapsed" IsEnabledChanged="Btn_IsEnabledChanged">
|
||||
<StackPanel Opacity="0.2">
|
||||
<ui:SymbolIcon Symbol="Undo" />
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Undo}" />
|
||||
<TextBlock Text="{i18n:I18n Key=Board_Undo}" Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
@@ -7567,7 +7691,7 @@
|
||||
Background="{Binding ElementName=BtnExit, Path=Background}"
|
||||
IsEnabled="False" Visibility="Collapsed" IsEnabledChanged="Btn_IsEnabledChanged">
|
||||
<StackPanel Opacity="0.2">
|
||||
<ui:SymbolIcon Symbol="Redo" />
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Redo}" />
|
||||
<TextBlock Text="{i18n:I18n Key=OldUI_Restore}" Margin="0,4,0,0" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
@@ -8656,8 +8780,10 @@
|
||||
<ComboBox Height="30" VerticalAlignment="Center"
|
||||
Name="ComboBoxPenStyle"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
SelectedIndex="0"
|
||||
SelectedIndex="1"
|
||||
SelectionChanged="ComboBoxPenStyle_SelectionChanged">
|
||||
<ComboBoxItem Content="实时笔锋"
|
||||
FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="基于点集"
|
||||
FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="基于速率"
|
||||
@@ -8707,7 +8833,6 @@
|
||||
</ikw:SimpleStackPanel>
|
||||
</Controls:UniformGrid>
|
||||
|
||||
|
||||
<StackPanel Orientation="Horizontal" Height="30">
|
||||
<Label Margin="0,0,10,0" Content="粗细" FontWeight="Bold"
|
||||
Foreground="{DynamicResource FloatBarForeground}"
|
||||
@@ -10255,7 +10380,7 @@
|
||||
<Label Content="{i18n:I18n Key=FloatingBar_Gesture_MultiTouchWriting}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
Name="ToggleSwitchEnableMultiTouchMode"
|
||||
FontFamily="Microsoft YaHei UI" IsOn="False" OnContent=""
|
||||
@@ -10281,7 +10406,7 @@
|
||||
<Label Content="{i18n:I18n Key=FloatingBar_Gesture_TwoFingerMove}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
Name="ToggleSwitchEnableTwoFingerTranslate"
|
||||
FontFamily="Microsoft YaHei UI" IsOn="False" OnContent=""
|
||||
@@ -10308,7 +10433,7 @@
|
||||
<Label Content="{i18n:I18n Key=FloatingBar_Gesture_TwoFingerZoom}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
IsOn="False" OnContent="" OffContent=""
|
||||
@@ -10335,7 +10460,7 @@
|
||||
<Label Content="{i18n:I18n Key=FloatingBar_Gesture_TwoFingerRotate}" FontSize="8"
|
||||
VerticalAlignment="Center" Width="56"
|
||||
Style="{StaticResource AutoFitGestureOptionLabel10}" />
|
||||
<Viewbox Width="36" Height="18" Margin="2,0,0,0">
|
||||
<Viewbox Width="36" Height="20" Margin="2,0,0,0">
|
||||
<ui:ToggleSwitch MinWidth="0" Margin="0,-6,0,-6"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
IsOn="False" OnContent="" OffContent=""
|
||||
|
||||
+47
-103
@@ -1,5 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers.Plugins;
|
||||
using Ink_Canvas.Windows;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
@@ -83,6 +82,8 @@ namespace Ink_Canvas
|
||||
private static Cursor _cachedPenCursor = null;
|
||||
private static readonly object _cursorLock = new object();
|
||||
|
||||
internal static DateTime? TrayTemporaryShowUntilUtc;
|
||||
|
||||
#region Window Initialization
|
||||
|
||||
/// <summary>
|
||||
@@ -1165,6 +1166,7 @@ namespace Ink_Canvas
|
||||
public static Settings Settings = new Settings();
|
||||
public static string settingsFileName = Path.Combine("Configs", "Settings.json");
|
||||
private bool isLoaded;
|
||||
private bool _suppressChickenSoupSourceSelectionChanged;
|
||||
private bool forcePointEraser;
|
||||
|
||||
/// <summary>
|
||||
@@ -1319,11 +1321,13 @@ namespace Ink_Canvas
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = CurrentWhiteboardIndex != 1;
|
||||
BorderInkReplayToolBox.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 提前加载IA库,优化第一笔等待时间
|
||||
if (Settings.InkToShape.IsInkToShapeEnabled && !Environment.Is64BitProcess)
|
||||
// 提前加载识别后端,优化第一笔等待时间
|
||||
if (ShapeRecognitionRouter.ShouldRunShapeRecognition(
|
||||
Settings.InkToShape.IsInkToShapeEnabled,
|
||||
ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine)))
|
||||
{
|
||||
var strokeEmpty = new StrokeCollection();
|
||||
InkRecognizeHelper.RecognizeShape(strokeEmpty);
|
||||
InkRecognizeHelper.WarmupShapeRecognition(
|
||||
ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine));
|
||||
}
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged;
|
||||
@@ -1331,6 +1335,7 @@ namespace Ink_Canvas
|
||||
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode && !App.StartWithShowMode)
|
||||
{
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
ScheduleStartupFoldAbsenceVerification();
|
||||
}
|
||||
|
||||
// 恢复崩溃后操作设置
|
||||
@@ -1362,8 +1367,6 @@ namespace Ink_Canvas
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
// 初始化插件系统
|
||||
InitializePluginSystem();
|
||||
// 确保开关和设置同步
|
||||
ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode;
|
||||
ApplyNoFocusMode();
|
||||
@@ -1406,6 +1409,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 检查模式设置并应用
|
||||
CheckMainWindowVisibility();
|
||||
EnsurePptOnlyVisibilityProbeTimer();
|
||||
|
||||
// 检查是否通过--board参数启动,如果是则自动切换到白板模式
|
||||
if (App.StartWithBoardMode)
|
||||
@@ -1860,8 +1864,7 @@ namespace Ink_Canvas
|
||||
if (AvailableLatestVersion != null && Settings.Startup.IsAutoUpdate)
|
||||
{
|
||||
// 检查更新文件是否已下载
|
||||
string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
string statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{AvailableLatestVersion}Status.txt");
|
||||
string statusFilePath = AutoUpdateHelper.GetUpdateDownloadStatusFilePath(AvailableLatestVersion);
|
||||
|
||||
if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true")
|
||||
{
|
||||
@@ -2263,6 +2266,7 @@ namespace Ink_Canvas
|
||||
SetFloatingBarHighlightPosition("select");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 手写笔输入
|
||||
@@ -2574,9 +2578,6 @@ namespace Ink_Canvas
|
||||
case "about":
|
||||
targetGroupBox = GroupBoxAbout;
|
||||
break;
|
||||
case "plugins":
|
||||
targetGroupBox = GroupBoxPlugins;
|
||||
break;
|
||||
default:
|
||||
// 默认滚动到顶部
|
||||
SettingsPanelScrollViewer.ScrollToTop();
|
||||
@@ -2724,9 +2725,6 @@ namespace Ink_Canvas
|
||||
case "about":
|
||||
SetNavButtonTag("about");
|
||||
break;
|
||||
case "plugins":
|
||||
SetNavButtonTag("plugins");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2813,97 +2811,18 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion Navigation Sidebar Methods
|
||||
|
||||
#region 插件???
|
||||
|
||||
// 添加插件系统初始化方法
|
||||
private void InitializePluginSystem()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 初始化插件管理器
|
||||
PluginManager.Instance.Initialize();
|
||||
LogHelper.WriteLogToFile("插件系统已初始化");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化插件系统时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加插件管理导航点击事件处理
|
||||
private void NavPlugins_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ShowSettingsSection("plugins");
|
||||
}
|
||||
|
||||
// 添加打开插件管理器按钮点击事件
|
||||
private void BtnOpenPluginManager_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 暂时隐藏设置面板
|
||||
BorderSettings.Visibility = Visibility.Hidden;
|
||||
BorderSettingsMask.Visibility = Visibility.Hidden;
|
||||
|
||||
// 创建并显示插件设置窗口
|
||||
PluginSettingsWindow pluginSettingsWindow = new PluginSettingsWindow();
|
||||
|
||||
// 设置窗口关闭事件,用于在插件管理窗口关闭后恢复设置面板
|
||||
pluginSettingsWindow.Closed += (s, args) =>
|
||||
{
|
||||
// 恢复设置面板显示
|
||||
BorderSettings.Visibility = Visibility.Visible;
|
||||
BorderSettingsMask.Visibility = Visibility.Visible;
|
||||
};
|
||||
|
||||
// 显示插件设置窗口
|
||||
pluginSettingsWindow.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 确保在发生错误时也恢复设置面板显示
|
||||
BorderSettings.Visibility = Visibility.Visible;
|
||||
BorderSettingsMask.Visibility = Visibility.Visible;
|
||||
|
||||
LogHelper.WriteLogToFile($"打开插件管理器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"打开插件管理器时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
#endregion 插件???
|
||||
|
||||
#region 新设置窗口
|
||||
|
||||
/// <summary>
|
||||
/// 在隐藏子面板后打开新的设置窗口;若需要则先提示并验证安全密码,并在正在打开或隐藏设置面板时不执行任何操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在验证密码失败或发生异常时会中止操作。成功通过验证后以模式窗口方式显示设置窗口并将当前窗口设为其所有者。
|
||||
/// </remarks>
|
||||
private async void BtnOpenNewSettings_Click(object sender, RoutedEventArgs e)
|
||||
private async void BtnOpenNewNewSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isOpeningOrHidingSettingsPane) return;
|
||||
HideSubPanels();
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SecurityManager.IsPasswordRequiredForEnterSettings(Settings))
|
||||
{
|
||||
bool ok = await SecurityManager.PromptAndVerifyAsync(Settings, this, "进入设置", "请输入安全密码以进入设置。");
|
||||
if (!ok) return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"安全密码校验失败: {ex}", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsWindow = new SettingsWindow();
|
||||
var settingsWindow = new Windows.SettingsViews.SettingsWindow();
|
||||
settingsWindow.Owner = this;
|
||||
settingsWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion 新设置窗口
|
||||
|
||||
// 在MainWindow类中添加:
|
||||
@@ -3535,7 +3454,6 @@ namespace Ink_Canvas
|
||||
ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "启用" : "禁用")}", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -3587,7 +3505,6 @@ namespace Ink_Canvas
|
||||
ToggleSwitchInkFadeInPanel2.IsOn = Settings.Canvas.EnableInkFade;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"批注子面板中墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "启用" : "禁用")}", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -4442,9 +4359,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
Hide();
|
||||
LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event);
|
||||
EnsurePptOnlyVisibilityProbeTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopPptOnlyVisibilityProbeTimer();
|
||||
// 如果切换到正常模式,显示主窗口
|
||||
Show();
|
||||
LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event);
|
||||
@@ -4460,14 +4379,23 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 检查是否应该显示主窗口(基于PPT模式和PPT放映状态)
|
||||
/// </summary>
|
||||
private void CheckMainWindowVisibility()
|
||||
internal void CheckMainWindowVisibility()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.ModeSettings.IsPPTOnlyMode)
|
||||
{
|
||||
// 仅PPT模式下,只有在PPT放映时才显示
|
||||
bool isInSlideShow = BtnPPTSlideShowEnd.Visibility == Visibility.Visible;
|
||||
if (TrayTemporaryShowUntilUtc.HasValue && DateTime.UtcNow < TrayTemporaryShowUntilUtc.Value)
|
||||
{
|
||||
if (!IsVisible)
|
||||
Show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 仅PPT模式:以 COM/UI 状态为主,Win32 检测全屏放映窗口(screenClass)作兜底,避免 COM 异常时无法唤出
|
||||
bool comUiSlideShow = BtnPPTSlideShowEnd.Visibility == Visibility.Visible;
|
||||
bool win32SlideShow = IsPowerPointSlideshowSurfacePresentWin32();
|
||||
bool isInSlideShow = comUiSlideShow || win32SlideShow;
|
||||
if (isInSlideShow && !IsVisible)
|
||||
{
|
||||
Show();
|
||||
@@ -4783,6 +4711,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
internal void OpenQuickDrawFromHotkey()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings?.RandSettings?.EnableQuickDraw != true)
|
||||
return;
|
||||
|
||||
var quickDrawWindow = new QuickDrawWindow();
|
||||
quickDrawWindow.ShowDialog();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开快抽窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示快抽悬浮按钮
|
||||
/// </summary>
|
||||
@@ -4812,4 +4756,4 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,5 +525,85 @@ namespace Ink_Canvas
|
||||
});
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
|
||||
private bool IsFloatingBarUiAbsentFromScreens()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ViewboxFloatingBar == null) return true;
|
||||
if (Settings?.Automation?.ThoroughlyHideWhenFolded == true &&
|
||||
(!IsVisible || Visibility != Visibility.Visible))
|
||||
return true;
|
||||
if (ViewboxFloatingBar.Visibility != Visibility.Visible)
|
||||
return true;
|
||||
return IsOutsideOfScreenHelper.IsOutsideOfScreen(ViewboxFloatingBar);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动收纳校验:检测浮动栏可见性时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WaitUntilFloatingBarHideModeIdleAsync(TimeSpan timeout)
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
bool busy = await Dispatcher.InvokeAsync(() => isFloatingBarChangingHideMode);
|
||||
if (!busy) return;
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task WaitForStartupFoldSettledAsync(TimeSpan timeout)
|
||||
{
|
||||
var start = DateTime.UtcNow;
|
||||
while (DateTime.UtcNow - start < timeout)
|
||||
{
|
||||
var settled = await Dispatcher.InvokeAsync(() => isFloatingBarFolded && !isFloatingBarChangingHideMode);
|
||||
if (settled) return;
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task VerifyStartupFoldAbsenceAfterDelayAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Settings.Startup.IsFoldAtStartup || App.StartWithBoardMode || App.StartWithShowMode)
|
||||
return;
|
||||
|
||||
await WaitForStartupFoldSettledAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false);
|
||||
await Task.Delay(3000).ConfigureAwait(false);
|
||||
|
||||
bool needRetry = await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (!isFloatingBarFolded) return false;
|
||||
if (currentMode != 0) return false;
|
||||
return !IsFloatingBarUiAbsentFromScreens();
|
||||
});
|
||||
|
||||
if (!needRetry) return;
|
||||
|
||||
LogHelper.WriteLogToFile("启动收纳校验:检测到浮动栏仍在屏幕上,将展开后等待 0.2s 再次收纳", LogHelper.LogType.Event);
|
||||
|
||||
await UnFoldFloatingBar(null);
|
||||
await WaitUntilFloatingBarHideModeIdleAsync(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
||||
await Task.Delay(200).ConfigureAwait(false);
|
||||
await FoldFloatingBar(new object()).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动收纳校验失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleStartupFoldAbsenceVerification()
|
||||
{
|
||||
_ = VerifyStartupFoldAbsenceAfterDelayAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -81,7 +82,7 @@ namespace Ink_Canvas
|
||||
var missingElements = 0;
|
||||
foreach (UIElement child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image || child is MediaElement)
|
||||
if (child is Image || child is MediaElement || child is PdfEmbeddedView)
|
||||
{
|
||||
if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag)
|
||||
{
|
||||
@@ -225,6 +226,7 @@ namespace Ink_Canvas
|
||||
if (TimeMachineHistories[targetIndex] == null)
|
||||
{
|
||||
timeMachine.ClearStrokeHistory();
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -281,7 +283,11 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private void ProcessElementsAfterRestore(List<UIElement> elements)
|
||||
{
|
||||
if (elements == null || elements.Count == 0) return;
|
||||
if (elements == null || elements.Count == 0)
|
||||
{
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用低优先级异步处理,让 UI 先响应,图片位置和事件绑定稍后完成
|
||||
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
|
||||
@@ -310,7 +316,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
BindElementEvents(media);
|
||||
}
|
||||
else if (element is PdfEmbeddedView pdf)
|
||||
{
|
||||
double left = InkCanvas.GetLeft(pdf);
|
||||
double top = InkCanvas.GetTop(pdf);
|
||||
if (double.IsNaN(left) || double.IsNaN(top))
|
||||
{
|
||||
CenterAndScaleElement(pdf);
|
||||
}
|
||||
BindElementEvents(pdf);
|
||||
}
|
||||
}
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
|
||||
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
||||
@@ -553,37 +554,22 @@ namespace Ink_Canvas
|
||||
BoardHighlightPenTabButton.Background = new SolidColorBrush(Colors.Transparent);
|
||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Collapsed;
|
||||
|
||||
// PenPalette.Margin = new Thickness(-160, -200, -33, 32);
|
||||
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -200, -33, 32),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
||||
PenPalette.BeginAnimation(MarginProperty, null);
|
||||
var currentMargin = PenPalette.Margin;
|
||||
// 先设置正确的Top/Bottom,保持当前Left/Right
|
||||
PenPalette.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 32);
|
||||
UpdatePenPalettePosition();
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -200, -33, 50),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
|
||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
|
||||
var currentMargin = BoardPenPaletteGrid.Margin;
|
||||
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 50);
|
||||
});
|
||||
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -200, -33, 32); });
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -200, -33, 50); });
|
||||
}
|
||||
else if (penType == 1)
|
||||
{
|
||||
@@ -621,36 +607,22 @@ namespace Ink_Canvas
|
||||
BoardHighlightPenTabButton.Background = new SolidColorBrush(Color.FromArgb(72, 219, 234, 254));
|
||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Visible;
|
||||
|
||||
// PenPalette.Margin = new Thickness(-160, -157, -33, 32);
|
||||
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -157, -33, 32),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
||||
PenPalette.BeginAnimation(MarginProperty, null);
|
||||
var currentMargin = PenPalette.Margin;
|
||||
// 荧光笔模式面板稍小,使用不同的Top/Bottom
|
||||
PenPalette.Margin = new Thickness(currentMargin.Left, -157, currentMargin.Right, 32);
|
||||
UpdatePenPalettePosition();
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -154, -33, 50),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
|
||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
|
||||
var currentMargin = BoardPenPaletteGrid.Margin;
|
||||
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -154, currentMargin.Right, 50);
|
||||
});
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -157, -33, 32); });
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -154, -33, 50); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
@@ -34,6 +35,14 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private Point dragStartPoint;
|
||||
|
||||
/// <summary>页码侧栏当前订阅 <see cref="PdfEmbeddedView.PageNavigationStateChanged"/> 的 PDF 视图。</summary>
|
||||
private PdfEmbeddedView _pdfPageSidebarEventSource;
|
||||
|
||||
private bool _pdfSidebarPositionRefreshPending;
|
||||
|
||||
/// <summary>为 true 时,下一次成功算出 PDF 边界后的侧栏定位使用宿主 Visual 变换(仅用于刚插入/恢复 PDF 的首帧对齐)。</summary>
|
||||
private bool _pdfSidebarNextPositionUseHostTransform;
|
||||
|
||||
#region Image
|
||||
/// <summary>
|
||||
/// 处理图片插入按钮点击事件
|
||||
@@ -56,48 +65,52 @@ namespace Ink_Canvas
|
||||
private async void BtnImageInsert_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFileDialog openFileDialog = new OpenFileDialog();
|
||||
openFileDialog.Filter = "Image files (*.jpg; *.jpeg; *.png; *.bmp)|*.jpg;*.jpeg;*.png;*.bmp";
|
||||
openFileDialog.Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf";
|
||||
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
string filePath = openFileDialog.FileName;
|
||||
|
||||
Image image = await CreateAndCompressImageAsync(filePath);
|
||||
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||
|
||||
if (image != null)
|
||||
if (element != null)
|
||||
{
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
element.Name = timestamp;
|
||||
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
element.IsHitTestVisible = true;
|
||||
element.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
InitializeInkCanvasSelectionSettings();
|
||||
|
||||
// 先添加到画布
|
||||
inkCanvas.Children.Add(image);
|
||||
inkCanvas.Children.Add(element);
|
||||
|
||||
// 等待图片加载完成后再进行后续处理
|
||||
image.Loaded += (s, args) =>
|
||||
element.Loaded += (s, args) =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
// 初始化TransformGroup
|
||||
InitializeElementTransform(image);
|
||||
InitializeElementTransform(element);
|
||||
|
||||
// 居中缩放
|
||||
CenterAndScaleElement(image);
|
||||
CenterAndScaleElement(element);
|
||||
|
||||
// 最后绑定事件处理器
|
||||
BindElementEvents(image);
|
||||
BindElementEvents(element);
|
||||
|
||||
LogHelper.WriteLogToFile($"图片插入完成: {image.Name}");
|
||||
if (element is PdfEmbeddedView)
|
||||
_pdfSidebarNextPositionUseHostTransform = true;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
|
||||
LogHelper.WriteLogToFile($"图片插入完成: {element.Name}");
|
||||
}), DispatcherPriority.Loaded);
|
||||
};
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
timeMachine.CommitElementInsertHistory(element);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
@@ -270,13 +283,13 @@ namespace Ink_Canvas
|
||||
ApplyMouseDragTransform(element, currentPoint, dragStartPoint);
|
||||
|
||||
// 如果是图片元素,更新工具栏位置
|
||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
// 如果是图片元素,更新选择点位置
|
||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
}
|
||||
@@ -306,13 +319,13 @@ namespace Ink_Canvas
|
||||
ApplyWheelScaleTransform(element, e);
|
||||
|
||||
// 如果是图片元素,更新工具栏位置
|
||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
// 如果是图片元素,更新选择点位置
|
||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
}
|
||||
@@ -396,13 +409,13 @@ namespace Ink_Canvas
|
||||
ApplyTouchManipulationTransform(element, e);
|
||||
|
||||
// 如果是图片元素,更新工具栏位置
|
||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
// 如果是图片元素,更新选择点位置
|
||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
}
|
||||
@@ -523,12 +536,12 @@ namespace Ink_Canvas
|
||||
currentSelectedElement = element;
|
||||
|
||||
// 根据元素类型显示不同的选择工具栏
|
||||
if (element is Image)
|
||||
if (IsBitmapLikeCanvasElement(element))
|
||||
{
|
||||
// 显示图片选择工具栏并设置位置
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
// 计算工具栏位置
|
||||
// 计算工具栏位置(内部会同步 PDF 右侧栏位置)
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
BorderImageSelectionControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
@@ -568,6 +581,8 @@ namespace Ink_Canvas
|
||||
// 保持选择模式,这样用户可以直接点击墨迹来选择
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
||||
}
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -607,6 +622,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
||||
}
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -914,15 +931,24 @@ namespace Ink_Canvas
|
||||
/// - 否则使用原始尺寸
|
||||
/// - 返回创建的Image对象
|
||||
/// </remarks>
|
||||
private async Task<Image> CreateAndCompressImageAsync(string filePath)
|
||||
/// <summary>与图片选择工具栏、缩放控制点联动的画布位图类元素(普通图片或多页 PDF 嵌入)。</summary>
|
||||
private static bool IsBitmapLikeCanvasElement(FrameworkElement fe)
|
||||
{
|
||||
return fe is Image || fe is PdfEmbeddedView;
|
||||
}
|
||||
|
||||
private async Task<FrameworkElement> CreateAndCompressImageAsync(string filePath)
|
||||
{
|
||||
string fileExtension = Path.GetExtension(filePath);
|
||||
if (string.Equals(fileExtension, ".pdf", StringComparison.OrdinalIgnoreCase))
|
||||
return await CreateAndCompressImageFromPdfAsync(filePath);
|
||||
|
||||
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||
if (!Directory.Exists(savePath))
|
||||
{
|
||||
Directory.CreateDirectory(savePath);
|
||||
}
|
||||
|
||||
string fileExtension = Path.GetExtension(filePath);
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
string newFilePath = Path.Combine(savePath, timestamp + fileExtension);
|
||||
|
||||
@@ -965,6 +991,84 @@ namespace Ink_Canvas
|
||||
return image;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入完整 PDF:嵌入控件内可翻页,右下角显示页码(类似希沃白板交互)。
|
||||
/// </summary>
|
||||
private async Task<PdfEmbeddedView> CreateAndCompressImageFromPdfAsync(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||
if (!Directory.Exists(savePath))
|
||||
Directory.CreateDirectory(savePath);
|
||||
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
string newFilePath = Path.Combine(savePath, timestamp + ".pdf");
|
||||
await Task.Run(() => File.Copy(filePath, newFilePath, true));
|
||||
|
||||
uint pageCount = await PdfWinRtHelper.GetPageCountAsync(newFilePath);
|
||||
if (pageCount == 0)
|
||||
{
|
||||
ShowNotification("无法打开 PDF(可能已加密、损坏或不支持)。");
|
||||
return null;
|
||||
}
|
||||
|
||||
bool compress = isLoaded && Settings.Canvas.IsCompressPicturesUploaded;
|
||||
var view = new PdfEmbeddedView();
|
||||
await view.InitializeAsync(newFilePath, pageCount, compress);
|
||||
view.Tag = filePath;
|
||||
return view;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插入 PDF 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ShowNotification($"插入 PDF 失败: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从保存的 <see cref="CanvasElementInfo"/> 恢复 PDF(与打开墨迹时的图片恢复流程一致,不单独写入时间轴)。</summary>
|
||||
private async Task RestorePdfFromElementInfoAsync(CanvasElementInfo info)
|
||||
{
|
||||
if (info == null || inkCanvas == null) return;
|
||||
if (!string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase)) return;
|
||||
if (string.IsNullOrEmpty(info.SourcePath) || !File.Exists(info.SourcePath)) return;
|
||||
|
||||
try
|
||||
{
|
||||
uint pageCount = await PdfWinRtHelper.GetPageCountAsync(info.SourcePath);
|
||||
if (pageCount == 0) return;
|
||||
|
||||
bool compress = isLoaded && Settings.Canvas.IsCompressPicturesUploaded;
|
||||
uint initial = 0;
|
||||
if (info.PdfCurrentPage.HasValue)
|
||||
initial = (uint)Math.Max(0, Math.Min(info.PdfCurrentPage.Value, (int)pageCount - 1));
|
||||
|
||||
var view = new PdfEmbeddedView
|
||||
{
|
||||
Name = "pdf_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff")
|
||||
};
|
||||
await view.InitializeAsync(info.SourcePath, pageCount, compress, initial);
|
||||
|
||||
if (info.Width > 0) view.Width = info.Width;
|
||||
if (info.Height > 0) view.Height = info.Height;
|
||||
|
||||
InkCanvas.SetLeft(view, info.Left);
|
||||
InkCanvas.SetTop(view, info.Top);
|
||||
|
||||
InitializeElementTransform(view);
|
||||
BindElementEvents(view);
|
||||
inkCanvas.Children.Add(view);
|
||||
_pdfSidebarNextPositionUseHostTransform = true;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从 .elements.json 恢复 PDF 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Media
|
||||
@@ -1453,6 +1557,10 @@ namespace Ink_Canvas
|
||||
|
||||
// 设置工具栏位置
|
||||
BorderImageSelectionControl.Margin = new Thickness(toolbarLeft, toolbarTop, 0, 0);
|
||||
|
||||
var pdfTarget = GetPdfSidebarTargetElement();
|
||||
if (pdfTarget != null && BorderPdfPageSidebar != null && BorderPdfPageSidebar.Visibility == Visibility.Visible)
|
||||
UpdatePdfPageSidebarPosition(pdfTarget);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1460,6 +1568,245 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private const double PdfPageSidebarGap = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 侧栏绑定的 PDF:若当前选中的是 PDF 则用该项;否则用画布上最后一个 PdfEmbeddedView。
|
||||
/// </summary>
|
||||
private PdfEmbeddedView GetPdfSidebarTargetElement()
|
||||
{
|
||||
if (inkCanvas == null) return null;
|
||||
var pdfs = inkCanvas.Children.OfType<PdfEmbeddedView>().ToList();
|
||||
if (pdfs.Count == 0) return null;
|
||||
if (currentSelectedElement is PdfEmbeddedView sel && pdfs.Contains(sel))
|
||||
return sel;
|
||||
return pdfs[pdfs.Count - 1];
|
||||
}
|
||||
|
||||
private void AttachPdfPageSidebarEvents(PdfEmbeddedView pdf)
|
||||
{
|
||||
if (pdf == null || _pdfPageSidebarEventSource == pdf) return;
|
||||
DetachPdfPageSidebarEvents();
|
||||
_pdfPageSidebarEventSource = pdf;
|
||||
_pdfPageSidebarEventSource.PageNavigationStateChanged += SelectedPdf_PageNavigationStateChanged;
|
||||
_pdfPageSidebarEventSource.LayoutUpdated += OnPdfSidebarTargetLayoutUpdated;
|
||||
}
|
||||
|
||||
private void DetachPdfPageSidebarEvents()
|
||||
{
|
||||
if (_pdfPageSidebarEventSource != null)
|
||||
{
|
||||
_pdfPageSidebarEventSource.PageNavigationStateChanged -= SelectedPdf_PageNavigationStateChanged;
|
||||
_pdfPageSidebarEventSource.LayoutUpdated -= OnPdfSidebarTargetLayoutUpdated;
|
||||
_pdfPageSidebarEventSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPdfSidebarTargetLayoutUpdated(object sender, EventArgs e)
|
||||
{
|
||||
if (BorderPdfPageSidebar?.Visibility != Visibility.Visible || inkCanvas == null) return;
|
||||
if (!(sender is PdfEmbeddedView p) || !ReferenceEquals(_pdfPageSidebarEventSource, p)) return;
|
||||
if (!inkCanvas.Children.Contains(p))
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
else
|
||||
RequestPdfSidebarPositionRefresh();
|
||||
}
|
||||
|
||||
/// <summary>在下一帧合并更新侧栏位置(初始布局、翻页改尺寸后 ActualWidth 仍为 0 时尤其需要)。</summary>
|
||||
private void RequestPdfSidebarPositionRefresh()
|
||||
{
|
||||
if (_pdfSidebarPositionRefreshPending) return;
|
||||
_pdfSidebarPositionRefreshPending = true;
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
_pdfSidebarPositionRefreshPending = false;
|
||||
try
|
||||
{
|
||||
var t = GetPdfSidebarTargetElement();
|
||||
if (t == null || BorderPdfPageSidebar?.Visibility != Visibility.Visible)
|
||||
{
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inkCanvas.Children.Contains(t))
|
||||
{
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
t.UpdateLayout();
|
||||
inkCanvas.UpdateLayout();
|
||||
UpdatePdfPageSidebarPosition(t);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PDF 侧栏延迟定位失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}), DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 画布上存在 PDF 时始终显示右侧页码栏并跟随目标 PDF;无任何 PDF 时隐藏。
|
||||
/// </summary>
|
||||
private void SyncPdfPageSidebarWithCanvas()
|
||||
{
|
||||
if (BorderPdfPageSidebar == null || inkCanvas == null) return;
|
||||
|
||||
// 屏幕模式(已退出白板/黑板)下不显示侧栏,避免画布仍含 PDF 时栏残留在桌面上
|
||||
if (currentMode == 0)
|
||||
{
|
||||
DetachPdfPageSidebarEvents();
|
||||
BorderPdfPageSidebar.Visibility = Visibility.Collapsed;
|
||||
ResetPdfSidebarToIdle();
|
||||
return;
|
||||
}
|
||||
|
||||
var pdf = GetPdfSidebarTargetElement();
|
||||
if (pdf == null)
|
||||
{
|
||||
DetachPdfPageSidebarEvents();
|
||||
BorderPdfPageSidebar.Visibility = Visibility.Collapsed;
|
||||
ResetPdfSidebarToIdle();
|
||||
return;
|
||||
}
|
||||
|
||||
AttachPdfPageSidebarEvents(pdf);
|
||||
BorderPdfPageSidebar.Visibility = Visibility.Visible;
|
||||
UpdatePdfSidebarFromPdf(pdf);
|
||||
pdf.UpdateLayout();
|
||||
inkCanvas.UpdateLayout();
|
||||
UpdatePdfPageSidebarPosition(pdf);
|
||||
RequestPdfSidebarPositionRefresh();
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
var t = GetPdfSidebarTargetElement();
|
||||
if (t != null && BorderPdfPageSidebar?.Visibility == Visibility.Visible && inkCanvas.Children.Contains(t))
|
||||
UpdatePdfPageSidebarPosition(t);
|
||||
}), DispatcherPriority.ContextIdle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 PDF 专用页码栏贴在当前 PDF 右侧。常态与早期实现一致:画布坐标 + <c>Measure(Width, ∞)</c>;仅在 <see cref="_pdfSidebarNextPositionUseHostTransform"/> 为 true 时用宿主 Visual 变换对齐首帧。
|
||||
/// </summary>
|
||||
private void UpdatePdfPageSidebarPosition(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (BorderPdfPageSidebar == null || inkCanvas == null || !(element is PdfEmbeddedView pdfEl))
|
||||
return;
|
||||
|
||||
if (!inkCanvas.Children.Contains(pdfEl))
|
||||
{
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
bool wantHostOnce = _pdfSidebarNextPositionUseHostTransform;
|
||||
|
||||
// 插入首帧:先布局再取界,便于 Transform 与墨迹一致;常态与最初 PDF 侧栏实现一致,不在此强制 UpdateLayout。
|
||||
if (wantHostOnce)
|
||||
pdfEl.UpdateLayout();
|
||||
|
||||
Rect b = GetElementActualBounds(pdfEl);
|
||||
if (b.Width <= 0 || b.Height <= 0 || double.IsNaN(b.Width) || double.IsNaN(b.Height))
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
var t = GetPdfSidebarTargetElement();
|
||||
if (t is PdfEmbeddedView pe && inkCanvas.Children.Contains(pe) && BorderPdfPageSidebar?.Visibility == Visibility.Visible)
|
||||
UpdatePdfPageSidebarPosition(pe);
|
||||
}), DispatcherPriority.Loaded);
|
||||
return;
|
||||
}
|
||||
|
||||
Visual sidebarHost = VisualTreeHelper.GetParent(BorderPdfPageSidebar) as Visual;
|
||||
double left = 0, top = 0, maxLeft = 0, maxTop = 0;
|
||||
bool hostOk = false;
|
||||
|
||||
if (wantHostOnce && sidebarHost != null)
|
||||
{
|
||||
BorderPdfPageSidebar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
double sidebarW = BorderPdfPageSidebar.DesiredSize.Width;
|
||||
double sidebarH = BorderPdfPageSidebar.DesiredSize.Height;
|
||||
if (sidebarW <= 0)
|
||||
sidebarW = BorderPdfPageSidebar.Width;
|
||||
if (sidebarH <= 0)
|
||||
sidebarH = BorderPdfPageSidebar.ActualHeight;
|
||||
if (sidebarH <= 0)
|
||||
sidebarH = 220;
|
||||
|
||||
try
|
||||
{
|
||||
GeneralTransform inkToHost = inkCanvas.TransformToVisual(sidebarHost);
|
||||
Point tl = inkToHost.Transform(new Point(b.Left, b.Top));
|
||||
Point br = inkToHost.Transform(new Point(b.Right, b.Bottom));
|
||||
double rightX = Math.Max(tl.X, br.X);
|
||||
double midY = (tl.Y + br.Y) * 0.5;
|
||||
left = rightX + PdfPageSidebarGap;
|
||||
top = midY - sidebarH * 0.5;
|
||||
|
||||
var feHost = sidebarHost as FrameworkElement;
|
||||
double hostW = feHost != null && feHost.ActualWidth > 0 ? feHost.ActualWidth : inkCanvas.ActualWidth;
|
||||
double hostH = feHost != null && feHost.ActualHeight > 0 ? feHost.ActualHeight : inkCanvas.ActualHeight;
|
||||
maxLeft = Math.Max(0, hostW - sidebarW);
|
||||
maxTop = Math.Max(0, hostH - sidebarH);
|
||||
|
||||
if (left > maxLeft)
|
||||
{
|
||||
double leftEdge = Math.Min(tl.X, br.X);
|
||||
double leftAlt = leftEdge - PdfPageSidebarGap - sidebarW;
|
||||
if (leftAlt >= 0)
|
||||
left = leftAlt;
|
||||
}
|
||||
|
||||
hostOk = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
hostOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hostOk)
|
||||
{
|
||||
// 与 ea74592「PDF 侧栏」初版一致:固定宽度测量竖向所需高度,再按墨迹边界与 inkCanvas 尺寸夹紧。
|
||||
BorderPdfPageSidebar.Measure(new Size(BorderPdfPageSidebar.Width, double.PositiveInfinity));
|
||||
double sidebarW = BorderPdfPageSidebar.DesiredSize.Width;
|
||||
double sidebarH = BorderPdfPageSidebar.DesiredSize.Height;
|
||||
if (sidebarW <= 0)
|
||||
sidebarW = BorderPdfPageSidebar.Width;
|
||||
if (sidebarH <= 0)
|
||||
sidebarH = BorderPdfPageSidebar.ActualHeight;
|
||||
if (sidebarH <= 0)
|
||||
sidebarH = 220;
|
||||
|
||||
left = b.Right + PdfPageSidebarGap;
|
||||
top = b.Top + (b.Height * 0.5) - (sidebarH * 0.5);
|
||||
maxLeft = Math.Max(0, inkCanvas.ActualWidth - sidebarW);
|
||||
maxTop = Math.Max(0, inkCanvas.ActualHeight - sidebarH);
|
||||
if (left > maxLeft)
|
||||
{
|
||||
double leftAlt = b.Left - PdfPageSidebarGap - sidebarW;
|
||||
if (leftAlt >= 0)
|
||||
left = leftAlt;
|
||||
}
|
||||
}
|
||||
|
||||
if (wantHostOnce)
|
||||
_pdfSidebarNextPositionUseHostTransform = false;
|
||||
|
||||
left = Math.Max(0, Math.Min(left, maxLeft));
|
||||
top = Math.Max(0, Math.Min(top, maxTop));
|
||||
|
||||
BorderPdfPageSidebar.Margin = new Thickness(left, top, 0, 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新 PDF 右侧页码栏位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取元素的实际边界(考虑变换)
|
||||
/// </summary>
|
||||
@@ -1644,7 +1991,7 @@ namespace Ink_Canvas
|
||||
ApplyRotateTransform(currentSelectedElement, -45);
|
||||
|
||||
// 更新工具栏位置
|
||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||
}
|
||||
@@ -1678,7 +2025,7 @@ namespace Ink_Canvas
|
||||
ApplyRotateTransform(currentSelectedElement, 45);
|
||||
|
||||
// 更新工具栏位置
|
||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||
}
|
||||
@@ -1714,7 +2061,7 @@ namespace Ink_Canvas
|
||||
ApplyScaleTransform(currentSelectedElement, 0.9, elementCenter);
|
||||
|
||||
// 更新工具栏位置
|
||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||
}
|
||||
@@ -1750,7 +2097,7 @@ namespace Ink_Canvas
|
||||
ApplyScaleTransform(currentSelectedElement, 1.1, elementCenter);
|
||||
|
||||
// 更新工具栏位置
|
||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||
}
|
||||
@@ -1764,19 +2111,110 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPdfSidebarToIdle()
|
||||
{
|
||||
if (TextBlockPdfSidebarPageLabel != null)
|
||||
{
|
||||
TextBlockPdfSidebarPageLabel.Text = "— / —";
|
||||
TextBlockPdfSidebarPageLabel.Opacity = 0.55;
|
||||
}
|
||||
|
||||
if (BorderPdfSidebarPagePrev != null)
|
||||
{
|
||||
BorderPdfSidebarPagePrev.Opacity = 0.35;
|
||||
BorderPdfSidebarPagePrev.IsHitTestVisible = false;
|
||||
}
|
||||
|
||||
if (BorderPdfSidebarPageNext != null)
|
||||
{
|
||||
BorderPdfSidebarPageNext.Opacity = 0.35;
|
||||
BorderPdfSidebarPageNext.IsHitTestVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePdfSidebarFromPdf(PdfEmbeddedView pdf)
|
||||
{
|
||||
if (pdf == null) return;
|
||||
|
||||
if (TextBlockPdfSidebarPageLabel != null)
|
||||
{
|
||||
TextBlockPdfSidebarPageLabel.Text = pdf.PageLabelText;
|
||||
TextBlockPdfSidebarPageLabel.Opacity = 1.0;
|
||||
}
|
||||
|
||||
bool prevOk = pdf.CanGoPrevious;
|
||||
bool nextOk = pdf.CanGoNext;
|
||||
if (BorderPdfSidebarPagePrev != null)
|
||||
{
|
||||
BorderPdfSidebarPagePrev.Opacity = prevOk ? 1.0 : 0.35;
|
||||
BorderPdfSidebarPagePrev.IsHitTestVisible = prevOk;
|
||||
}
|
||||
|
||||
if (BorderPdfSidebarPageNext != null)
|
||||
{
|
||||
BorderPdfSidebarPageNext.Opacity = nextOk ? 1.0 : 0.35;
|
||||
BorderPdfSidebarPageNext.IsHitTestVisible = nextOk;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectedPdf_PageNavigationStateChanged(object sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (sender is PdfEmbeddedView pdf)
|
||||
{
|
||||
if (!inkCanvas.Children.Contains(pdf))
|
||||
{
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePdfSidebarFromPdf(pdf);
|
||||
UpdatePdfPageSidebarPosition(pdf);
|
||||
RequestPdfSidebarPositionRefresh();
|
||||
}
|
||||
if (currentSelectedElement != null && IsBitmapLikeCanvasElement(currentSelectedElement))
|
||||
{
|
||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||
if (ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(currentSelectedElement));
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private async void BorderPdfSidebarPagePrev_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pdf = GetPdfSidebarTargetElement();
|
||||
if (pdf != null && pdf.CanGoPrevious)
|
||||
await pdf.GoToPreviousPageAsync();
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PDF 上一页失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void BorderPdfSidebarPageNext_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pdf = GetPdfSidebarTargetElement();
|
||||
if (pdf != null && pdf.CanGoNext)
|
||||
await pdf.GoToNextPageAsync();
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PDF 下一页失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理图片删除功能
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="e">事件参数</param>
|
||||
/// <remarks>
|
||||
/// - 检查当前是否有选中元素
|
||||
/// - 保存删除前的编辑模式
|
||||
/// - 记录删除历史
|
||||
/// - 从画布中移除
|
||||
/// - 清除选中状态
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1789,16 +2227,19 @@ namespace Ink_Canvas
|
||||
// 记录删除历史
|
||||
timeMachine.CommitElementRemoveHistory(currentSelectedElement);
|
||||
|
||||
var toRemove = currentSelectedElement;
|
||||
// 从画布中移除
|
||||
inkCanvas.Children.Remove(currentSelectedElement);
|
||||
inkCanvas.Children.Remove(toRemove);
|
||||
|
||||
// 清除选中状态
|
||||
UnselectElement(currentSelectedElement);
|
||||
UnselectElement(toRemove);
|
||||
currentSelectedElement = null;
|
||||
|
||||
// 恢复到删除前的编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
|
||||
LogHelper.WriteLogToFile($"图片删除完成,已恢复到编辑模式: {previousEditingMode}");
|
||||
}
|
||||
}
|
||||
@@ -2029,7 +2470,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement is Image image && sender is Ellipse ellipse)
|
||||
if (IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse)
|
||||
{
|
||||
isResizingImage = true;
|
||||
imageResizeStartPoint = e.GetPosition(inkCanvas);
|
||||
@@ -2072,10 +2513,10 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isResizingImage && currentSelectedElement is Image image && sender is Ellipse ellipse)
|
||||
if (isResizingImage && IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse)
|
||||
{
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
ResizeImageByHandle(image, imageResizeStartPoint, currentPoint, activeResizeHandle);
|
||||
ResizeImageByHandle(currentSelectedElement, imageResizeStartPoint, currentPoint, activeResizeHandle);
|
||||
imageResizeStartPoint = currentPoint;
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -2087,11 +2528,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 根据控制点缩放图片
|
||||
private void ResizeImageByHandle(Image image, Point startPoint, Point currentPoint, string handleName)
|
||||
private void ResizeImageByHandle(FrameworkElement element, Point startPoint, Point currentPoint, string handleName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (image.RenderTransform is TransformGroup transformGroup)
|
||||
if (element.RenderTransform is TransformGroup transformGroup)
|
||||
{
|
||||
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||
@@ -2099,7 +2540,7 @@ namespace Ink_Canvas
|
||||
if (scaleTransform == null || translateTransform == null) return;
|
||||
|
||||
// 获取图片的当前边界
|
||||
Rect currentBounds = GetElementActualBounds(image);
|
||||
Rect currentBounds = GetElementActualBounds(element);
|
||||
double deltaX = currentPoint.X - startPoint.X;
|
||||
double deltaY = currentPoint.Y - startPoint.Y;
|
||||
|
||||
@@ -2160,7 +2601,10 @@ namespace Ink_Canvas
|
||||
translateTransform.Y += translateY;
|
||||
|
||||
// 更新选择点位置
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(image));
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
|
||||
if (BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using System;
|
||||
@@ -18,7 +19,6 @@ using Application = System.Windows.Application;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
using Cursors = System.Windows.Input.Cursors;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using Image = System.Windows.Controls.Image;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||
@@ -49,6 +49,7 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
HideSubPanels();
|
||||
UpdateTwoFingerGestureBorderPosition();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardTwoFingerGestureBorder);
|
||||
}
|
||||
@@ -370,7 +371,7 @@ namespace Ink_Canvas
|
||||
/// <param name="autoAlignCenter">
|
||||
/// 是否自動居中浮動工具欄
|
||||
/// </param>
|
||||
private async void HideSubPanels(string mode = null, bool autoAlignCenter = false)
|
||||
internal async void HideSubPanels(string mode = null, bool autoAlignCenter = false)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
||||
@@ -1365,28 +1366,27 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
string protocol = "";
|
||||
string[] protocols;
|
||||
switch (Settings.RandSettings.ExternalCallerType)
|
||||
{
|
||||
case 0: // ClassIsland点名
|
||||
protocol = "classisland://plugins/IslandCaller/Simple/1";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByType(0);
|
||||
break;
|
||||
case 1: // SecRandom点名
|
||||
protocol = "secrandom://direct_extraction";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByType(1);
|
||||
break;
|
||||
case 2: // NamePicker点名
|
||||
protocol = "namepicker://";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByType(2);
|
||||
break;
|
||||
default:
|
||||
protocol = "classisland://plugins/IslandCaller/Simple/1";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByType(0);
|
||||
break;
|
||||
}
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
if (!ExternalCallerLauncher.TryLaunch(protocols, out Exception lastException))
|
||||
{
|
||||
FileName = protocol,
|
||||
UseShellExecute = true
|
||||
});
|
||||
throw lastException ?? new InvalidOperationException("external caller protocols are unavailable");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1743,6 +1743,7 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
HideSubPanels();
|
||||
UpdateBorderToolsPosition();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderTools);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderTools);
|
||||
}
|
||||
@@ -2440,6 +2441,7 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
HideSubPanels();
|
||||
UpdatePenPalettePosition();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(PenPalette);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardPenPalette);
|
||||
}
|
||||
@@ -2549,6 +2551,7 @@ namespace Ink_Canvas
|
||||
// 已是橡皮状态,再次点击才弹出/收起面板
|
||||
if (EraserSizePanel.Visibility == Visibility.Collapsed)
|
||||
{
|
||||
UpdateEraserSizePanelPosition();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
|
||||
if (BoardEraserSizePanel != null)
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
|
||||
@@ -2599,6 +2602,7 @@ namespace Ink_Canvas
|
||||
// 已是橡皮状态,再次点击才弹出/收起面板
|
||||
if (BoardEraserSizePanel != null && BoardEraserSizePanel.Visibility == Visibility.Collapsed)
|
||||
{
|
||||
UpdateEraserSizePanelPosition();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
|
||||
}
|
||||
@@ -3178,7 +3182,7 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
/// <param name="sender">发送者</param>
|
||||
/// <param name="e">路由事件参数</param>
|
||||
private async void BtnSettings_Click(object sender, RoutedEventArgs e)
|
||||
internal async void BtnSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (BorderSettings.Visibility == Visibility.Visible)
|
||||
{
|
||||
@@ -3299,6 +3303,9 @@ namespace Ink_Canvas
|
||||
// 清空触摸点计数器
|
||||
dec.Clear();
|
||||
|
||||
if (isPalmEraserActive)
|
||||
isPalmEraserActive = false;
|
||||
|
||||
// 确保触摸事件能正常响应
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
@@ -3568,6 +3575,8 @@ namespace Ink_Canvas
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
|
||||
private int BoundsWidth = 5;
|
||||
@@ -3775,32 +3784,29 @@ namespace Ink_Canvas
|
||||
// Open file dialog to select image
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
||||
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
string filePath = dialog.FileName;
|
||||
Image image = await CreateAndCompressImageAsync(filePath);
|
||||
if (image != null)
|
||||
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||
if (element != null)
|
||||
{
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
element.Name = timestamp;
|
||||
|
||||
// 初始化TransformGroup
|
||||
if (image is FrameworkElement element)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
}
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
|
||||
CenterAndScaleElement(image);
|
||||
CenterAndScaleElement(element);
|
||||
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
element.IsHitTestVisible = true;
|
||||
element.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
if (inkCanvas != null)
|
||||
@@ -3811,34 +3817,33 @@ namespace Ink_Canvas
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
inkCanvas.Children.Add(image);
|
||||
inkCanvas.Children.Add(element);
|
||||
|
||||
// 绑定事件处理器
|
||||
if (image is FrameworkElement elementForEvents)
|
||||
{
|
||||
// 鼠标事件
|
||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
elementForEvents.MouseMove += Element_MouseMove;
|
||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
element.MouseMove += Element_MouseMove;
|
||||
element.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
elementForEvents.TouchDown += Element_TouchDown;
|
||||
elementForEvents.TouchUp += Element_TouchUp;
|
||||
elementForEvents.IsManipulationEnabled = true;
|
||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
// 触摸事件
|
||||
element.TouchDown += Element_TouchDown;
|
||||
element.TouchUp += Element_TouchUp;
|
||||
element.IsManipulationEnabled = true;
|
||||
element.ManipulationDelta += Element_ManipulationDelta;
|
||||
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
|
||||
// 设置光标
|
||||
elementForEvents.Cursor = Cursors.Hand;
|
||||
}
|
||||
// 设置光标
|
||||
element.Cursor = Cursors.Hand;
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
timeMachine.CommitElementInsertHistory(element);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
if (element is PdfEmbeddedView)
|
||||
_pdfSidebarNextPositionUseHostTransform = true;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3848,32 +3853,29 @@ namespace Ink_Canvas
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
||||
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
string filePath = dialog.FileName;
|
||||
Image image = await CreateAndCompressImageAsync(filePath);
|
||||
if (image != null)
|
||||
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||
if (element != null)
|
||||
{
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
element.Name = timestamp;
|
||||
|
||||
// 初始化TransformGroup
|
||||
if (image is FrameworkElement element)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
}
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
|
||||
CenterAndScaleElement(image);
|
||||
CenterAndScaleElement(element);
|
||||
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
element.IsHitTestVisible = true;
|
||||
element.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
if (inkCanvas != null)
|
||||
@@ -3884,34 +3886,33 @@ namespace Ink_Canvas
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
inkCanvas.Children.Add(image);
|
||||
inkCanvas.Children.Add(element);
|
||||
|
||||
// 绑定事件处理器
|
||||
if (image is FrameworkElement elementForEvents)
|
||||
{
|
||||
// 鼠标事件
|
||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
elementForEvents.MouseMove += Element_MouseMove;
|
||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
element.MouseMove += Element_MouseMove;
|
||||
element.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
elementForEvents.TouchDown += Element_TouchDown;
|
||||
elementForEvents.TouchUp += Element_TouchUp;
|
||||
elementForEvents.IsManipulationEnabled = true;
|
||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
// 触摸事件
|
||||
element.TouchDown += Element_TouchDown;
|
||||
element.TouchUp += Element_TouchUp;
|
||||
element.IsManipulationEnabled = true;
|
||||
element.ManipulationDelta += Element_ManipulationDelta;
|
||||
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
|
||||
// 设置光标
|
||||
elementForEvents.Cursor = Cursors.Hand;
|
||||
}
|
||||
// 设置光标
|
||||
element.Cursor = Cursors.Hand;
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
timeMachine.CommitElementInsertHistory(element);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
if (element is PdfEmbeddedView)
|
||||
_pdfSidebarNextPositionUseHostTransform = true;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3921,32 +3922,29 @@ namespace Ink_Canvas
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
||||
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
string filePath = dialog.FileName;
|
||||
Image image = await CreateAndCompressImageAsync(filePath); // 补充image定义
|
||||
if (image != null)
|
||||
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||
if (element != null)
|
||||
{
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
element.Name = timestamp;
|
||||
|
||||
// 初始化TransformGroup
|
||||
if (image is FrameworkElement element)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
}
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
|
||||
CenterAndScaleElement(image);
|
||||
CenterAndScaleElement(element);
|
||||
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
element.IsHitTestVisible = true;
|
||||
element.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
if (inkCanvas != null)
|
||||
@@ -3957,34 +3955,33 @@ namespace Ink_Canvas
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
inkCanvas.Children.Add(image);
|
||||
inkCanvas.Children.Add(element);
|
||||
|
||||
// 绑定事件处理器
|
||||
if (image is FrameworkElement elementForEvents)
|
||||
{
|
||||
// 鼠标事件
|
||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
elementForEvents.MouseMove += Element_MouseMove;
|
||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
element.MouseMove += Element_MouseMove;
|
||||
element.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
elementForEvents.TouchDown += Element_TouchDown;
|
||||
elementForEvents.TouchUp += Element_TouchUp;
|
||||
elementForEvents.IsManipulationEnabled = true;
|
||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
// 触摸事件
|
||||
element.TouchDown += Element_TouchDown;
|
||||
element.TouchUp += Element_TouchUp;
|
||||
element.IsManipulationEnabled = true;
|
||||
element.ManipulationDelta += Element_ManipulationDelta;
|
||||
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
|
||||
// 设置光标
|
||||
elementForEvents.Cursor = Cursors.Hand;
|
||||
}
|
||||
// 设置光标
|
||||
element.Cursor = Cursors.Hand;
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
timeMachine.CommitElementInsertHistory(element);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
if (element is PdfEmbeddedView)
|
||||
_pdfSidebarNextPositionUseHostTransform = true;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4217,6 +4214,101 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用子面板位置更新方法:根据触发按钮的位置,动态调整子面板的水平位置,
|
||||
/// 使面板水平中心对齐按钮中心。不改变面板大小,不改变上下边距。
|
||||
/// </summary>
|
||||
/// <param name="button">触发按钮元素</param>
|
||||
/// <param name="panel">需要定位的子面板</param>
|
||||
/// <param name="defaultPanelWidth">面板默认宽度(当无法从Margin计算时使用)</param>
|
||||
private void UpdateSubPanelPosition(FrameworkElement button, FrameworkElement panel, double defaultPanelWidth)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (button == null || panel == null) return;
|
||||
if (!(panel.Parent is FrameworkElement panelContainer)) return;
|
||||
|
||||
var ancestor = StackPanelFloatingBar;
|
||||
if (ancestor == null) return;
|
||||
|
||||
// 获取按钮中心的X坐标(相对于 StackPanelFloatingBar 坐标系)
|
||||
var buttonTransform = button.TransformToAncestor(ancestor);
|
||||
var buttonOrigin = buttonTransform.Transform(new Point(0, 0));
|
||||
double buttonCenterX = buttonOrigin.X + button.ActualWidth / 2.0;
|
||||
|
||||
// 获取面板容器(零宽度Grid)的X坐标(相对于 StackPanelFloatingBar 坐标系)
|
||||
var containerTransform = panelContainer.TransformToAncestor(ancestor);
|
||||
var containerOrigin = containerTransform.Transform(new Point(0, 0));
|
||||
double containerX = containerOrigin.X;
|
||||
|
||||
// 计算当前面板宽度(保持不变):panelWidth = -Margin.Left - Margin.Right
|
||||
double currentLeft = panel.Margin.Left;
|
||||
double currentRight = panel.Margin.Right;
|
||||
double panelWidth = -currentLeft - currentRight;
|
||||
if (panelWidth <= 0) panelWidth = defaultPanelWidth;
|
||||
|
||||
// 计算新的左边距,使面板水平中心对齐按钮:
|
||||
// panel_center = containerX + newLeft + panelWidth/2 = buttonCenterX
|
||||
// => newLeft = buttonCenterX - containerX - panelWidth/2
|
||||
double newLeft = buttonCenterX - containerX - panelWidth / 2.0;
|
||||
|
||||
// 保持面板宽度不变:-newLeft - newRight = panelWidth
|
||||
// => newRight = -panelWidth - newLeft
|
||||
double newRight = -panelWidth - newLeft;
|
||||
|
||||
// 清除可能残留的 Margin 动画(HoldEnd 会阻止本地值生效)
|
||||
panel.BeginAnimation(FrameworkElement.MarginProperty, null);
|
||||
|
||||
// 更新边距,仅调整Left/Right,保持Top/Bottom不变
|
||||
var margin = panel.Margin;
|
||||
panel.Margin = new Thickness(newLeft, margin.Top, newRight, margin.Bottom);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新子面板位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新批注子面板(PenPalette)的弹出位置,使其水平中心对齐笔按钮。
|
||||
/// </summary>
|
||||
private void UpdatePenPalettePosition()
|
||||
{
|
||||
UpdateSubPanelPosition(Pen_Icon, PenPalette, 193);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新工具面板(BorderTools)的弹出位置,使其水平中心对齐工具按钮。
|
||||
/// </summary>
|
||||
private void UpdateBorderToolsPosition()
|
||||
{
|
||||
UpdateSubPanelPosition(ToolsFloatingBarBtn, BorderTools, 119);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新橡皮擦尺寸面板(EraserSizePanel)的弹出位置,使其水平中心对齐橡皮擦按钮。
|
||||
/// </summary>
|
||||
private void UpdateEraserSizePanelPosition()
|
||||
{
|
||||
UpdateSubPanelPosition(Eraser_Icon, EraserSizePanel, 120);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新形状绘制面板(BorderDrawShape)的弹出位置,使其水平中心对齐形状按钮。
|
||||
/// </summary>
|
||||
private void UpdateBorderDrawShapePosition()
|
||||
{
|
||||
UpdateSubPanelPosition(ShapeDrawFloatingBarBtn, BorderDrawShape, 317);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新手势面板(TwoFingerGestureBorder)的弹出位置,使其水平中心对齐手势按钮。
|
||||
/// </summary>
|
||||
private void UpdateTwoFingerGestureBorderPosition()
|
||||
{
|
||||
UpdateSubPanelPosition(EnableTwoFingerGestureBorder, TwoFingerGestureBorder, 119);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏浮动栏高光显示
|
||||
/// </summary>
|
||||
|
||||
@@ -90,6 +90,12 @@ namespace Ink_Canvas
|
||||
/// <param name="e">执行路由事件参数</param>
|
||||
internal void KeyExit(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (currentMode != 0)
|
||||
{
|
||||
ImageBlackboard_MouseUp(lastBorderMouseDownObject, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
|
||||
}
|
||||
|
||||
@@ -196,4 +202,4 @@ namespace Ink_Canvas
|
||||
SymbolIconEmoji_MouseUp(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,19 @@ namespace Ink_Canvas
|
||||
public Bitmap CameraImage;
|
||||
public BitmapSource CameraBitmapSource;
|
||||
public bool AddToWhiteboard;
|
||||
public bool IncludeInk;
|
||||
public BitmapSource InkOverlayBitmapSource;
|
||||
|
||||
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null,
|
||||
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false)
|
||||
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false, bool includeInk = false, BitmapSource inkOverlayBitmapSource = null)
|
||||
{
|
||||
Area = area;
|
||||
Path = path;
|
||||
CameraImage = cameraImage;
|
||||
CameraBitmapSource = cameraBitmapSource;
|
||||
AddToWhiteboard = addToWhiteboard;
|
||||
IncludeInk = includeInk;
|
||||
InkOverlayBitmapSource = inkOverlayBitmapSource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +64,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource();
|
||||
|
||||
// 隐藏主窗口以避免截图包含窗口本身
|
||||
var originalVisibility = Visibility;
|
||||
Visibility = Visibility.Hidden;
|
||||
@@ -68,7 +74,7 @@ namespace Ink_Canvas
|
||||
await Task.Delay(200);
|
||||
|
||||
// 启动区域选择截图
|
||||
var screenshotResult = await ShowScreenshotSelector();
|
||||
var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview);
|
||||
|
||||
// 恢复窗口显示
|
||||
Visibility = originalVisibility;
|
||||
@@ -104,11 +110,33 @@ namespace Ink_Canvas
|
||||
|
||||
try
|
||||
{
|
||||
if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
|
||||
{
|
||||
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
|
||||
if (withInkBitmap != null && withInkBitmap != finalBitmap)
|
||||
{
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
finalBitmap = withInkBitmap;
|
||||
needDisposeFinalBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有路径信息,应用形状遮罩
|
||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
if (maskedBitmap != null && maskedBitmap != finalBitmap)
|
||||
{
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
finalBitmap = maskedBitmap;
|
||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||
}
|
||||
}
|
||||
|
||||
// 将截图转换为WPF Image并插入到画布
|
||||
@@ -190,16 +218,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示截图区域选择器
|
||||
/// 显示截图区域选择器并返回用户的截图结果(区域截图或摄像头截图)。
|
||||
/// </summary>
|
||||
/// <returns>截图结果,包含区域、路径和摄像头截图信息</returns>
|
||||
/// <param name="inkOverlayPreview">当用户选择包含墨迹的区域截图时,用于作为墨迹叠加的预览 <see cref="BitmapSource"/>;可为 <c>null</c>。</param>
|
||||
/// <returns>若用户确认截图则返回 <see cref="ScreenshotResult"/>,否则返回 <c>null</c>。返回的结果可能为摄像头截图或区域截图,摄像头截图会包含 <see cref="ScreenshotResult.CameraBitmapSource"/> 或 <see cref="ScreenshotResult.CameraImage"/>,区域截图会包含有效的区域与路径。</returns>
|
||||
/// <remarks>
|
||||
/// 该方法会:
|
||||
/// 1. 显示截图选择器窗口
|
||||
/// 2. 获取用户选择的区域或摄像头截图
|
||||
/// 3. 返回截图结果
|
||||
/// 1. 在 UI 线程(通过 <see cref="Application.Current.Dispatcher"/>)上显示截图选择器窗口 <see cref="ScreenshotSelectorWindow"/>;
|
||||
/// 2. 获取用户选择的区域截图或摄像头截图;
|
||||
/// 3. 根据用户选择构建并返回 <see cref="ScreenshotResult"/>;
|
||||
/// 4. 若用户取消对话框或未确认截图,返回 <c>null</c>;
|
||||
/// 5. 方法内部捕获异常并记录日志(不会向调用方抛出异常),如需外部处理请调整实现以重新抛出或传回错误信息。
|
||||
/// </remarks>
|
||||
private async Task<ScreenshotResult?> ShowScreenshotSelector()
|
||||
private async Task<ScreenshotResult?> ShowScreenshotSelector(BitmapSource inkOverlayPreview = null)
|
||||
{
|
||||
ScreenshotResult? result = null;
|
||||
|
||||
@@ -207,7 +238,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var selectorWindow = new ScreenshotSelectorWindow();
|
||||
var selectorWindow = new ScreenshotSelectorWindow(inkOverlayPreview);
|
||||
if (selectorWindow.ShowDialog() == true)
|
||||
{
|
||||
// 检查是否是摄像头截图
|
||||
@@ -218,7 +249,9 @@ namespace Ink_Canvas
|
||||
null, // 摄像头截图不需要路径
|
||||
null, // 不再使用Bitmap
|
||||
selectorWindow.CameraBitmapSource, // 摄像头BitmapSource
|
||||
selectorWindow.ShouldAddToWhiteboard
|
||||
selectorWindow.ShouldAddToWhiteboard,
|
||||
false,
|
||||
null
|
||||
);
|
||||
}
|
||||
else if (selectorWindow.CameraImage != null)
|
||||
@@ -228,7 +261,9 @@ namespace Ink_Canvas
|
||||
null, // 摄像头截图不需要路径
|
||||
selectorWindow.CameraImage, // 摄像头图像
|
||||
null,
|
||||
selectorWindow.ShouldAddToWhiteboard
|
||||
selectorWindow.ShouldAddToWhiteboard,
|
||||
false,
|
||||
null
|
||||
);
|
||||
}
|
||||
else
|
||||
@@ -238,7 +273,9 @@ namespace Ink_Canvas
|
||||
selectorWindow.SelectedPath,
|
||||
null,
|
||||
null,
|
||||
selectorWindow.ShouldAddToWhiteboard
|
||||
selectorWindow.ShouldAddToWhiteboard,
|
||||
selectorWindow.IncludeInkInScreenshot,
|
||||
selectorWindow.IncludeInkInScreenshot ? inkOverlayPreview : null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -304,6 +341,142 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapSource CreateInkOverlayPreviewBitmapSource()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (inkCanvas == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((inkCanvas.Strokes?.Count ?? 0) == 0 && inkCanvas.Children.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inkCanvas.ActualWidth <= 0 || inkCanvas.ActualHeight <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
var source = PresentationSource.FromVisual(inkCanvas);
|
||||
var transformToDevice = source?.CompositionTarget?.TransformToDevice ?? System.Windows.Media.Matrix.Identity;
|
||||
|
||||
// PointToScreen 返回WPF坐标(DIP),统一转换为设备像素后再与 VirtualScreen 对齐
|
||||
var inkTopLeftDip = inkCanvas.PointToScreen(new Point(0, 0));
|
||||
var inkTopLeftPx = transformToDevice.Transform(inkTopLeftDip);
|
||||
var offsetX = inkTopLeftPx.X - virtualScreen.Left;
|
||||
var offsetY = inkTopLeftPx.Y - virtualScreen.Top;
|
||||
var widthPx = inkCanvas.ActualWidth * transformToDevice.M11;
|
||||
var heightPx = inkCanvas.ActualHeight * transformToDevice.M22;
|
||||
|
||||
var drawingVisual = new DrawingVisual();
|
||||
using (var dc = drawingVisual.RenderOpen())
|
||||
{
|
||||
// 使用完整 InkCanvas 视觉树,确保包含图片等子元素
|
||||
var visualBrush = new VisualBrush(inkCanvas)
|
||||
{
|
||||
Stretch = Stretch.Fill
|
||||
};
|
||||
dc.DrawRectangle(visualBrush, null, new Rect(offsetX, offsetY, widthPx, heightPx));
|
||||
}
|
||||
|
||||
var rtb = new RenderTargetBitmap(
|
||||
Math.Max(1, virtualScreen.Width),
|
||||
Math.Max(1, virtualScreen.Height),
|
||||
96,
|
||||
96,
|
||||
PixelFormats.Pbgra32);
|
||||
rtb.Render(drawingVisual);
|
||||
rtb.Freeze();
|
||||
return rtb;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建截图墨迹预览失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap OverlayInkOnCapturedBitmap(Bitmap capturedBitmap, Rectangle captureArea, BitmapSource inkOverlayBitmapSource)
|
||||
{
|
||||
if (capturedBitmap == null || inkOverlayBitmapSource == null)
|
||||
{
|
||||
return capturedBitmap;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
var sourceRect = new Rectangle(
|
||||
captureArea.X - virtualScreen.X,
|
||||
captureArea.Y - virtualScreen.Y,
|
||||
captureArea.Width,
|
||||
captureArea.Height);
|
||||
|
||||
sourceRect.Intersect(new Rectangle(0, 0, inkOverlayBitmapSource.PixelWidth, inkOverlayBitmapSource.PixelHeight));
|
||||
if (sourceRect.Width <= 0 || sourceRect.Height <= 0)
|
||||
{
|
||||
return capturedBitmap;
|
||||
}
|
||||
|
||||
using (var inkOverlayBitmap = ConvertBitmapSourceToBitmap(inkOverlayBitmapSource))
|
||||
{
|
||||
if (inkOverlayBitmap == null)
|
||||
{
|
||||
return capturedBitmap;
|
||||
}
|
||||
|
||||
Bitmap resultBitmap = null;
|
||||
try
|
||||
{
|
||||
resultBitmap = new Bitmap(capturedBitmap.Width, capturedBitmap.Height, PixelFormat.Format32bppArgb);
|
||||
using (var g = Graphics.FromImage(resultBitmap))
|
||||
{
|
||||
g.DrawImage(capturedBitmap, 0, 0, capturedBitmap.Width, capturedBitmap.Height);
|
||||
|
||||
var targetRect = new Rectangle(0, 0, Math.Min(sourceRect.Width, capturedBitmap.Width), Math.Min(sourceRect.Height, capturedBitmap.Height));
|
||||
g.DrawImage(inkOverlayBitmap, targetRect, sourceRect, GraphicsUnit.Pixel);
|
||||
}
|
||||
|
||||
return resultBitmap;
|
||||
}
|
||||
catch
|
||||
{
|
||||
resultBitmap?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"叠加截图墨迹失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return capturedBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap ConvertBitmapSourceToBitmap(BitmapSource bitmapSource)
|
||||
{
|
||||
if (bitmapSource == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
|
||||
encoder.Save(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
using (var tempBitmap = new Bitmap(memoryStream))
|
||||
{
|
||||
return new Bitmap(tempBitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将截图插入到画布
|
||||
/// </summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Office.Core;
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -167,6 +168,18 @@ namespace Ink_Canvas
|
||||
/// 断开连接后退出PPT模式的延迟时间(毫秒),即连接断开后多长时间才退出PPT模式。
|
||||
/// </summary>
|
||||
private const int ExitPPTModeAfterDisconnectDelayMs = 1200;
|
||||
|
||||
/// <summary>
|
||||
/// 仅PPT模式下周期性探测放映界面(COM 失效时依赖 Win32),间隔不宜过小以免多余开销。
|
||||
/// </summary>
|
||||
private DispatcherTimer _pptOnlyVisibilityProbeTimer;
|
||||
|
||||
private const int PptOnlyVisibilityProbeIntervalMs = 800;
|
||||
|
||||
/// <summary>
|
||||
/// PowerPoint 全屏放映顶层窗口类名(与编辑态 PPTFrameClass 区分)。
|
||||
/// </summary>
|
||||
private const string PowerPointSlideShowWindowClassName = "screenClass";
|
||||
#endregion
|
||||
|
||||
#region PPT Managers
|
||||
@@ -235,7 +248,7 @@ namespace Ink_Canvas
|
||||
// 根据设置选择 COM / ROT 架构
|
||||
if (Settings.PowerPointSettings.UseRotPptLink)
|
||||
{
|
||||
_pptManager = new ROTPPTManager();
|
||||
_pptManager = new ROTPPTLinkManager();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -638,6 +651,8 @@ namespace Ink_Canvas
|
||||
ClosePowerPointApplication();
|
||||
ClearStaticInteropState();
|
||||
|
||||
StopPptOnlyVisibilityProbeTimer();
|
||||
|
||||
LogHelper.WriteLogToFile("PPT管理器已释放", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -700,6 +715,104 @@ namespace Ink_Canvas
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 仅PPT模式可见性(COM + Win32 兜底)
|
||||
|
||||
/// <summary>
|
||||
/// 在启用「仅PPT模式」时启动轻量探测,COM 事件延迟或失效时仍可根据全屏放映窗口显示主窗口。
|
||||
/// </summary>
|
||||
internal void EnsurePptOnlyVisibilityProbeTimer()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Settings.ModeSettings.IsPPTOnlyMode)
|
||||
{
|
||||
StopPptOnlyVisibilityProbeTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pptOnlyVisibilityProbeTimer == null)
|
||||
{
|
||||
_pptOnlyVisibilityProbeTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(PptOnlyVisibilityProbeIntervalMs)
|
||||
};
|
||||
_pptOnlyVisibilityProbeTimer.Tick += (_, __) => CheckMainWindowVisibility();
|
||||
}
|
||||
|
||||
if (!_pptOnlyVisibilityProbeTimer.IsEnabled)
|
||||
_pptOnlyVisibilityProbeTimer.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"仅PPT可见性探测计时器启动失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
internal void StopPptOnlyVisibilityProbeTimer()
|
||||
{
|
||||
try
|
||||
{
|
||||
_pptOnlyVisibilityProbeTimer?.Stop();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测是否存在 PowerPoint 全屏放映顶层窗口(类名 screenClass,进程 powerpnt),用于 COM 不可用时的兜底。
|
||||
/// </summary>
|
||||
internal bool IsPowerPointSlideshowSurfacePresentWin32()
|
||||
{
|
||||
if (!Settings.ModeSettings.IsPPTOnlyMode)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
bool found = false;
|
||||
EnumWindows((hWnd, _) =>
|
||||
{
|
||||
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd))
|
||||
return true;
|
||||
|
||||
var cls = new StringBuilder(256);
|
||||
if (GetClassName(hWnd, cls, cls.Capacity) == 0)
|
||||
return true;
|
||||
|
||||
if (!string.Equals(cls.ToString(), PowerPointSlideShowWindowClassName, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
GetWindowThreadProcessId(hWnd, out uint pid);
|
||||
using (var proc = Process.GetProcessById((int)pid))
|
||||
{
|
||||
var name = proc.ProcessName;
|
||||
if (string.Equals(name, "POWERPNT", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
|
||||
return found;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Win32 检测 PPT 放映窗口失败: {ex.Message}", LogHelper.LogType.Trace);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region New PPT Event Handlers
|
||||
/// <summary>
|
||||
/// 处理 PowerPoint 连接状态的变更:更新界面连接/放映状态,并在断开时启动一个短延迟以安全退出 PPT 模式。
|
||||
@@ -731,6 +844,8 @@ namespace Ink_Canvas
|
||||
_ = HandleManualSlideShowEnd();
|
||||
if (Settings.PowerPointSettings.UseRotPptLink)
|
||||
_pptManager?.ReloadConnection();
|
||||
|
||||
CheckMainWindowVisibility();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1114,6 +1229,9 @@ namespace Ink_Canvas
|
||||
|
||||
// 加载当前页墨迹
|
||||
LoadCurrentSlideInk(currentSlide);
|
||||
|
||||
// 仅PPT模式:放映开始立即同步主窗口可见性(勿仅依赖 SlideShowStateChanged 定时器)
|
||||
CheckMainWindowVisibility();
|
||||
});
|
||||
|
||||
if (!isFloatingBarFolded)
|
||||
@@ -1186,7 +1304,7 @@ namespace Ink_Canvas
|
||||
|
||||
int prev = _previousSlideID;
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
inkCanvas.Strokes.Save(ms);
|
||||
@@ -1228,7 +1346,7 @@ namespace Ink_Canvas
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted || t.Result == null) return;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (_currentSlideShowPosition != loadingPage) return;
|
||||
inkCanvas.Strokes.Add(t.Result);
|
||||
@@ -1399,6 +1517,8 @@ namespace Ink_Canvas
|
||||
currentMode = 0;
|
||||
}
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
|
||||
ClearStrokes(true);
|
||||
// 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹
|
||||
// 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
|
||||
@@ -1435,6 +1555,8 @@ namespace Ink_Canvas
|
||||
|
||||
UpdateCurrentToolMode("cursor");
|
||||
SetFloatingBarHighlightPosition("cursor");
|
||||
|
||||
CheckMainWindowVisibility();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -2291,6 +2413,7 @@ namespace Ink_Canvas
|
||||
_pptUIManager?.UpdateSlideShowStatus(false);
|
||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||
LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
|
||||
CheckMainWindowVisibility();
|
||||
});
|
||||
|
||||
// 手动处理自动收纳,因为OnPPTSlideShowEnd事件可能未触发
|
||||
@@ -2323,6 +2446,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
_pptUIManager?.UpdateSlideShowStatus(false);
|
||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||
CheckMainWindowVisibility();
|
||||
});
|
||||
|
||||
// 异常情况下也手动处理自动收纳
|
||||
@@ -2594,4 +2718,4 @@ namespace Ink_Canvas
|
||||
BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
@@ -16,6 +17,7 @@ using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Color = System.Drawing.Color;
|
||||
@@ -28,16 +30,58 @@ namespace Ink_Canvas
|
||||
// 1. 定义元素信息结构
|
||||
public class CanvasElementInfo
|
||||
{
|
||||
public string Type { get; set; } // "Image"
|
||||
public string Type { get; set; } // "Image" | "Pdf"
|
||||
public string SourcePath { get; set; }
|
||||
public double Left { get; set; }
|
||||
public double Top { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public string Stretch { get; set; } = "Fill"; // 默认为Fill
|
||||
/// <summary>PDF 当前页(从 0 开始),仅 Type == Pdf 时有效。</summary>
|
||||
public int? PdfCurrentPage { get; set; }
|
||||
/// <summary>保存时的 PDF 总页数,用于校验;仅 Type == Pdf 时有效。</summary>
|
||||
public int? PdfPageCount { get; set; }
|
||||
}
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>收集画布上图片与 PDF 的元数据,写入 .elements.json(与墨迹文件同路径)。</summary>
|
||||
private void CollectCanvasElementsMetadata(List<CanvasElementInfo> elementInfos)
|
||||
{
|
||||
if (elementInfos == null || inkCanvas == null) return;
|
||||
|
||||
foreach (var child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image img && img.Source is BitmapImage bmp)
|
||||
{
|
||||
elementInfos.Add(new CanvasElementInfo
|
||||
{
|
||||
Type = "Image",
|
||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
||||
Left = InkCanvas.GetLeft(img),
|
||||
Top = InkCanvas.GetTop(img),
|
||||
Width = img.Width,
|
||||
Height = img.Height,
|
||||
Stretch = img.Stretch.ToString()
|
||||
});
|
||||
}
|
||||
else if (child is PdfEmbeddedView pdf && !string.IsNullOrEmpty(pdf.PdfPath))
|
||||
{
|
||||
elementInfos.Add(new CanvasElementInfo
|
||||
{
|
||||
Type = "Pdf",
|
||||
SourcePath = pdf.PdfPath,
|
||||
Left = InkCanvas.GetLeft(pdf),
|
||||
Top = InkCanvas.GetTop(pdf),
|
||||
Width = pdf.Width,
|
||||
Height = pdf.Height,
|
||||
Stretch = "Uniform",
|
||||
PdfCurrentPage = (int)pdf.CurrentPageIndex,
|
||||
PdfPageCount = (int)pdf.PageCount
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存墨迹的鼠标释放事件处理
|
||||
/// </summary>
|
||||
@@ -419,22 +463,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 保存元素信息
|
||||
var elementInfos = new List<CanvasElementInfo>();
|
||||
foreach (var child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image img && img.Source is BitmapImage bmp)
|
||||
{
|
||||
elementInfos.Add(new CanvasElementInfo
|
||||
{
|
||||
Type = "Image",
|
||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
||||
Left = InkCanvas.GetLeft(img),
|
||||
Top = InkCanvas.GetTop(img),
|
||||
Width = img.Width,
|
||||
Height = img.Height,
|
||||
Stretch = img.Stretch.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
CollectCanvasElementsMetadata(elementInfos);
|
||||
string elementsPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".elements.json") : Path.ChangeExtension(savePathWithName, ".elements.json");
|
||||
File.WriteAllText(elementsPath, JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
}
|
||||
@@ -485,22 +514,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 同时保存元素信息
|
||||
var elementInfos = new List<CanvasElementInfo>();
|
||||
foreach (var child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image img && img.Source is BitmapImage bmp)
|
||||
{
|
||||
elementInfos.Add(new CanvasElementInfo
|
||||
{
|
||||
Type = "Image",
|
||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
||||
Left = InkCanvas.GetLeft(img),
|
||||
Top = InkCanvas.GetTop(img),
|
||||
Width = img.Width,
|
||||
Height = img.Height,
|
||||
Stretch = img.Stretch.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
CollectCanvasElementsMetadata(elementInfos);
|
||||
File.WriteAllText(Path.ChangeExtension(xmlPath, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
|
||||
// 异步上传到Dlass
|
||||
@@ -1266,6 +1280,10 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetTop(img, info.Top);
|
||||
inkCanvas.Children.Add(img);
|
||||
}
|
||||
else if (string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => { _ = RestorePdfFromElementInfoAsync(info); }), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1416,6 +1434,10 @@ namespace Ink_Canvas
|
||||
InkCanvas.SetTop(img, info.Top);
|
||||
inkCanvas.Children.Add(img);
|
||||
}
|
||||
else if (string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => { _ = RestorePdfFromElementInfoAsync(info); }), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,10 +162,11 @@ namespace Ink_Canvas
|
||||
var originalVisibility = Visibility;
|
||||
try
|
||||
{
|
||||
var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource();
|
||||
Visibility = Visibility.Hidden;
|
||||
await Task.Delay(200);
|
||||
|
||||
var screenshotResult = await ShowScreenshotSelector();
|
||||
var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview);
|
||||
|
||||
if (!screenshotResult.HasValue)
|
||||
{
|
||||
@@ -202,10 +203,32 @@ namespace Ink_Canvas
|
||||
|
||||
try
|
||||
{
|
||||
if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
|
||||
{
|
||||
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
|
||||
if (withInkBitmap != null && withInkBitmap != finalBitmap)
|
||||
{
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
finalBitmap = withInkBitmap;
|
||||
needDisposeFinalBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
needDisposeFinalBitmap = true;
|
||||
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
if (maskedBitmap != null && maskedBitmap != finalBitmap)
|
||||
{
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
finalBitmap = maskedBitmap;
|
||||
needDisposeFinalBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(desktopPath);
|
||||
@@ -275,10 +298,32 @@ namespace Ink_Canvas
|
||||
|
||||
try
|
||||
{
|
||||
if (screenshotResult.IncludeInk && screenshotResult.InkOverlayBitmapSource != null)
|
||||
{
|
||||
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Area, screenshotResult.InkOverlayBitmapSource);
|
||||
if (withInkBitmap != null && withInkBitmap != finalBitmap)
|
||||
{
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
finalBitmap = withInkBitmap;
|
||||
needDisposeFinalBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (screenshotResult.Path != null && screenshotResult.Path.Count > 0)
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Path, screenshotResult.Area);
|
||||
needDisposeFinalBitmap = true;
|
||||
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Path, screenshotResult.Area);
|
||||
if (maskedBitmap != null && maskedBitmap != finalBitmap)
|
||||
{
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
finalBitmap = maskedBitmap;
|
||||
needDisposeFinalBitmap = true;
|
||||
}
|
||||
}
|
||||
|
||||
bitmapSourceForClipboard = ConvertBitmapToBitmapSource(finalBitmap);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Controls;
|
||||
using System;
|
||||
@@ -610,7 +611,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 检查是否有图片元素被选中(通过InkCanvas的选中元素)
|
||||
var selectedElements = inkCanvas.GetSelectedElements();
|
||||
bool hasImageElement = selectedElements.Any(element => element is Image);
|
||||
bool hasImageElement = selectedElements.Any(element => element is Image || element is PdfEmbeddedView);
|
||||
|
||||
// 如果有图片元素被选中,不显示选择框
|
||||
if (hasImageElement)
|
||||
@@ -621,7 +622,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 检查是否有图片元素被选中(通过currentSelectedElement)
|
||||
if (currentSelectedElement != null && currentSelectedElement is Image)
|
||||
if (currentSelectedElement != null && (currentSelectedElement is Image || currentSelectedElement is PdfEmbeddedView))
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
HideSelectionDisplay();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using OSVersionExtension;
|
||||
@@ -38,6 +38,8 @@ namespace Ink_Canvas
|
||||
/// 内部标记:是否正在内部更改更新通道
|
||||
/// </summary>
|
||||
private bool _isChangingUpdateChannelInternally;
|
||||
/// <summary>内部标记:是否正在内部更改「更新包架构」(32/64 位 ZIP)</summary>
|
||||
private bool _isChangingUpdatePackageArchInternally;
|
||||
/// <summary>
|
||||
/// 内部标记:是否正在内部更改遥测设置
|
||||
/// </summary>
|
||||
@@ -1207,17 +1209,12 @@ namespace Ink_Canvas
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建API URL,包含选中的分类参数
|
||||
var categories = Settings.Appearance.HitokotoCategories;
|
||||
if (categories == null || categories.Count == 0)
|
||||
{
|
||||
// 如果没有选中任何分类,默认全选
|
||||
categories = new List<string> { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" };
|
||||
Settings.Appearance.HitokotoCategories = categories;
|
||||
}
|
||||
var cats = Settings.Appearance.HitokotoCategories;
|
||||
if (cats == null || cats.Count == 0)
|
||||
cats = new List<string> { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" };
|
||||
|
||||
var urlBuilder = new StringBuilder("https://v1.hitokoto.cn/?encode=text");
|
||||
foreach (var category in categories)
|
||||
foreach (var category in cats)
|
||||
{
|
||||
urlBuilder.Append($"&c={category}");
|
||||
}
|
||||
@@ -1264,8 +1261,13 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private async void ComboBoxChickenSoupSource_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppressChickenSoupSourceSelectionChanged) return;
|
||||
if (!isLoaded) return;
|
||||
Settings.Appearance.ChickenSoupSource = ComboBoxChickenSoupSource.SelectedIndex;
|
||||
int idx = ComboBoxChickenSoupSource.SelectedIndex;
|
||||
if (idx < 0) return;
|
||||
if (Settings.Appearance.ChickenSoupSource == idx) return;
|
||||
|
||||
Settings.Appearance.ChickenSoupSource = idx;
|
||||
|
||||
if (BtnHitokotoCustomize != null)
|
||||
{
|
||||
@@ -1315,6 +1317,9 @@ namespace Ink_Canvas
|
||||
// 存储各个分类的复选框
|
||||
var categoryCheckBoxes = new Dictionary<string, CheckBox>();
|
||||
|
||||
var savedHitokoto = Settings.Appearance.HitokotoCategories;
|
||||
bool implicitAllCategories = savedHitokoto == null || savedHitokoto.Count == 0;
|
||||
|
||||
// 创建分类复选框
|
||||
foreach (var category in categories)
|
||||
{
|
||||
@@ -1324,7 +1329,7 @@ namespace Ink_Canvas
|
||||
Tag = category.Key,
|
||||
FontSize = 13,
|
||||
FontFamily = new FontFamily("Microsoft YaHei UI"),
|
||||
IsChecked = Settings.Appearance.HitokotoCategories.Contains(category.Key),
|
||||
IsChecked = implicitAllCategories || savedHitokoto.Contains(category.Key),
|
||||
Margin = new Thickness(0, 0, 0, 8)
|
||||
};
|
||||
categoryCheckBoxes[category.Key] = checkBox;
|
||||
@@ -1333,7 +1338,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 全选复选框逻辑
|
||||
bool isUpdatingSelectAll = false;
|
||||
selectAllCheckBox.IsChecked = Settings.Appearance.HitokotoCategories.Count == categories.Count;
|
||||
selectAllCheckBox.IsChecked = implicitAllCategories || savedHitokoto.Count == categories.Count;
|
||||
|
||||
selectAllCheckBox.Checked += (s, args) =>
|
||||
{
|
||||
@@ -2700,6 +2705,15 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
Settings.Canvas.LaunchSeewoVideoShowcaseForWhiteboardBooth =
|
||||
ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchAutoStraightenLine_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2762,19 +2776,44 @@ namespace Ink_Canvas
|
||||
|
||||
#region Canvas
|
||||
|
||||
/// <summary>笔锋下拉 UI 顺序:0 实时笔锋,1 基于点集,2 基于速率,3 关闭。与存储值 InkStyle:3,0,1,2 对应。</summary>
|
||||
private static int PenStyleUiIndexFromInkStyle(int inkStyle)
|
||||
{
|
||||
switch (inkStyle)
|
||||
{
|
||||
case 3: return 0;
|
||||
case 0: return 1;
|
||||
case 1: return 2;
|
||||
case 2: return 3;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int InkStyleFromPenStyleUiIndex(int uiIndex)
|
||||
{
|
||||
switch (uiIndex)
|
||||
{
|
||||
case 0: return 3;
|
||||
case 1: return 0;
|
||||
case 2: return 1;
|
||||
case 3: return 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void ComboBoxPenStyle_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
int uiIndex = sender == ComboBoxPenStyle
|
||||
? ComboBoxPenStyle.SelectedIndex
|
||||
: BoardComboBoxPenStyle.SelectedIndex;
|
||||
if (uiIndex < 0) return;
|
||||
|
||||
Settings.Canvas.InkStyle = InkStyleFromPenStyleUiIndex(uiIndex);
|
||||
if (sender == ComboBoxPenStyle)
|
||||
{
|
||||
Settings.Canvas.InkStyle = ComboBoxPenStyle.SelectedIndex;
|
||||
BoardComboBoxPenStyle.SelectedIndex = ComboBoxPenStyle.SelectedIndex;
|
||||
}
|
||||
BoardComboBoxPenStyle.SelectedIndex = uiIndex;
|
||||
else
|
||||
{
|
||||
Settings.Canvas.InkStyle = BoardComboBoxPenStyle.SelectedIndex;
|
||||
ComboBoxPenStyle.SelectedIndex = BoardComboBoxPenStyle.SelectedIndex;
|
||||
}
|
||||
ComboBoxPenStyle.SelectedIndex = uiIndex;
|
||||
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
@@ -3558,6 +3597,11 @@ namespace Ink_Canvas
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = true;
|
||||
|
||||
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||
Settings.Canvas.EnablePalmEraser = false;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = false;
|
||||
|
||||
// 恢复到之前的编辑状态
|
||||
inkCanvas.EditingMode = currentEditingMode;
|
||||
drawingShapeMode = currentDrawingShapeMode;
|
||||
@@ -3588,6 +3632,13 @@ namespace Ink_Canvas
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = false;
|
||||
|
||||
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||
{
|
||||
Settings.Canvas.EnablePalmEraser = true;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = true;
|
||||
}
|
||||
|
||||
// 恢复到之前的编辑状态
|
||||
inkCanvas.EditingMode = currentEditingMode;
|
||||
drawingShapeMode = currentDrawingShapeMode;
|
||||
@@ -3832,7 +3883,8 @@ namespace Ink_Canvas
|
||||
Settings.InkToShape.IsInkToShapeTriangle = true;
|
||||
Settings.InkToShape.IsInkToShapeRectangle = true;
|
||||
Settings.InkToShape.IsInkToShapeRounded = true;
|
||||
|
||||
Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify = false;
|
||||
Settings.InkToShape.HandwritingCorrectionFontFamily = "Ink Free,KaiTi,Segoe Script";
|
||||
|
||||
Settings.Startup.IsEnableNibMode = false;
|
||||
Settings.Startup.IsAutoUpdate = true;
|
||||
@@ -3905,6 +3957,25 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ComboBoxShapeRecognitionEngine_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded || ComboBoxShapeRecognitionEngine == null) return;
|
||||
int idx = ComboBoxShapeRecognitionEngine.SelectedIndex;
|
||||
if (idx < 0) idx = 0;
|
||||
if (idx > 2) idx = 2;
|
||||
Settings.InkToShape.ShapeRecognitionEngine = idx;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableWinRtHandwritingStrokeBeautify_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify =
|
||||
ToggleSwitchEnableWinRtHandwritingStrokeBeautify != null &&
|
||||
ToggleSwitchEnableWinRtHandwritingStrokeBeautify.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableInkToShapeNoFakePressureTriangle_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -5136,6 +5207,24 @@ namespace Ink_Canvas
|
||||
HideSubPanels();
|
||||
}
|
||||
|
||||
private void UpdatePackageArchitectureSelector_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
if (_isChangingUpdatePackageArchInternally) return;
|
||||
if (!(sender is RadioButton radioButton) || radioButton.Tag == null) return;
|
||||
|
||||
var newArch = string.Equals(radioButton.Tag.ToString(), "X64", StringComparison.OrdinalIgnoreCase)
|
||||
? UpdatePackageArchitecture.X64
|
||||
: UpdatePackageArchitecture.X86;
|
||||
|
||||
if (Settings.Startup.UpdatePackageArchitecture == newArch)
|
||||
return;
|
||||
|
||||
Settings.Startup.UpdatePackageArchitecture = newArch;
|
||||
SaveSettingsToFile();
|
||||
LogHelper.WriteLogToFile($"Settings | Update package architecture: {newArch}");
|
||||
}
|
||||
|
||||
private async void UpdateChannelSelector_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -301,6 +301,29 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化更新包架构
|
||||
if (UpdatePackageArchitectureSelector != null)
|
||||
{
|
||||
_isChangingUpdatePackageArchInternally = true;
|
||||
try
|
||||
{
|
||||
string wantTag = Settings.Startup.UpdatePackageArchitecture == UpdatePackageArchitecture.X64 ? "X64" : "X86";
|
||||
foreach (var item in UpdatePackageArchitectureSelector.Items)
|
||||
{
|
||||
if (item is RadioButton rb && rb.Tag != null &&
|
||||
string.Equals(rb.Tag.ToString(), wantTag, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
rb.IsChecked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isChangingUpdatePackageArchInternally = false;
|
||||
}
|
||||
}
|
||||
|
||||
AutoUpdateTimePeriodBlock.Visibility = Settings.Startup.IsAutoUpdateWithSilence
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
@@ -417,7 +440,17 @@ namespace Ink_Canvas
|
||||
// 设置主题下拉框
|
||||
ComboBoxTheme.SelectedIndex = Settings.Appearance.Theme;
|
||||
|
||||
ComboBoxChickenSoupSource.SelectedIndex = Settings.Appearance.ChickenSoupSource;
|
||||
_suppressChickenSoupSourceSelectionChanged = true;
|
||||
try
|
||||
{
|
||||
ComboBoxChickenSoupSource.SelectedIndex = Settings.Appearance.ChickenSoupSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Dispatcher.BeginInvoke(
|
||||
(Action)(() => { _suppressChickenSoupSourceSelectionChanged = false; }),
|
||||
DispatcherPriority.ContextIdle);
|
||||
}
|
||||
|
||||
// 初始化自定义按钮的可见性(仅在选择API时显示)
|
||||
if (BtnHitokotoCustomize != null)
|
||||
@@ -427,11 +460,6 @@ namespace Ink_Canvas
|
||||
: Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 初始化HitokotoCategories,如果为空则默认全选
|
||||
if (Settings.Appearance.HitokotoCategories == null || Settings.Appearance.HitokotoCategories.Count == 0)
|
||||
{
|
||||
Settings.Appearance.HitokotoCategories = new List<string> { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" };
|
||||
}
|
||||
|
||||
ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel;
|
||||
|
||||
@@ -824,8 +852,21 @@ namespace Ink_Canvas
|
||||
ToggleSwitchDisablePressure.IsOn = Settings.Canvas.DisablePressure;
|
||||
inkCanvas.DefaultDrawingAttributes.IgnorePressure = Settings.Canvas.DisablePressure;
|
||||
|
||||
ComboBoxPenStyle.SelectedIndex = Settings.Canvas.InkStyle;
|
||||
BoardComboBoxPenStyle.SelectedIndex = Settings.Canvas.InkStyle;
|
||||
ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth.IsOn =
|
||||
Settings.Canvas.LaunchSeewoVideoShowcaseForWhiteboardBooth;
|
||||
|
||||
if (Settings.Canvas.EnableVelocityBrushTip)
|
||||
{
|
||||
Settings.Canvas.InkStyle = 3;
|
||||
Settings.Canvas.EnableVelocityBrushTip = false;
|
||||
}
|
||||
|
||||
if (Settings.Canvas.InkStyle < 0 || Settings.Canvas.InkStyle > 3)
|
||||
Settings.Canvas.InkStyle = 0;
|
||||
|
||||
int penStyleUi = PenStyleUiIndexFromInkStyle(Settings.Canvas.InkStyle);
|
||||
ComboBoxPenStyle.SelectedIndex = penStyleUi;
|
||||
BoardComboBoxPenStyle.SelectedIndex = penStyleUi;
|
||||
|
||||
ComboBoxEraserSize.SelectedIndex = Settings.Canvas.EraserSize;
|
||||
ComboBoxEraserSizeFloatingBar.SelectedIndex = Settings.Canvas.EraserSize;
|
||||
@@ -1020,6 +1061,17 @@ namespace Ink_Canvas
|
||||
{
|
||||
ToggleSwitchEnableInkToShape.IsOn = Settings.InkToShape.IsInkToShapeEnabled;
|
||||
|
||||
if (ComboBoxShapeRecognitionEngine != null)
|
||||
{
|
||||
int eng = Settings.InkToShape.ShapeRecognitionEngine;
|
||||
if (eng < 0 || eng > 2) eng = 0;
|
||||
ComboBoxShapeRecognitionEngine.SelectedIndex = eng;
|
||||
}
|
||||
|
||||
if (ToggleSwitchEnableWinRtHandwritingStrokeBeautify != null)
|
||||
ToggleSwitchEnableWinRtHandwritingStrokeBeautify.IsOn =
|
||||
Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify;
|
||||
|
||||
ToggleSwitchEnableInkToShapeNoFakePressureRectangle.IsOn =
|
||||
Settings.InkToShape.IsInkToShapeNoFakePressureRectangle;
|
||||
|
||||
@@ -1450,7 +1502,7 @@ namespace Ink_Canvas
|
||||
Settings defaultSettings = new Settings();
|
||||
|
||||
// 将默认配置和用户配置都序列化为JObject
|
||||
JObject defaultConfigObj = JObject.FromObject(defaultSettings);
|
||||
JObject defaultConfigObj = JObject.FromObject(defaultSettings); EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj);
|
||||
JObject userConfigObj = JObject.Parse(userConfigJson);
|
||||
|
||||
// 记录是否有清理操作
|
||||
@@ -1491,6 +1543,13 @@ namespace Ink_Canvas
|
||||
/// 7. 删除标记的键
|
||||
/// 8. 设置变更标志
|
||||
/// </remarks>
|
||||
private static void EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(JObject defaultConfigObj)
|
||||
{
|
||||
if (defaultConfigObj == null) return;
|
||||
if (defaultConfigObj["appearance"] is JObject appearance && !appearance.ContainsKey("hitokotoCategories"))
|
||||
appearance["hitokotoCategories"] = JValue.CreateNull();
|
||||
}
|
||||
|
||||
private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges)
|
||||
{
|
||||
if (userObj == null || defaultObj == null)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Common.IconKeys;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -48,6 +49,7 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
HideSubPanels();
|
||||
UpdateBorderDrawShapePosition();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderDrawShape);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderDrawShape);
|
||||
}
|
||||
@@ -113,9 +115,9 @@ namespace Ink_Canvas
|
||||
ToggleSwitchDrawShapeBorderAutoHide.IsOn = !ToggleSwitchDrawShapeBorderAutoHide.IsOn;
|
||||
|
||||
if (ToggleSwitchDrawShapeBorderAutoHide.IsOn)
|
||||
((SymbolIcon)sender).Symbol = Symbol.Pin;
|
||||
((FontIcon)sender).Icon = SegoeFluentIcons.Pin;
|
||||
else
|
||||
((SymbolIcon)sender).Symbol = Symbol.UnPin;
|
||||
((FontIcon)sender).Icon = SegoeFluentIcons.Unpin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2564,7 +2566,7 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void inkCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HandleEraserOperationEnded(); // 橡皮擦自动切换回批注模式:松手后启动/重置计时
|
||||
HandleEraserOperationEnded();
|
||||
inkCanvas.ReleaseMouseCapture();
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
@@ -1147,8 +1146,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 检查更新文件是否已下载
|
||||
string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
string statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{AvailableLatestVersion}Status.txt");
|
||||
string statusFilePath = AutoUpdateHelper.GetUpdateDownloadStatusFilePath(AvailableLatestVersion);
|
||||
|
||||
if (!File.Exists(statusFilePath) || File.ReadAllText(statusFilePath).Trim().ToLower() != "true")
|
||||
{
|
||||
@@ -1441,7 +1439,6 @@ namespace Ink_Canvas
|
||||
if (_eraserAutoSwitchBackTimer != null)
|
||||
{
|
||||
_eraserAutoSwitchBackTimer.Stop();
|
||||
LogHelper.WriteLogToFile("橡皮擦自动切换计时器已停止", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1486,4 +1483,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ namespace Ink_Canvas
|
||||
/// 多点触控延迟时间(毫秒)
|
||||
/// </summary>
|
||||
private const double MULTI_TOUCH_DELAY_MS = 100;
|
||||
private bool isMultiTouchTimerActive;
|
||||
private bool isPalmEraserActive;
|
||||
private bool palmEraserWasEnabledBeforeMultiTouch;
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||
@@ -227,6 +230,12 @@ namespace Ink_Canvas
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = false;
|
||||
|
||||
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||
{
|
||||
Settings.Canvas.EnablePalmEraser = true;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -247,6 +256,11 @@ namespace Ink_Canvas
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = true;
|
||||
|
||||
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||
Settings.Canvas.EnablePalmEraser = false;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,7 +556,25 @@ namespace Ink_Canvas
|
||||
var stylusPointCollection = e.GetStylusPoints(this);
|
||||
foreach (var stylusPoint in stylusPointCollection)
|
||||
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
||||
strokeVisual.Redraw();
|
||||
|
||||
// 实时笔锋:混合度 > 0 时在绘制过程中更新压感并整笔重绘预览;混合为 0 时与普通过程一致用增量 Redraw,避免每点 ForceRedraw 整笔清空(长笔画卡顿)。
|
||||
var committedStroke = strokeVisual.Stroke;
|
||||
if (committedStroke != null
|
||||
&& 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();
|
||||
}
|
||||
else
|
||||
{
|
||||
strokeVisual.Redraw();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
@@ -703,21 +735,13 @@ namespace Ink_Canvas
|
||||
/// <param name="e">触摸事件参数</param>
|
||||
/// <returns>返回触摸边界宽度</returns>
|
||||
/// <remarks>
|
||||
/// 根据触摸事件参数计算触摸边界宽度,包括以下逻辑:
|
||||
/// 1. 获取触摸点的边界
|
||||
/// 2. 如果不是四边红外屏幕,使用边界宽度
|
||||
/// 3. 如果是四边红外屏幕,使用边界宽度和高度的平方根
|
||||
/// 4. 如果是特殊屏幕,乘以触摸倍数
|
||||
/// 5. 返回计算得到的触摸边界宽度
|
||||
/// 手掌擦阈值与特殊屏 <c>TouchMultiplier</c> 在激活逻辑中单独参与计算,此处仅返回几何接触尺寸。
|
||||
/// </remarks>
|
||||
public double GetTouchBoundWidth(TouchEventArgs e)
|
||||
{
|
||||
var args = e.GetTouchPoint(null).Bounds;
|
||||
double value;
|
||||
if (!Settings.Advanced.IsQuadIR) value = args.Width;
|
||||
else value = Math.Sqrt(args.Width * args.Height); //四边红外
|
||||
if (Settings.Advanced.IsSpecialScreen) value *= Settings.Advanced.TouchMultiplier;
|
||||
return value;
|
||||
if (!Settings.Advanced.IsQuadIR) return args.Width;
|
||||
return Math.Sqrt(args.Width * args.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -741,28 +765,146 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
var touchPointForBar = e.GetTouchPoint(this);
|
||||
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
||||
new Rect(0, 0, ViewboxFloatingBar.ActualWidth, ViewboxFloatingBar.ActualHeight));
|
||||
if (floatingBarBounds.Contains(touchPointForBar.Position))
|
||||
return;
|
||||
|
||||
if ((inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint
|
||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke)
|
||||
&& !isPalmEraserActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
|
||||
isTouchDown = true;
|
||||
|
||||
if (dec.Count == 0)
|
||||
{
|
||||
var inkTouchPoint = e.GetTouchPoint(inkCanvas);
|
||||
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||
{
|
||||
if (drawMultiStepShapeCurrentStep == 0)
|
||||
iniP = inkTouchPoint.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
iniP = inkTouchPoint.Position;
|
||||
}
|
||||
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
||||
}
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
|
||||
lastTouchDownTime = DateTime.Now;
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
//设备1个的时候,记录中心点
|
||||
|
||||
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive && drawingShapeMode == 0)
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
double boundWidth = GetTouchBoundWidth(e);
|
||||
|
||||
if ((Settings.Advanced.TouchMultiplier != 0 || !Settings.Advanced.IsSpecialScreen)
|
||||
&& (boundWidth > BoundsWidth))
|
||||
{
|
||||
double thresholdMultiplier;
|
||||
switch (Settings.Canvas.PalmEraserSensitivity)
|
||||
{
|
||||
case 0:
|
||||
thresholdMultiplier = 3.0;
|
||||
break;
|
||||
case 1:
|
||||
thresholdMultiplier = 2.5;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
thresholdMultiplier = 2.0;
|
||||
break;
|
||||
}
|
||||
|
||||
double EraserThresholdValue = Settings.Startup.IsEnableNibMode
|
||||
? Settings.Advanced.NibModeBoundsWidthThresholdValue
|
||||
: Settings.Advanced.FingerModeBoundsWidthThresholdValue;
|
||||
|
||||
if (boundWidth > BoundsWidth * EraserThresholdValue * thresholdMultiplier)
|
||||
{
|
||||
boundWidth *= Settings.Startup.IsEnableNibMode
|
||||
? Settings.Advanced.NibModeBoundsWidthEraserSize
|
||||
: Settings.Advanced.FingerModeBoundsWidthEraserSize;
|
||||
|
||||
if (Settings.Advanced.IsSpecialScreen)
|
||||
boundWidth *= Settings.Advanced.TouchMultiplier;
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
||||
isPalmEraserActive = true;
|
||||
|
||||
EnableEraserOverlay();
|
||||
eraserWidth = boundWidth;
|
||||
UpdateEraserStyle();
|
||||
touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
EraserOverlay_PointerDown(sender);
|
||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||
if (Settings.Canvas.IsShowCursor)
|
||||
{
|
||||
inkCanvas.ForceCursor = false;
|
||||
inkCanvas.UseCustomCursor = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dec.Count == 1)
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
centerPoint = touchPoint.Position;
|
||||
|
||||
//记录第一根手指点击时的 StrokeCollection
|
||||
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
||||
}
|
||||
//设备两个及两个以上,将画笔功能关闭
|
||||
|
||||
if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture)
|
||||
{
|
||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None ||
|
||||
inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
||||
var timeSinceLastTouch = (DateTime.Now - lastTouchDownTime).TotalMilliseconds;
|
||||
if (timeSinceLastTouch < MULTI_TOUCH_DELAY_MS && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
if (!isMultiTouchTimerActive)
|
||||
{
|
||||
isMultiTouchTimerActive = true;
|
||||
var remainingTime = MULTI_TOUCH_DELAY_MS - timeSinceLastTouch;
|
||||
Task.Delay((int)remainingTime).ContinueWith(_ =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (dec.Count > 1 && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
isMultiTouchTimerActive = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lastInkCanvasEditingMode = inkCanvas.EditingMode;
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +918,11 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -802,32 +949,18 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
inkCanvas.ReleaseAllTouchCaptures();
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
//手势完成后切回之前的状态
|
||||
if (dec.Count > 1)
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
dec.Remove(e.TouchDevice.Id);
|
||||
|
||||
if (dec.Count == 0)
|
||||
{
|
||||
isSingleFingerDragMode = false;
|
||||
isWaitUntilNextTouchDown = false;
|
||||
if (drawingShapeMode == 0
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.None)
|
||||
{
|
||||
if (lastInkCanvasEditingMode != InkCanvasEditingMode.None)
|
||||
{
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dec.Count <= 1)
|
||||
isMultiTouchTimerActive = false;
|
||||
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
@@ -839,12 +972,10 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (drawMultiStepShapeCurrentStep == 0)
|
||||
{
|
||||
// 第一笔完成,进入第二笔
|
||||
drawMultiStepShapeCurrentStep = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 第二笔完成,完成绘制
|
||||
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = MouseLeftButtonUpEvent,
|
||||
@@ -864,6 +995,36 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
if (drawingShapeMode == 0)
|
||||
{
|
||||
if (dec.Count > 1)
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||
{
|
||||
if (lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint)
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
}
|
||||
}
|
||||
else if (dec.Count == 0)
|
||||
{
|
||||
isSingleFingerDragMode = false;
|
||||
isWaitUntilNextTouchDown = false;
|
||||
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None &&
|
||||
lastInkCanvasEditingMode != InkCanvasEditingMode.None &&
|
||||
lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
}
|
||||
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
isPalmEraserActive = false;
|
||||
DisableEraserOverlay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inkCanvas.Opacity = 1;
|
||||
|
||||
if (dec.Count == 0)
|
||||
@@ -961,6 +1122,9 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
return;
|
||||
|
||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
|
||||
bool hasMultipleManipulators = e.Manipulators.Count() >= 2;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Controls;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Threading;
|
||||
using Application = System.Windows.Application;
|
||||
using ContextMenu = System.Windows.Controls.ContextMenu;
|
||||
using MenuItem = System.Windows.Controls.MenuItem;
|
||||
@@ -15,6 +18,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private const int TrayTemporaryShowMinutes = 2;
|
||||
|
||||
private DispatcherTimer _trayTemporaryShowTimer;
|
||||
|
||||
private bool _trayTemporaryShowRestoreHideChecked;
|
||||
|
||||
/// <summary>
|
||||
/// 系统托盘菜单打开时的事件处理方法
|
||||
@@ -37,7 +45,9 @@ namespace Ink_Canvas
|
||||
var FoldFloatingBarTrayIconMenuItemIconEyeOn = (Image)((Grid)((MenuItem)s.Items[s.Items.Count - 5]).Icon).Children[1];
|
||||
var FoldFloatingBarTrayIconMenuItemHeaderText = (TextBlock)((SimpleStackPanel)((MenuItem)s.Items[s.Items.Count - 5]).Header).Children[0];
|
||||
var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4];
|
||||
var HideICCMainWindowTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 9];
|
||||
var HideICCMainWindowTrayIconMenuItem = s.Items.OfType<MenuItem>()
|
||||
.FirstOrDefault(mi => mi.Name == "HideICCMainWindowTrayIconMenuItem");
|
||||
if (HideICCMainWindowTrayIconMenuItem == null) return;
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
{
|
||||
@@ -84,10 +94,156 @@ namespace Ink_Canvas
|
||||
/// 1. 获取主窗口实例
|
||||
/// 2. 如果主窗口已加载,且在无焦点模式下启用了始终置顶,则恢复主窗口的置顶状态
|
||||
/// </remarks>
|
||||
private bool EnsureMainWindowReadyForSettings(MainWindow mainWin)
|
||||
{
|
||||
if (mainWin?.IsLoaded != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var trayMenu = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||
var hideMainWindowMenuItem = trayMenu?.Items.OfType<MenuItem>()
|
||||
.FirstOrDefault(mi => mi.Name == "HideICCMainWindowTrayIconMenuItem");
|
||||
|
||||
if (hideMainWindowMenuItem != null && hideMainWindowMenuItem.IsChecked)
|
||||
{
|
||||
hideMainWindowMenuItem.IsChecked = false;
|
||||
}
|
||||
else if (!mainWin.IsVisible)
|
||||
{
|
||||
mainWin.Show();
|
||||
}
|
||||
|
||||
if (mainWin.WindowState == WindowState.Minimized)
|
||||
{
|
||||
mainWin.WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
mainWin.Activate();
|
||||
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)
|
||||
{
|
||||
var mainWin = Current.MainWindow as MainWindow;
|
||||
if (mainWin?.IsLoaded != true)
|
||||
return;
|
||||
|
||||
MenuItem hideItem = null;
|
||||
try
|
||||
{
|
||||
var trayMenu = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||
hideItem = trayMenu?.Items.OfType<MenuItem>()
|
||||
.FirstOrDefault(mi => mi.Name == "HideICCMainWindowTrayIconMenuItem");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_trayTemporaryShowRestoreHideChecked = hideItem?.IsChecked == true;
|
||||
|
||||
EnsureMainWindowReadyForSettings(mainWin);
|
||||
|
||||
global::Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = DateTime.UtcNow.AddMinutes(TrayTemporaryShowMinutes);
|
||||
|
||||
_trayTemporaryShowTimer?.Stop();
|
||||
if (_trayTemporaryShowTimer == null)
|
||||
{
|
||||
_trayTemporaryShowTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMinutes(TrayTemporaryShowMinutes)
|
||||
};
|
||||
_trayTemporaryShowTimer.Tick += TrayTemporaryShowTimer_OnTick;
|
||||
}
|
||||
else
|
||||
{
|
||||
_trayTemporaryShowTimer.Interval = TimeSpan.FromMinutes(TrayTemporaryShowMinutes);
|
||||
}
|
||||
|
||||
_trayTemporaryShowTimer.Start();
|
||||
}
|
||||
|
||||
private void TrayTemporaryShowTimer_OnTick(object sender, EventArgs e)
|
||||
{
|
||||
_trayTemporaryShowTimer?.Stop();
|
||||
global::Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = null;
|
||||
|
||||
var mainWin = Current.MainWindow as MainWindow;
|
||||
if (mainWin?.IsLoaded != true)
|
||||
{
|
||||
_trayTemporaryShowRestoreHideChecked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_trayTemporaryShowRestoreHideChecked)
|
||||
{
|
||||
var trayMenu = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||
var hideItem = trayMenu?.Items.OfType<MenuItem>()
|
||||
.FirstOrDefault(mi => mi.Name == "HideICCMainWindowTrayIconMenuItem");
|
||||
if (hideItem != null)
|
||||
hideItem.IsChecked = true;
|
||||
else
|
||||
mainWin.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
mainWin.CheckMainWindowVisibility();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"托盘临时显示计时结束处理失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_trayTemporaryShowRestoreHideChecked = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSettingsTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = Current.MainWindow as MainWindow;
|
||||
if (!EnsureMainWindowReadyForSettings(mainWin))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsLegacySettingsVisible(mainWin))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var method = typeof(MainWindow).GetMethod("BtnSettings_Click", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
method?.Invoke(mainWin, new object[] { null, null });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Open settings from tray failed: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void SysTrayMenu_Closed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
// 菜单关闭后,恢复主窗口的置顶状态
|
||||
if (Ink_Canvas.MainWindow.Settings.Advanced.IsAlwaysOnTop && Ink_Canvas.MainWindow.Settings.Advanced.IsNoFocusMode)
|
||||
@@ -112,7 +268,7 @@ namespace Ink_Canvas
|
||||
private void CloseAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
IsAppExitByUser = true;
|
||||
mainWin.BtnExit_Click(null, null);
|
||||
@@ -136,7 +292,7 @@ namespace Ink_Canvas
|
||||
private void RestartAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
IsAppExitByUser = true;
|
||||
|
||||
@@ -176,7 +332,7 @@ namespace Ink_Canvas
|
||||
private void ForceFullScreenTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
Ink_Canvas.MainWindow.MoveWindow(new WindowInteropHelper(mainWin).Handle, 0, 0,
|
||||
Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, true);
|
||||
@@ -199,7 +355,7 @@ namespace Ink_Canvas
|
||||
private void FoldFloatingBarTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
if (mainWin.isFloatingBarFolded) mainWin.UnFoldFloatingBar_MouseUp(new object(), null);
|
||||
else mainWin.FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
@@ -221,7 +377,7 @@ namespace Ink_Canvas
|
||||
private void ResetFloatingBarPositionTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
var isInPPTPresentationMode = false;
|
||||
Dispatcher.Invoke(() =>
|
||||
@@ -255,21 +411,28 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void HideICCMainWindowTrayIconMenuItem_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_trayTemporaryShowTimer?.Stop();
|
||||
global::Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = null;
|
||||
_trayTemporaryShowRestoreHideChecked = false;
|
||||
|
||||
var mi = (MenuItem)sender;
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
mainWin.Hide();
|
||||
var s = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||
var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4];
|
||||
var FoldFloatingBarTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 5];
|
||||
var ForceFullScreenTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 6];
|
||||
ResetFloatingBarPositionTrayIconMenuItem.IsEnabled = false;
|
||||
FoldFloatingBarTrayIconMenuItem.IsEnabled = false;
|
||||
ForceFullScreenTrayIconMenuItem.IsEnabled = false;
|
||||
ResetFloatingBarPositionTrayIconMenuItem.Opacity = 0.5;
|
||||
FoldFloatingBarTrayIconMenuItem.Opacity = 0.5;
|
||||
ForceFullScreenTrayIconMenuItem.Opacity = 0.5;
|
||||
if (s != null)
|
||||
{
|
||||
var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4];
|
||||
var FoldFloatingBarTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 5];
|
||||
var ForceFullScreenTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 6];
|
||||
ResetFloatingBarPositionTrayIconMenuItem.IsEnabled = false;
|
||||
FoldFloatingBarTrayIconMenuItem.IsEnabled = false;
|
||||
ForceFullScreenTrayIconMenuItem.IsEnabled = false;
|
||||
ResetFloatingBarPositionTrayIconMenuItem.Opacity = 0.5;
|
||||
FoldFloatingBarTrayIconMenuItem.Opacity = 0.5;
|
||||
ForceFullScreenTrayIconMenuItem.Opacity = 0.5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -299,19 +462,22 @@ namespace Ink_Canvas
|
||||
{
|
||||
var mi = (MenuItem)sender;
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
mainWin.Show();
|
||||
var s = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||
var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4];
|
||||
var FoldFloatingBarTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 5];
|
||||
var ForceFullScreenTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 6];
|
||||
ResetFloatingBarPositionTrayIconMenuItem.IsEnabled = true;
|
||||
FoldFloatingBarTrayIconMenuItem.IsEnabled = true;
|
||||
ForceFullScreenTrayIconMenuItem.IsEnabled = true;
|
||||
ResetFloatingBarPositionTrayIconMenuItem.Opacity = 1;
|
||||
FoldFloatingBarTrayIconMenuItem.Opacity = 1;
|
||||
ForceFullScreenTrayIconMenuItem.Opacity = 1;
|
||||
if (s != null)
|
||||
{
|
||||
var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4];
|
||||
var FoldFloatingBarTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 5];
|
||||
var ForceFullScreenTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 6];
|
||||
ResetFloatingBarPositionTrayIconMenuItem.IsEnabled = true;
|
||||
FoldFloatingBarTrayIconMenuItem.IsEnabled = true;
|
||||
ForceFullScreenTrayIconMenuItem.IsEnabled = true;
|
||||
ResetFloatingBarPositionTrayIconMenuItem.Opacity = 1;
|
||||
FoldFloatingBarTrayIconMenuItem.Opacity = 1;
|
||||
ForceFullScreenTrayIconMenuItem.Opacity = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -340,7 +506,7 @@ namespace Ink_Canvas
|
||||
private void DisableAllHotkeysMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
if (mainWin != null && mainWin.IsLoaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -51,6 +51,14 @@ namespace Ink_Canvas
|
||||
/// <param name="e">鼠标按钮事件的参数。</param>
|
||||
private void BtnToggleVideoPresenter_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings?.Canvas?.LaunchSeewoVideoShowcaseForWhiteboardBooth == true)
|
||||
{
|
||||
// 与主窗口「希沃视频展台」入口(BoardLaunchEasiCamera_MouseUp)一致:先走黑板/白板入口逻辑再启动
|
||||
ImageBlackboard_MouseUp(null, null);
|
||||
SoftwareLauncher.LaunchEasiCamera("希沃视频展台");
|
||||
return;
|
||||
}
|
||||
|
||||
ToggleVideoPresenterSidebar();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,5 +43,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.18.8")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.8")]
|
||||
[assembly: AssemblyVersion("1.7.18.9")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
||||
|
||||
+3
-1
@@ -19,7 +19,7 @@ namespace Ink_Canvas.Properties
|
||||
[CompilerGenerated]
|
||||
public static class Strings
|
||||
{
|
||||
private const string EmbeddedEnUsResxName = "Ink_Canvas.Properties.Strings.enUS.xml";
|
||||
private const string EmbeddedEnUsResxName = "Ink_Canvas.Properties.Strings.en-US.resx";
|
||||
private static readonly object EnUsLock = new object();
|
||||
private static Dictionary<string, string> _embeddedEnUs;
|
||||
private static ResourceManager _resourceMan;
|
||||
@@ -119,5 +119,7 @@ namespace Ink_Canvas.Properties
|
||||
public static string Nav_About => GetString(nameof(Nav_About)) ?? "关于";
|
||||
public static string App_Title => GetString(nameof(App_Title)) ?? "InkCanvasforClass";
|
||||
public static string Booth_Resolution_Tooltip => GetString(nameof(Booth_Resolution_Tooltip)) ?? "展台/截图分辨率";
|
||||
public static string Tray_TempShowMainWindow => GetString(nameof(Tray_TempShowMainWindow)) ?? "显示主窗口(2分钟)";
|
||||
public static string Tray_OpenSettings => GetString(nameof(Tray_OpenSettings)) ?? "打开设置";
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,757 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Nav_Plugins" xml:space="preserve">
|
||||
<value>Plugins</value>
|
||||
</data>
|
||||
<data name="Nav_Startup" xml:space="preserve">
|
||||
<value>Startup</value>
|
||||
</data>
|
||||
<data name="Nav_Canvas" xml:space="preserve">
|
||||
<value>Canvas</value>
|
||||
</data>
|
||||
<data name="Nav_CrashAction" xml:space="preserve">
|
||||
<value>Crash Action</value>
|
||||
</data>
|
||||
<data name="Nav_Gesture" xml:space="preserve">
|
||||
<value>Gesture</value>
|
||||
</data>
|
||||
<data name="Nav_InkRecognition" xml:space="preserve">
|
||||
<value>Ink Recognition</value>
|
||||
</data>
|
||||
<data name="Nav_PPT" xml:space="preserve">
|
||||
<value>PPT</value>
|
||||
</data>
|
||||
<data name="Nav_Advanced" xml:space="preserve">
|
||||
<value>Advanced</value>
|
||||
</data>
|
||||
<data name="Nav_Automation" xml:space="preserve">
|
||||
<value>Automation</value>
|
||||
</data>
|
||||
<data name="Nav_RandomWindow" xml:space="preserve">
|
||||
<value>Random Picker</value>
|
||||
</data>
|
||||
<data name="Nav_Theme" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Nav_Shortcuts" xml:space="preserve">
|
||||
<value>Shortcuts</value>
|
||||
</data>
|
||||
<data name="Nav_About" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="App_Title" xml:space="preserve">
|
||||
<value>InkCanvasforClass</value>
|
||||
</data>
|
||||
<data name="Booth_Resolution_Tooltip" xml:space="preserve">
|
||||
<value>Booth / Screenshot resolution</value>
|
||||
</data>
|
||||
<data name="Nav_Gesture_Settings" xml:space="preserve"><value>Gesture</value></data>
|
||||
<data name="Nav_Theme_Settings" xml:space="preserve"><value>Appearance</value></data>
|
||||
<data name="Nav_PPT_Settings" xml:space="preserve"><value>PPT</value></data>
|
||||
<data name="Nav_Advanced_Settings" xml:space="preserve"><value>Advanced</value></data>
|
||||
<data name="Nav_Automation_Settings" xml:space="preserve"><value>Automation</value></data>
|
||||
<data name="Nav_RandomWindow_Settings" xml:space="preserve"><value>Random Picker</value></data>
|
||||
<data name="Nav_Shortcuts_Settings" xml:space="preserve"><value>Shortcuts</value></data>
|
||||
<data name="CollapseNavSidebar" xml:space="preserve"><value>Collapse sidebar</value></data>
|
||||
<data name="ShowNavSidebar" xml:space="preserve"><value>Show sidebar</value></data>
|
||||
<data name="Tooltip_IccProtocol" xml:space="preserve"><value>Control via icc:// protocol</value></data>
|
||||
<data name="Settings_Title" xml:space="preserve"><value>Settings</value></data>
|
||||
<data name="Settings_AutoSaveHint" xml:space="preserve"><value>Changes are saved automatically; some require restart.</value></data>
|
||||
<data name="Btn_Restart" xml:space="preserve"><value>Restart</value></data>
|
||||
<data name="Btn_Reset" xml:space="preserve"><value>Reset</value></data>
|
||||
<data name="Btn_Exit" xml:space="preserve"><value>Exit</value></data>
|
||||
<data name="Settings_Mode" xml:space="preserve"><value>Mode</value></data>
|
||||
<data name="Settings_ModeDesc" xml:space="preserve"><value>Choose run mode. In PPT-only mode the app is hidden until slide show. (Experimental)</value></data>
|
||||
<data name="Mode_Normal" xml:space="preserve"><value>Normal</value></data>
|
||||
<data name="Mode_PPTOnly" xml:space="preserve"><value>PPT only</value></data>
|
||||
<data name="Settings_NewWindow" xml:space="preserve"><value>New settings window</value></data>
|
||||
<data name="Settings_NewWindowDesc" xml:space="preserve"><value>Open a new settings window. (In development)</value></data>
|
||||
<data name="Btn_OpenNewSettings" xml:space="preserve"><value>Open new settings</value></data>
|
||||
<data name="Settings_Plugins" xml:space="preserve"><value>Plugins</value></data>
|
||||
<data name="Settings_PluginsDesc" xml:space="preserve"><value>Extend Ink Canvas with plugins. Enable, disable, or load custom plugins.</value></data>
|
||||
<data name="Btn_OpenPluginManager" xml:space="preserve"><value>Open plugin manager</value></data>
|
||||
<data name="Startup_Start" xml:space="preserve"><value>Startup</value></data>
|
||||
<data name="Startup_NoFocusMode" xml:space="preserve"><value>No-focus mode</value></data>
|
||||
<data name="Startup_NoBorderMode" xml:space="preserve"><value>Borderless</value></data>
|
||||
<data name="Startup_TopMost" xml:space="preserve"><value>Topmost</value></data>
|
||||
<data name="Startup_UIATopMost" xml:space="preserve"><value>UIA topmost</value></data>
|
||||
<data name="Startup_UIATopMostHint" xml:space="preserve"><value># UIA topmost requires admin to take effect.</value></data>
|
||||
<data name="Header_AutoUpdate" xml:space="preserve"><value>Auto-update</value></data>
|
||||
<data name="Header_SilentUpdate" xml:space="preserve"><value>Silent update</value></data>
|
||||
<data name="SilentUpdate_Hint" xml:space="preserve"><value># Silent update installs when app is idle.</value></data>
|
||||
<data name="Update_Channel" xml:space="preserve"><value>Update channel</value></data>
|
||||
<data name="Channel_Release" xml:space="preserve"><value>Stable (Release)</value></data>
|
||||
<data name="Channel_Preview" xml:space="preserve"><value>Preview</value></data>
|
||||
<data name="Channel_Beta" xml:space="preserve"><value>Beta</value></data>
|
||||
<data name="Channel_Hint" xml:space="preserve"><value># Stable for reliability; Preview for new features.</value></data>
|
||||
<data name="Btn_ManualUpdate" xml:space="preserve"><value>Check for updates</value></data>
|
||||
<data name="ManualUpdate_Hint" xml:space="preserve"><value># Check and download now.</value></data>
|
||||
<data name="Btn_VersionFix" xml:space="preserve"><value>Version fix</value></data>
|
||||
<data name="VersionFix_Hint" xml:space="preserve"><value># Download and install latest for current channel.</value></data>
|
||||
<data name="Btn_Rollback" xml:space="preserve"><value>Rollback</value></data>
|
||||
<data name="Rollback_Hint" xml:space="preserve"><value># Open rollback page.</value></data>
|
||||
<data name="SilentUpdate_AfterDownloadHint" xml:space="preserve"><value># When silent update is off, you will be prompted after download.</value></data>
|
||||
<data name="SilentUpdate_TimeRange" xml:space="preserve"><value>Silent update time range</value></data>
|
||||
<data name="Time_Start" xml:space="preserve"><value>Start time</value></data>
|
||||
<data name="Time_End" xml:space="preserve"><value>End time</value></data>
|
||||
<data name="TimeRange_Hint" xml:space="preserve"><value># If end < start…</value></data>
|
||||
<data name="Startup_RunAtLogin" xml:space="preserve"><value>Run at login</value></data>
|
||||
<data name="Startup_MinimizeToSidebar" xml:space="preserve"><value>Minimize to sidebar at startup</value></data>
|
||||
<data name="Canvas_AndInk" xml:space="preserve"><value>Canvas & ink</value></data>
|
||||
<data name="Canvas_ShowPenCursor" xml:space="preserve"><value>Show pen cursor</value></data>
|
||||
<data name="Canvas_PressureTouch" xml:space="preserve"><value>Pressure-sensitive touch</value></data>
|
||||
<data name="Canvas_PressureTouchHint" xml:space="preserve"><value># Touch devices will support pressure.</value></data>
|
||||
<data name="Canvas_IgnorePressure" xml:space="preserve"><value>Ignore pressure</value></data>
|
||||
<data name="Canvas_IgnorePressureHint" xml:space="preserve"><value># Ignore all device pressure.</value></data>
|
||||
<data name="Canvas_EraserSize" xml:space="preserve"><value>Eraser size</value></data>
|
||||
<data name="Size_VerySmall" xml:space="preserve"><value>Very small</value></data>
|
||||
<data name="Size_Small" xml:space="preserve"><value>Small</value></data>
|
||||
<data name="Size_Medium" xml:space="preserve"><value>Medium</value></data>
|
||||
<data name="Size_Large" xml:space="preserve"><value>Large</value></data>
|
||||
<data name="Size_VeryLarge" xml:space="preserve"><value>Very large</value></data>
|
||||
<data name="EraserSize_SwitchHint" xml:space="preserve"><value># Takes effect on next area eraser use.</value></data>
|
||||
<data name="Canvas_HideInkOnExit" xml:space="preserve"><value>Hide ink when leaving canvas</value></data>
|
||||
<data name="Canvas_HideInkOnExitHint" xml:space="preserve"><value># When enabled…</value></data>
|
||||
<data name="Canvas_ClearInkHistory" xml:space="preserve"><value>Clear ink history when clearing</value></data>
|
||||
<data name="Canvas_ClearImageOnClear" xml:space="preserve"><value>Clear images with canvas</value></data>
|
||||
<data name="Canvas_CompressImage" xml:space="preserve"><value>Compress images >1920×1080</value></data>
|
||||
<data name="Canvas_KeepAsymptote" xml:space="preserve"><value>Keep hyperbola asymptotes</value></data>
|
||||
<data name="Yes" xml:space="preserve"><value>Yes</value></data>
|
||||
<data name="No" xml:space="preserve"><value>No</value></data>
|
||||
<data name="AskEachTime" xml:space="preserve"><value>Ask each time</value></data>
|
||||
<data name="Canvas_AsymptoteHint" xml:space="preserve"><value># Disabling may cause undo bugs.</value></data>
|
||||
<data name="Canvas_ShowCircleCenter" xml:space="preserve"><value>Show circle center</value></data>
|
||||
<data name="Canvas_WPFBezier" xml:space="preserve"><value>WPF default Bezier smoothing</value></data>
|
||||
<data name="Canvas_AdvancedSmoothing" xml:space="preserve"><value>Advanced curve smoothing (recommended)</value></data>
|
||||
<data name="Canvas_InkFade" xml:space="preserve"><value>Ink fade</value></data>
|
||||
<data name="Canvas_InkFadeHint" xml:space="preserve"><value># Ink will not be drawn on canvas when enabled.</value></data>
|
||||
<data name="Canvas_InkFadeTime" xml:space="preserve"><value>Ink fade time</value></data>
|
||||
<data name="Canvas_HideFadeInPenMenu" xml:space="preserve"><value>Hide fade in pen menu</value></data>
|
||||
<data name="Canvas_HideFadeInPenMenuHint" xml:space="preserve"><value># Fade control will be hidden in pen context menu.</value></data>
|
||||
<data name="Color" xml:space="preserve"><value>Color</value></data>
|
||||
<data name="Color_Default" xml:space="preserve"><value>Default</value></data>
|
||||
<data name="Color_Black" xml:space="preserve"><value>Black</value></data>
|
||||
<data name="Color_White" xml:space="preserve"><value>White</value></data>
|
||||
<data name="Color_Red" xml:space="preserve"><value>Red</value></data>
|
||||
<data name="Color_Yellow" xml:space="preserve"><value>Yellow</value></data>
|
||||
<data name="Color_Blue" xml:space="preserve"><value>Blue</value></data>
|
||||
<data name="Color_Green" xml:space="preserve"><value>Green</value></data>
|
||||
<data name="Color_Orange" xml:space="preserve"><value>Orange</value></data>
|
||||
<data name="Color_Purple" xml:space="preserve"><value>Purple</value></data>
|
||||
<data name="Msg_UpdateReady" xml:space="preserve"><value>Update downloaded. It will install when you close the app.</value></data>
|
||||
<data name="Msg_UpdateReadyTitle" xml:space="preserve"><value>Update ready</value></data>
|
||||
<data name="Msg_UpdateDownloadFailed" xml:space="preserve"><value>Update download failed. Please check your network and try again.</value></data>
|
||||
<data name="Msg_DownloadFailedTitle" xml:space="preserve"><value>Download failed</value></data>
|
||||
<data name="Msg_SkipVersion" xml:space="preserve"><value>Version {0} skipped; you will not be prompted until a newer version is released.</value></data>
|
||||
<data name="Msg_SkipVersionTitle" xml:space="preserve"><value>Version skipped</value></data>
|
||||
<data name="Msg_UnexpectedError" xml:space="preserve"><value>An unexpected error occurred. Save your ink and restart the app.</value></data>
|
||||
<data name="Msg_RestartLimitTitle" xml:space="preserve"><value>Too many restarts</value></data>
|
||||
<data name="Msg_RestartLimit" xml:space="preserve"><value>App has restarted 5 times. Auto-restart stopped. Contact the developer or check the system.</value></data>
|
||||
<data name="Splash_Starting" xml:space="preserve"><value>Starting Ink Canvas...</value></data>
|
||||
<data name="Crash_Title" xml:space="preserve"><value>Crash action</value></data>
|
||||
<data name="Crash_Desc" xml:space="preserve"><value>Choose what to do when an unhandled exception occurs:</value></data>
|
||||
<data name="Crash_SilentRestart" xml:space="preserve"><value>Silent restart</value></data>
|
||||
<data name="Crash_NoAction" xml:space="preserve"><value>No action</value></data>
|
||||
<data name="Crash_Hint" xml:space="preserve"><value># Silent restart: automatically restart without prompt. No action: only log, do not restart.</value></data>
|
||||
<data name="Gesture_Title" xml:space="preserve"><value>Gestures</value></data>
|
||||
<data name="Gesture_AutoToggleTwoFinger" xml:space="preserve"><value>Auto-toggle two-finger move in/out of whiteboard</value></data>
|
||||
<data name="Gesture_AutoToggleHint" xml:space="preserve"><value># When enabled: leaving canvas disables two-finger move; entering whiteboard enables it.</value></data>
|
||||
<data name="Gesture_AllowRotateScale" xml:space="preserve"><value>Allow rotate & scale selected ink</value></data>
|
||||
<data name="Gesture_AllowRotateScaleHint" xml:space="preserve"><value># Allows scaling selected ink with two or more fingers (independent of rotate setting).</value></data>
|
||||
<data name="Gesture_EnablePalmEraser" xml:space="preserve"><value>Enable palm eraser</value></data>
|
||||
<data name="Gesture_PalmSensitivity" xml:space="preserve"><value>Palm eraser sensitivity</value></data>
|
||||
<data name="Gesture_PalmSensitivityLow" xml:space="preserve"><value>Low sensitivity</value></data>
|
||||
<data name="Gesture_PalmSensitivityMedium" xml:space="preserve"><value>Medium sensitivity</value></data>
|
||||
<data name="Gesture_PalmSensitivityHigh" xml:space="preserve"><value>High sensitivity</value></data>
|
||||
<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></data>
|
||||
<data name="InkRecog_Title" xml:space="preserve"><value>Ink correction</value></data>
|
||||
<data name="InkRecog_EnableInkRecognition" xml:space="preserve"><value>Enable ink recognition</value></data>
|
||||
<data name="InkRecog_BlockRectFakePressure" xml:space="preserve"><value>Block fake pressure on corrected rectangles</value></data>
|
||||
<data name="InkRecog_BlockTriFakePressure" xml:space="preserve"><value>Block fake pressure on corrected triangles</value></data>
|
||||
<data name="InkRecog_FixTriangle" xml:space="preserve"><value>Correct freehand triangles</value></data>
|
||||
<data name="InkRecog_FixRectangle" xml:space="preserve"><value>Correct freehand rectangles</value></data>
|
||||
<data name="InkRecog_FixEllipse" xml:space="preserve"><value>Correct circles and ellipses</value></data>
|
||||
<data name="InkRecog_AutoStraightLine" xml:space="preserve"><value>Auto-straighten lines</value></data>
|
||||
<data name="InkRecog_LengthThreshold" xml:space="preserve"><value>Length threshold</value></data>
|
||||
<data name="InkRecog_Sensitivity" xml:space="preserve"><value>Sensitivity</value></data>
|
||||
<data name="InkRecog_HighPrecisionStraighten" xml:space="preserve"><value>High-precision straightening</value></data>
|
||||
<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></data>
|
||||
<data name="InkRecog_LineEndpointSnapping" xml:space="preserve"><value>Line endpoint snapping</value></data>
|
||||
<data name="InkRecog_SnappingDistance" xml:space="preserve"><value>Snapping distance</value></data>
|
||||
<data name="Theme_GroupTitle" xml:space="preserve"><value>Personalization</value></data>
|
||||
<data name="Theme_Label" xml:space="preserve"><value>Theme</value></data>
|
||||
<data name="Theme_Light" xml:space="preserve"><value>Light theme</value></data>
|
||||
<data name="Theme_Dark" xml:space="preserve"><value>Dark theme</value></data>
|
||||
<data name="Theme_System" xml:space="preserve"><value>Follow system</value></data>
|
||||
<data name="Theme_EnableSplash" xml:space="preserve"><value>Enable startup animation</value></data>
|
||||
<data name="Theme_SplashStyle" xml:space="preserve"><value>Startup animation style</value></data>
|
||||
<data name="Theme_Splash_Random" xml:space="preserve"><value>Random</value></data>
|
||||
<data name="Theme_Splash_Seasonal" xml:space="preserve"><value>Follow seasons</value></data>
|
||||
<data name="Theme_Splash_Spring" xml:space="preserve"><value>Spring</value></data>
|
||||
<data name="Theme_Splash_Summer" xml:space="preserve"><value>Summer</value></data>
|
||||
<data name="Theme_Splash_Autumn" xml:space="preserve"><value>Autumn</value></data>
|
||||
<data name="Theme_Splash_Winter" xml:space="preserve"><value>Winter</value></data>
|
||||
<data name="Theme_Splash_Horse" xml:space="preserve"><value>Year-of-Horse special</value></data>
|
||||
<data name="Theme_FloatingBarIcon" xml:space="preserve"><value>Floating toolbar icon</value></data>
|
||||
<data name="Theme_FloatingIcon_IccDefault" xml:space="preserve"><value>“ICC-CE” default</value></data>
|
||||
<data name="Theme_FloatingIcon_IccNoShadow" xml:space="preserve"><value>“ICC-CE” no shadow</value></data>
|
||||
<data name="Theme_FloatingIcon_IccDark" xml:space="preserve"><value>“ICC-CE” dark</value></data>
|
||||
<data name="Theme_FloatingIcon_IccDarkBreath" xml:space="preserve"><value>“ICC-CE” dark breathing</value></data>
|
||||
<data name="Theme_FloatingIcon_IccWhiteTransparent" xml:space="preserve"><value>“ICC-CE” white transparent</value></data>
|
||||
<data name="Theme_FloatingIcon_IccBlackTransparent" xml:space="preserve"><value>“ICC-CE” black transparent</value></data>
|
||||
<data name="Theme_FloatingIcon_CoolapkCrossEye" xml:space="preserve"><value>Coolapk cross-eye emoji</value></data>
|
||||
<data name="Theme_FloatingIcon_CoolapkAbused" xml:space="preserve"><value>Coolapk abused emoji</value></data>
|
||||
<data name="Theme_FloatingIcon_CoolapkSmile" xml:space="preserve"><value>Coolapk grin emoji</value></data>
|
||||
<data name="Theme_FloatingIcon_CoolapkUnderwear" xml:space="preserve"><value>Coolapk underwear emoji</value></data>
|
||||
<data name="Theme_FloatingIcon_CoolapkGreenHatDoge" xml:space="preserve"><value>Coolapk green-hat Doge</value></data>
|
||||
<data name="Theme_FloatingIcon_TiebaEmoji" xml:space="preserve"><value>Tieba emoji</value></data>
|
||||
<data name="Theme_CustomFloatingIconLabel" xml:space="preserve"><value>Custom floating icon</value></data>
|
||||
<data name="Theme_Upload" xml:space="preserve"><value>Upload</value></data>
|
||||
<data name="Theme_Manage" xml:space="preserve"><value>Manage</value></data>
|
||||
<data name="Theme_FloatingBarScale" xml:space="preserve"><value>Floating toolbar scale</value></data>
|
||||
<data name="Theme_FloatingBarOpacity" xml:space="preserve"><value>Floating toolbar opacity</value></data>
|
||||
<data name="Theme_FloatingBarOpacityInPPT" xml:space="preserve"><value>Floating bar opacity in PPT</value></data>
|
||||
<data name="Theme_FloatingBarOpacityInPPTHint" xml:space="preserve"><value># Takes effect after re-entering slide show</value></data>
|
||||
<data name="Theme_ShowNibButton" xml:space="preserve"><value>Show nib-mode button in palette</value></data>
|
||||
<data name="Theme_BlackboardScale80" xml:space="preserve"><value>Whiteboard UI 80% scale</value></data>
|
||||
<data name="Theme_ShowTimeInWhiteboard" xml:space="preserve"><value>Show time and date in whiteboard</value></data>
|
||||
<data name="Theme_ShowQuoteInWhiteboard" xml:space="preserve"><value>Show quotes in whiteboard</value></data>
|
||||
<data name="Theme_QuoteSource" xml:space="preserve"><value>Where is the quote from?</value></data>
|
||||
<data name="Theme_QuoteSource_OsuQuotes" xml:space="preserve"><value>osu! player quotes</value></data>
|
||||
<data name="Theme_QuoteSource_Mottos" xml:space="preserve"><value>Inspirational mottos</value></data>
|
||||
<data name="Theme_QuoteSource_GaokaoBless" xml:space="preserve"><value>Gaokao blessings</value></data>
|
||||
<data name="Theme_QuoteSource_Hitokoto" xml:space="preserve"><value>Hitokoto API</value></data>
|
||||
<data name="Theme_Customize" xml:space="preserve"><value>Custom</value></data>
|
||||
<data name="Theme_EnableQuickPanel" xml:space="preserve"><value>Enable quick panel in docked mode</value></data>
|
||||
<data name="Theme_UnfoldButtonIcon" xml:space="preserve"><value>Un-dock button icon</value></data>
|
||||
<data name="Theme_UnfoldIcon_Arrow" xml:space="preserve"><value>Arrow</value></data>
|
||||
<data name="Theme_UnfoldIcon_Pen" xml:space="preserve"><value>Pen</value></data>
|
||||
<data name="Theme_FloatingBarButtonsTitle" xml:space="preserve"><value>Floating bar buttons</value></data>
|
||||
<data name="Theme_UseLegacyFloatingBarUI" xml:space="preserve"><value>Use legacy floating bar UI</value></data>
|
||||
<data name="Theme_ShowShapeButton" xml:space="preserve"><value>Show shape button</value></data>
|
||||
<data name="Theme_ShowUndoButton" xml:space="preserve"><value>Show undo button</value></data>
|
||||
<data name="Theme_ShowRedoButton" xml:space="preserve"><value>Show redo button</value></data>
|
||||
<data name="Theme_ShowClearButton" xml:space="preserve"><value>Show clear button</value></data>
|
||||
<data name="Theme_ShowWhiteboardButton" xml:space="preserve"><value>Show whiteboard button</value></data>
|
||||
<data name="Theme_ShowHideButton" xml:space="preserve"><value>Show hide button</value></data>
|
||||
<data name="Theme_ShowLassoButton" xml:space="preserve"><value>Show lasso select button</value></data>
|
||||
<data name="Theme_ShowClearAndMouseButton" xml:space="preserve"><value>Show clear+mouse button</value></data>
|
||||
<data name="Theme_ShowQuickPalette" xml:space="preserve"><value>Show quick palette</value></data>
|
||||
<data name="Theme_QuickPaletteMode" xml:space="preserve"><value>Quick palette display mode</value></data>
|
||||
<data name="Theme_QuickPalette_SingleRow" xml:space="preserve"><value>Single row (6 colors)</value></data>
|
||||
<data name="Theme_QuickPalette_DoubleRow" xml:space="preserve"><value>Double row (8 colors)</value></data>
|
||||
<data name="Theme_EraserButtonDisplay" xml:space="preserve"><value>Eraser button display</value></data>
|
||||
<data name="Theme_EraserDisplay_Both" xml:space="preserve"><value>Show both</value></data>
|
||||
<data name="Theme_EraserDisplay_AreaOnly" xml:space="preserve"><value>Area eraser only</value></data>
|
||||
<data name="Theme_EraserDisplay_LineOnly" xml:space="preserve"><value>Line eraser only</value></data>
|
||||
<data name="Theme_EraserDisplay_None" xml:space="preserve"><value>Hide all</value></data>
|
||||
<data name="Tray_GroupTitle" xml:space="preserve"><value>Taskbar tray icon</value></data>
|
||||
<data name="Tray_EnableTrayIcon" xml:space="preserve"><value>Enable tray icon</value></data>
|
||||
<data name="PPT_GroupTitle" xml:space="preserve"><value>PPT integration</value></data>
|
||||
<data name="PPT_GroupHint" xml:space="preserve"><value>These settings apply during slide show and override others.</value></data>
|
||||
<data name="PPT_SupportPowerPoint" xml:space="preserve"><value>Microsoft PowerPoint support</value></data>
|
||||
<data name="PPT_Enhancement" xml:space="preserve"><value>PowerPoint enhancement</value></data>
|
||||
<data name="PPT_SkipAnimations" xml:space="preserve"><value>Steal focus to skip animations (PPT)</value></data>
|
||||
<data name="PPT_UseRot" xml:space="preserve"><value>Use ROT integration</value></data>
|
||||
<data name="PPT_SupportWPS" xml:space="preserve"><value>WPS support</value></data>
|
||||
<data name="PPT_KillWppProcess" xml:space="preserve"><value>Kill WPP process (avoid leftovers)</value></data>
|
||||
<data name="PPT_KillWppHint" xml:space="preserve"><value># When disabled, leftover WPP processes may cause slow close or cannot exit completely.</value></data>
|
||||
<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></data>
|
||||
<data name="PPT_WpsLagWarning" xml:space="preserve"><value>Enabling WPS support may cause lag when closing WPS!</value></data>
|
||||
<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></data>
|
||||
<data name="Canvas_HideStrokeWhenSelecting" xml:space="preserve"><value>Hide ink when exiting board mode</value></data>
|
||||
<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></data>
|
||||
<data name="Canvas_ClearInkAlsoClearHistory" xml:space="preserve"><value>Clear ink history when clearing ink</value></data>
|
||||
<data name="Canvas_ClearCanvasAlsoClearImages" xml:space="preserve"><value>Clear images when clearing canvas</value></data>
|
||||
<data name="Canvas_CompressPicturesUploaded" xml:space="preserve"><value>Auto-compress images when inserting (larger than 1920x1080)</value></data>
|
||||
<data name="PPT_FlipButtonsTitle" xml:space="preserve"><value>PPT page-turn buttons</value></data>
|
||||
<data name="PPT_ShowFlipButtons" xml:space="preserve"><value>Show page-turn buttons in PPT mode</value></data>
|
||||
<data name="PPT_Position_LeftBottom" xml:space="preserve"><value>Bottom left</value></data>
|
||||
<data name="PPT_Position_RightBottom" xml:space="preserve"><value>Bottom right</value></data>
|
||||
<data name="PPT_Position_Left" xml:space="preserve"><value>Left</value></data>
|
||||
<data name="PPT_Position_Right" xml:space="preserve"><value>Right</value></data>
|
||||
<data name="PPT_LeftOffset" xml:space="preserve"><value>Left offset</value></data>
|
||||
<data name="PPT_LeftOpacity" xml:space="preserve"><value>Left opacity</value></data>
|
||||
<data name="PPT_RightOffset" xml:space="preserve"><value>Right offset</value></data>
|
||||
<data name="PPT_RightOpacity" xml:space="preserve"><value>Right opacity</value></data>
|
||||
<data name="PPT_OffsetHint" xml:space="preserve"><value># Increase for up, decrease for down; 0 = no offset, centered.</value></data>
|
||||
<data name="PPT_LeftBottomOffset" xml:space="preserve"><value>Bottom left offset</value></data>
|
||||
<data name="PPT_LeftBottomOpacity" xml:space="preserve"><value>Bottom left opacity</value></data>
|
||||
<data name="PPT_RightBottomOffset" xml:space="preserve"><value>Bottom right offset</value></data>
|
||||
<data name="PPT_RightBottomOpacity" xml:space="preserve"><value>Bottom right opacity</value></data>
|
||||
<data name="PPT_OffsetHintHorizontal" xml:space="preserve"><value># Increase for right, decrease for left; 0 = no offset, centered.</value></data>
|
||||
<data name="PPT_SideGroupTitle" xml:space="preserve"><value>Sides</value></data>
|
||||
<data name="PPT_ShowPageNumber" xml:space="preserve"><value>Show page number</value></data>
|
||||
<data name="PPT_HalfOpacity" xml:space="preserve"><value>Half opacity</value></data>
|
||||
<data name="PPT_BlackBackground" xml:space="preserve"><value>Black background</value></data>
|
||||
<data name="PPT_BottomGroupTitle" xml:space="preserve"><value>Bottom left & right</value></data>
|
||||
<data name="PPT_PageButtonClickable" xml:space="preserve"><value>PPT page button clickable</value></data>
|
||||
<data name="PPT_PageButtonClickableHint" xml:space="preserve"><value># When enabled, clicking the page button opens PowerPoint grid thumbnails. Not supported in WPS.</value></data>
|
||||
<data name="PPT_LongPressPageTurn" xml:space="preserve"><value>PPT long-press to turn page</value></data>
|
||||
<data name="PPT_LongPressPageTurnHint" xml:space="preserve"><value># When enabled, long-press on PPT page button to turn pages continuously.</value></data>
|
||||
<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></data>
|
||||
<data name="Header_SilentUpdate" xml:space="preserve"><value>Silent update</value></data>
|
||||
<data name="Startup_SilentUpdateHint" xml:space="preserve"><value># Silent update installs when the app is idle; no manual action needed.</value></data>
|
||||
<data name="Startup_UpdateChannel" xml:space="preserve"><value>Update channel</value></data>
|
||||
<data name="Update_Release" xml:space="preserve"><value>Stable (Release)</value></data>
|
||||
<data name="Update_Preview" xml:space="preserve"><value>Preview</value></data>
|
||||
<data name="Update_Beta" xml:space="preserve"><value>Beta</value></data>
|
||||
<data name="Startup_UpdateChannelHint" xml:space="preserve"><value># Stable: reliable updates. Preview: new features with better stability than Beta. Beta: earliest new features.</value></data>
|
||||
<data name="Btn_ManualUpdate" xml:space="preserve"><value>Check for updates</value></data>
|
||||
<data name="Startup_ManualUpdateHint" xml:space="preserve"><value># Check and download the latest version now.</value></data>
|
||||
<data name="Btn_FixVersion" xml:space="preserve"><value>Repair installation</value></data>
|
||||
<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></data>
|
||||
<data name="Btn_HistoryRollback" xml:space="preserve"><value>Rollback to previous version</value></data>
|
||||
<data name="Startup_HistoryRollbackHint" xml:space="preserve"><value># Opens a page to manually roll back to an earlier version.</value></data>
|
||||
<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></data>
|
||||
<data name="Startup_SilentUpdateTimePeriod" xml:space="preserve"><value>Silent update time window</value></data>
|
||||
<data name="Startup_StartTime" xml:space="preserve"><value>Start time</value></data>
|
||||
<data name="Startup_EndTime" xml:space="preserve"><value>End time</value></data>
|
||||
<data name="Startup_TimePeriodHint" xml:space="preserve"><value># If end < start, end is next day. If start = end, window is 24h.</value></data>
|
||||
<data name="Startup_RunAtStartup" xml:space="preserve"><value>Run at startup</value></data>
|
||||
<data name="Startup_FoldAtStartup" xml:space="preserve"><value>Dock to sidebar after startup</value></data>
|
||||
<data name="Canvas_GroupTitle" xml:space="preserve"><value>Canvas and ink</value></data>
|
||||
<data name="Canvas_ShowCursor" xml:space="preserve"><value>Show pen cursor</value></data>
|
||||
<data name="Canvas_EnablePressureTouch" xml:space="preserve"><value>Enable pressure-sensitive touch</value></data>
|
||||
<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></data>
|
||||
<data name="Canvas_DisablePressure" xml:space="preserve"><value>Ignore pressure</value></data>
|
||||
<data name="Canvas_DisablePressureHint" xml:space="preserve"><value># When on, all strokes use uniform thickness; mutually exclusive with pressure-sensitive touch.</value></data>
|
||||
<data name="Canvas_EraserSize" xml:space="preserve"><value>Eraser size</value></data>
|
||||
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve"><value>Very small</value></data>
|
||||
<data name="Canvas_EraserSize_Small" xml:space="preserve"><value>Small</value></data>
|
||||
<data name="Canvas_EraserSize_Medium" xml:space="preserve"><value>Medium</value></data>
|
||||
<data name="Canvas_EraserSize_Large" xml:space="preserve"><value>Large</value></data>
|
||||
<data name="Canvas_EraserSize_VeryLarge" xml:space="preserve"><value>Very large</value></data>
|
||||
<data name="Canvas_EraserSizeHint" xml:space="preserve"><value># Change takes effect next time you use area eraser.</value></data>
|
||||
<data name="Canvas_KeepHyperbolaAsymptote" xml:space="preserve"><value>Keep hyperbola asymptotes</value></data>
|
||||
<data name="Canvas_Yes" xml:space="preserve"><value>Yes</value></data>
|
||||
<data name="Canvas_No" xml:space="preserve"><value>No</value></data>
|
||||
<data name="Canvas_AskEachTime" xml:space="preserve"><value>Ask each time</value></data>
|
||||
<data name="Canvas_HyperbolaAsymptoteHint" xml:space="preserve"><value># If not kept, undo-related bugs may occur.</value></data>
|
||||
<data name="Canvas_ShowCircleCenter" xml:space="preserve"><value>Show circle center when drawing</value></data>
|
||||
<data name="Canvas_WPFBezierSmoothing" xml:space="preserve"><value>Use WPF default Bezier smoothing</value></data>
|
||||
<data name="Canvas_AdvancedBezierSmoothing" xml:space="preserve"><value>Use advanced curve smoothing (recommended)</value></data>
|
||||
<data name="Canvas_EnableInkFade" xml:space="preserve"><value>Enable ink fade</value></data>
|
||||
<data name="Canvas_EnableInkFadeHint" xml:space="preserve"><value># When on, ink is not committed to canvas; it fades after the set time.</value></data>
|
||||
<data name="Canvas_InkFadeTime" xml:space="preserve"><value>Ink fade time</value></data>
|
||||
<data name="Canvas_HideInkFadeInPenMenu" xml:space="preserve"><value>Hide ink fade control in pen menu</value></data>
|
||||
<data name="Canvas_HideInkFadeInPenMenuHint" xml:space="preserve"><value># When on, the pen context menu will not show the ink fade control.</value></data>
|
||||
<data name="Canvas_BrushAutoRestore" xml:space="preserve"><value>Enable brush auto-restore</value></data>
|
||||
<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></data>
|
||||
<data name="Canvas_AutoRestoreTimePoints" xml:space="preserve"><value>Auto-restore time points (HH:mm, multiple with ;)</value></data>
|
||||
<data name="Canvas_RestoreColor" xml:space="preserve"><value>Restore color</value></data>
|
||||
<data name="Canvas_Color_Default" xml:space="preserve"><value>Default</value></data>
|
||||
<data name="Canvas_Color_Black" xml:space="preserve"><value>Black</value></data>
|
||||
<data name="Canvas_Color_White" xml:space="preserve"><value>White</value></data>
|
||||
<data name="Canvas_Color_Red" xml:space="preserve"><value>Red</value></data>
|
||||
<data name="Canvas_Color_Yellow" xml:space="preserve"><value>Yellow</value></data>
|
||||
<data name="Canvas_Color_Blue" xml:space="preserve"><value>Blue</value></data>
|
||||
<data name="Canvas_Color_Green" xml:space="preserve"><value>Green</value></data>
|
||||
<data name="Canvas_Color_Orange" xml:space="preserve"><value>Orange</value></data>
|
||||
<data name="Canvas_Color_Purple" xml:space="preserve"><value>Purple</value></data>
|
||||
<data name="Canvas_RestoreWidth" xml:space="preserve"><value>Restore stroke width</value></data>
|
||||
<data name="Canvas_RestoreOpacity" xml:space="preserve"><value>Restore opacity</value></data>
|
||||
<data name="Canvas_SwitchBackAfterEraser" xml:space="preserve"><value>Switch back to annotation after eraser</value></data>
|
||||
<data name="Canvas_SwitchBackAfterEraserHint" xml:space="preserve"><value># When on, after erasing, staying idle for a while will switch back to annotation mode.</value></data>
|
||||
<data name="Canvas_SwitchBackDelay" xml:space="preserve"><value>Auto switch delay</value></data>
|
||||
<data name="Canvas_SwitchBackDelayHint" xml:space="preserve"><value># If you erase again within the delay, the timer resets.</value></data>
|
||||
<data name="InkRecog_LineEndpointSnappingHint" xml:space="preserve"><value># When on, line endpoints near other endpoints will snap and connect.</value></data>
|
||||
<data name="PPT_EnterAnnotationOnShow" xml:space="preserve"><value>Enter annotation mode when starting PPT slide show</value></data>
|
||||
<data name="PPT_ConflictWithAutoFold" xml:space="preserve"><value>Conflicts with "Auto fold when playing PPT" in Automation!</value></data>
|
||||
<data name="PPT_TwoFingerGesture" xml:space="preserve"><value>Allow two-finger gestures in slide show</value></data>
|
||||
<data name="PPT_FingerGestureSlide" xml:space="preserve"><value>Allow finger gesture to turn slides</value></data>
|
||||
<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></data>
|
||||
<data name="PPT_ShowGestureButtonInShow" xml:space="preserve"><value>Show gesture buttons in PPT slide show</value></data>
|
||||
<data name="PPT_ShowGestureButtonInShowHint" xml:space="preserve"><value># When on, gesture buttons are shown in PPT slide show.</value></data>
|
||||
<data name="PPT_TimeCapsule" xml:space="preserve"><value>PPT time capsule</value></data>
|
||||
<data name="PPT_TimeCapsuleHint" xml:space="preserve"><value># When on, show time capsule in PPT show; can replace minimized timer window.</value></data>
|
||||
<data name="PPT_TimeCapsulePosition" xml:space="preserve"><value>Time capsule position:</value></data>
|
||||
<data name="PPT_TimeCapsulePos_TL" xml:space="preserve"><value>Top left</value></data>
|
||||
<data name="PPT_TimeCapsulePos_TR" xml:space="preserve"><value>Top right</value></data>
|
||||
<data name="PPT_TimeCapsulePos_Center" xml:space="preserve"><value>Top center</value></data>
|
||||
<data name="PPT_ShowQuickPanelInShow" xml:space="preserve"><value>Show quick panel in PPT slide show</value></data>
|
||||
<data name="PPT_ShowQuickPanelInShowHint" xml:space="preserve"><value># When off, quick panel is hidden in PPT slide show.</value></data>
|
||||
<data name="PPT_AutoScreenshot" xml:space="preserve"><value>Auto screenshot on slide change</value></data>
|
||||
<data name="PPT_AutoScreenshotHint" xml:space="preserve"><value># When on, auto-screenshot when turning page with ink on slide.</value></data>
|
||||
<data name="PPT_AutoSaveStrokes" xml:space="preserve"><value>Auto-save slide ink</value></data>
|
||||
<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></data>
|
||||
<data name="PPT_RememberLastPage" xml:space="preserve"><value>Remember and prompt last slide position</value></data>
|
||||
<data name="PPT_RememberLastPageHint" xml:space="preserve"><value># When on, last page is recorded; choose Yes to jump to it.</value></data>
|
||||
<data name="PPT_GoToFirstPageOnReenter" xml:space="preserve"><value>Go to first slide when entering show</value></data>
|
||||
<data name="PPT_NotifyHiddenPage" xml:space="preserve"><value>Warn about hidden slides</value></data>
|
||||
<data name="PPT_NotifyAutoPlay" xml:space="preserve"><value>Warn if auto-play is enabled</value></data>
|
||||
<data name="Advanced_Title" xml:space="preserve"><value>Advanced</value></data>
|
||||
<data name="Advanced_TouchMultiplierHint" xml:space="preserve"><value>Adjust when finger-touch shows circle eraser or palm eraser is much larger than palm</value></data>
|
||||
<data name="Advanced_SpecialScreenMode" xml:space="preserve"><value>Special screen mode</value></data>
|
||||
<data name="Advanced_TouchMultiplier" xml:space="preserve"><value>Touch multiplier</value></data>
|
||||
<data name="Advanced_TouchMultiplierCalibrateHint" xml:space="preserve"><value>Tap with pen in the area below to estimate touch size multiplier</value></data>
|
||||
<data name="Advanced_TouchMultiplierValueHint" xml:space="preserve"><value># Value is for reference only</value></data>
|
||||
<data name="Advanced_EraserBindTouchMultiplier" xml:space="preserve"><value>Bind eraser to touch size multiplier</value></data>
|
||||
<data name="Advanced_EraserBindTouchHint" xml:space="preserve"><value># BoundsWidth is used as contact area threshold</value></data>
|
||||
<data name="Advanced_QuadIRMode" xml:space="preserve"><value>Quad IR mode</value></data>
|
||||
<data name="Advanced_Logging" xml:space="preserve"><value>Enable logging</value></data>
|
||||
<data name="Advanced_LogByDate" xml:space="preserve"><value>Save logs by date</value></data>
|
||||
<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></data>
|
||||
<data name="Advanced_ConfirmExit" xml:space="preserve"><value>Confirm exit with dialog</value></data>
|
||||
<data name="Advanced_FullScreenHelper" xml:space="preserve"><value>Enable FullScreenHelper</value></data>
|
||||
<data name="Advanced_Experimental" xml:space="preserve"><value>Experimental</value></data>
|
||||
<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></data>
|
||||
<data name="Advanced_AvoidFullScreenHelper" xml:space="preserve"><value>Enable AvoidFullScreenHelper</value></data>
|
||||
<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></data>
|
||||
<data name="Advanced_EdgeGestureUtil" xml:space="preserve"><value>Enable EdgeGestureUtil</value></data>
|
||||
<data name="Tools_MoreFeaturesTitle" xml:space="preserve"><value>More features</value></data>
|
||||
<data name="Tools_Timer" xml:space="preserve"><value>Timer</value></data>
|
||||
<data name="Tools_RandomDraw" xml:space="preserve"><value>Random draw</value></data>
|
||||
<data name="Tools_SingleDraw" xml:space="preserve"><value>Single draw</value></data>
|
||||
<data name="Tools_Save" xml:space="preserve"><value>Save</value></data>
|
||||
<data name="Tools_Open" xml:space="preserve"><value>Open...</value></data>
|
||||
<data name="Tools_Replay" xml:space="preserve"><value>Replay</value></data>
|
||||
<data name="Tools_Screenshot" xml:space="preserve"><value>Screenshot</value></data>
|
||||
<data name="Tools_Manual" xml:space="preserve"><value>Manual</value></data>
|
||||
<data name="Tools_Settings" xml:space="preserve"><value>Settings</value></data>
|
||||
<data name="QuickPanel_SingleDraw" xml:space="preserve"><value>Single draw</value></data>
|
||||
<data name="QuickPanel_RandomDraw" xml:space="preserve"><value>Random draw</value></data>
|
||||
<data name="QuickPanel_Timer" xml:space="preserve"><value>Timer</value></data>
|
||||
<data name="QuickPanel_Whiteboard" xml:space="preserve"><value>Whiteboard</value></data>
|
||||
<data name="QuickPanel_ExitShow" xml:space="preserve"><value>Exit slide show</value></data>
|
||||
<data name="QuickPanel_Show" xml:space="preserve"><value>Show</value></data>
|
||||
<data name="QuickPanel_Exit" xml:space="preserve"><value>Exit</value></data>
|
||||
<data name="Backup_Title" xml:space="preserve"><value>Settings backup & restore</value></data>
|
||||
<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></data>
|
||||
<data name="Backup_AutoBeforeUpdate" xml:space="preserve"><value>Backup before update</value></data>
|
||||
<data name="Backup_AutoPeriodic" xml:space="preserve"><value>Periodic auto-backup</value></data>
|
||||
<data name="Backup_Interval" xml:space="preserve"><value>Backup interval</value></data>
|
||||
<data name="Backup_Interval_1Day" xml:space="preserve"><value>1 day</value></data>
|
||||
<data name="Backup_Interval_3Days" xml:space="preserve"><value>3 days</value></data>
|
||||
<data name="Backup_Interval_7Days" xml:space="preserve"><value>7 days</value></data>
|
||||
<data name="Backup_Interval_14Days" xml:space="preserve"><value>14 days</value></data>
|
||||
<data name="Backup_Interval_30Days" xml:space="preserve"><value>30 days</value></data>
|
||||
<data name="Backup_Interval_DefaultHint" xml:space="preserve"><value>(default: 7 days)</value></data>
|
||||
<data name="Backup_Manual" xml:space="preserve"><value>Backup now</value></data>
|
||||
<data name="Backup_Restore" xml:space="preserve"><value>Restore backup</value></data>
|
||||
<data name="ConfigProfiles_Title" xml:space="preserve"><value>Config profiles & hot reload</value></data>
|
||||
<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></data>
|
||||
<data name="ConfigProfiles_Label" xml:space="preserve"><value>Profile:</value></data>
|
||||
<data name="ConfigProfiles_Delete" xml:space="preserve"><value>Delete profile</value></data>
|
||||
<data name="ConfigProfiles_SaveAs" xml:space="preserve"><value>Save as profile</value></data>
|
||||
<data name="Automation_Title" xml:space="preserve"><value>Automation</value></data>
|
||||
<data name="Automation_AutoFoldTitle" xml:space="preserve"><value>Auto fold</value></data>
|
||||
<data name="AutoFold_App_SeewoBoard5" xml:space="preserve"><value>Seewo Whiteboard 5</value></data>
|
||||
<data name="AutoFold_App_SeewoCamera" xml:space="preserve"><value>Seewo Visual Presenter</value></data>
|
||||
<data name="AutoFold_App_SeewoBoard3" xml:space="preserve"><value>Seewo Whiteboard 3</value></data>
|
||||
<data name="AutoFold_App_SeewoLightBoard" xml:space="preserve"><value>Seewo Lite Whiteboard</value></data>
|
||||
<data name="AutoFold_App_SeewoLightBoard5C" xml:space="preserve"><value>Seewo Lite Whiteboard 5C</value></data>
|
||||
<data name="AutoFold_App_SeewoPinco" xml:space="preserve"><value>Seewo Pinco</value></data>
|
||||
<data name="AutoFold_App_HiteBoard" xml:space="preserve"><value>HiteBoard</value></data>
|
||||
<data name="AutoFold_App_HiteCamera" xml:space="preserve"><value>Hite visual presenter</value></data>
|
||||
<data name="AutoFold_App_HiteLightBoard" xml:space="preserve"><value>Hite Lite Whiteboard</value></data>
|
||||
<data name="AutoFold_App_WenXiangBoard" xml:space="preserve"><value>WenXiang Whiteboard</value></data>
|
||||
<data name="AutoFold_App_MSWhiteboard" xml:space="preserve"><value>Microsoft Whiteboard</value></data>
|
||||
<data name="AutoFold_App_AdmoxBoard" xml:space="preserve"><value>Admox Whiteboard</value></data>
|
||||
<data name="AutoFold_App_AdmoxBooth" xml:space="preserve"><value>Admox visual presenter</value></data>
|
||||
<data name="AutoFold_App_YiYunBoard" xml:space="preserve"><value>YiYun Whiteboard</value></data>
|
||||
<data name="AutoFold_App_YiYunBooth" xml:space="preserve"><value>YiYun visual presenter</value></data>
|
||||
<data name="AutoFold_App_MaxHubBoard" xml:space="preserve"><value>MaxHub Whiteboard</value></data>
|
||||
<data name="AutoFold_IgnoreEasiNoteDesktopAnno" xml:space="preserve"><value>Ignore EN5 desktop annotation window when auto folding</value></data>
|
||||
<data name="AutoFold_OldZyBoard" xml:space="preserve"><value>Auto fold when entering old ZhongYuan whiteboard</value></data>
|
||||
<data name="Automation_AutoFoldInPPT" xml:space="preserve"><value>Auto fold while playing PPT</value></data>
|
||||
<data name="Automation_KeepFoldAfterExit" xml:space="preserve"><value>Keep folded after app exit</value></data>
|
||||
<data name="Automation_KeepFoldAfterExitHint" xml:space="preserve"><value># When on, apps that trigger auto fold will stay folded even after they exit.</value></data>
|
||||
<data name="AutoKill_Title" xml:space="preserve"><value>Auto kill</value></data>
|
||||
<data name="AutoKill_PptTools" xml:space="preserve"><value>Auto kill Seewo PPT tools</value></data>
|
||||
<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></data>
|
||||
<data name="AutoKill_EasiNote5" xml:space="preserve"><value>Auto kill Seewo Whiteboard 5</value></data>
|
||||
<data name="AutoKill_HiteAnnotation" xml:space="preserve"><value>Auto kill Hite screen writing</value></data>
|
||||
<data name="AutoKill_HiteAfterKillEnterAnnotation" xml:space="preserve"><value>Enter annotation after killing Hite screen writing</value></data>
|
||||
<data name="AutoKill_YouJiao" xml:space="preserve"><value>Auto kill YouJiao teacher</value></data>
|
||||
<data name="AutoKill_SeewoDesktop2Anno" xml:space="preserve"><value>Auto kill Seewo Desktop 2.0 annotation</value></data>
|
||||
<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></data>
|
||||
<data name="AutoKill_SameAppTitle" xml:space="preserve"><value>Kill similar apps</value></data>
|
||||
<data name="AutoKill_InkCanvasIC" xml:space="preserve"><value>Auto kill Ink Canvas and IC+</value></data>
|
||||
<data name="AutoKill_ICA" xml:space="preserve"><value>Auto kill ICA (both new & old)</value></data>
|
||||
<data name="AutoKill_Inkeys" xml:space="preserve"><value>Auto kill Inkeys (new only)</value></data>
|
||||
<data name="FileAssoc_Title" xml:space="preserve"><value>File association</value></data>
|
||||
<data name="FileAssoc_Desc" xml:space="preserve"><value>Manage .icstk file association so double-click opens in Ink Canvas.</value></data>
|
||||
<data name="FileAssoc_Unregister" xml:space="preserve"><value>Remove association</value></data>
|
||||
<data name="FileAssoc_Check" xml:space="preserve"><value>Check status</value></data>
|
||||
<data name="FileAssoc_Register" xml:space="preserve"><value>Register association</value></data>
|
||||
<data name="FloatingInterceptor_Title" xml:space="preserve"><value>Floating window interceptor</value></data>
|
||||
<data name="FloatingInterceptor_Desc" xml:space="preserve"><value>Detect and block floating windows from similar software</value></data>
|
||||
<data name="FloatingInterceptor_Enable" xml:space="preserve"><value>Enable floating window interceptor</value></data>
|
||||
<data name="FloatingInterceptor_StatusNotRunning" xml:space="preserve"><value>Interceptor not running</value></data>
|
||||
<data name="Storage_AutoScreenshotOnClear" xml:space="preserve"><value>Auto screenshot on clear</value></data>
|
||||
<data name="Storage_ScreenshotsByDateFolder" xml:space="preserve"><value>Save screenshots in date folders</value></data>
|
||||
<data name="Storage_AutoSaveInkOnScreenshot" xml:space="preserve"><value>Auto-save ink when screenshotting</value></data>
|
||||
<data name="Storage_AutoSaveInk" xml:space="preserve"><value>Auto-save ink periodically</value></data>
|
||||
<data name="Storage_AutoSaveInterval" xml:space="preserve"><value>Save interval</value></data>
|
||||
<data name="Storage_AutoSaveInterval_1Min" xml:space="preserve"><value>1 minute</value></data>
|
||||
<data name="Storage_AutoSaveInterval_3Min" xml:space="preserve"><value>3 minutes</value></data>
|
||||
<data name="Storage_AutoSaveInterval_5Min" xml:space="preserve"><value>5 minutes</value></data>
|
||||
<data name="Storage_AutoSaveInterval_10Min" xml:space="preserve"><value>10 minutes</value></data>
|
||||
<data name="Storage_AutoSaveInterval_15Min" xml:space="preserve"><value>15 minutes</value></data>
|
||||
<data name="Storage_AutoSaveInterval_30Min" xml:space="preserve"><value>30 minutes</value></data>
|
||||
<data name="Storage_AutoSaveInterval_60Min" xml:space="preserve"><value>60 minutes</value></data>
|
||||
<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></data>
|
||||
<data name="Storage_SaveFullPageStrokes" xml:space="preserve"><value>Save full-page strokes</value></data>
|
||||
<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></data>
|
||||
<data name="Storage_SaveAsXml" xml:space="preserve"><value>Save as XML format</value></data>
|
||||
<data name="Storage_SaveAsXmlHint" xml:space="preserve"><value># When on, strokes are saved as XML (ISF) for easier inspection and editing.</value></data>
|
||||
<data name="Storage_AutoScreenshotMinInk" xml:space="preserve"><value>Minimum ink for auto screenshot</value></data>
|
||||
<data name="Storage_PathTitle" xml:space="preserve"><value>Stroke and screenshot save path</value></data>
|
||||
<data name="Storage_PathBrowse" xml:space="preserve"><value>Browse</value></data>
|
||||
<data name="Storage_PathSetToD" xml:space="preserve"><value>Set save path to D:\Ink Canvas</value></data>
|
||||
<data name="Storage_PathSetToDocuments" xml:space="preserve"><value>Set save path to Documents</value></data>
|
||||
<data name="Storage_PathPermissionHint" xml:space="preserve"><value># Please ensure the save folder is writable.</value></data>
|
||||
<data name="Storage_AutoDeleteTitle" xml:space="preserve"><value>Auto delete old strokes and screenshots</value></data>
|
||||
<data name="Storage_AutoDeleteHint" xml:space="preserve"><value># When on, all .icstk and .png files in the auto-save folder may be deleted!</value></data>
|
||||
<data name="Storage_RetentionTitle" xml:space="preserve"><value>Retention duration</value></data>
|
||||
<data name="Storage_RetentionUnitDays" xml:space="preserve"><value>days</value></data>
|
||||
<data name="CloudStorage_Manage" xml:space="preserve"><value>Cloud storage management</value></data>
|
||||
<data name="FoldMode_Title" xml:space="preserve"><value>Fold mode</value></data>
|
||||
<data name="FoldMode_ExitToAnnotation" xml:space="preserve"><value>Switch to annotation when exiting fold mode</value></data>
|
||||
<data name="FoldMode_ExitToAnnotationHint" xml:space="preserve"><value># When on, exiting fold mode switches back to annotation for convenience.</value></data>
|
||||
<data name="FoldMode_AutoFoldAfterPPT" xml:space="preserve"><value>Auto fold floating bar after PPT show</value></data>
|
||||
<data name="FoldMode_AutoFoldAfterPPTHint" xml:space="preserve"><value># When on, floating bar is auto-folded after exiting PPT slide show.</value></data>
|
||||
<data name="FoldMode_AutoFoldAfterWhiteboard" xml:space="preserve"><value>Auto fold when exiting whiteboard</value></data>
|
||||
<data name="FoldMode_AutoFoldAfterWhiteboardHint" xml:space="preserve"><value># When on, exiting whiteboard folds back to sidebar.</value></data>
|
||||
<data name="Random_Title" xml:space="preserve"><value>Random roll call</value></data>
|
||||
<data name="Random_ShowEditNamesButton" xml:space="preserve"><value>Show button to edit name list</value></data>
|
||||
<data name="Random_BackgroundSettingsTitle" xml:space="preserve"><value>Roll-call window background (legacy UI only)</value></data>
|
||||
<data name="Random_BackgroundSelectLabel" xml:space="preserve"><value>Background:</value></data>
|
||||
<data name="Random_Background_Default" xml:space="preserve"><value>Default background</value></data>
|
||||
<data name="Random_CustomBackgroundLabel" xml:space="preserve"><value>Custom background:</value></data>
|
||||
<data name="Random_CustomBackground_Upload" xml:space="preserve"><value>Upload</value></data>
|
||||
<data name="Random_CustomBackground_Manage" xml:space="preserve"><value>Manage</value></data>
|
||||
<data name="Random_EnableButtons" xml:space="preserve"><value>Enable random & single-draw buttons</value></data>
|
||||
<data name="Random_EnableQuickButton" xml:space="preserve"><value>Enable quick-draw floating button</value></data>
|
||||
<data name="Random_UseExternal" xml:space="preserve"><value>Use external roll-call app</value></data>
|
||||
<data name="Random_ExternalTypeLabel" xml:space="preserve"><value>Roll-call type</value></data>
|
||||
<data name="Random_ExternalType_ClassIsland" xml:space="preserve"><value>ClassIsland</value></data>
|
||||
<data name="Random_ExternalType_SecRandom" xml:space="preserve"><value>SecRandom</value></data>
|
||||
<data name="Random_ExternalType_NamePicker" xml:space="preserve"><value>NamePicker</value></data>
|
||||
<data name="Random_OnceCloseDelay" xml:space="preserve"><value>Single-draw window close delay</value></data>
|
||||
<data name="Random_OnceMaxStudents" xml:space="preserve"><value>Max students per single draw</value></data>
|
||||
<data name="Random_NewUI_Title" xml:space="preserve"><value>New roll-call UI</value></data>
|
||||
<data name="Random_NewUI_Enable" xml:space="preserve"><value>Enable new roll-call UI</value></data>
|
||||
<data name="Random_ML_AvoidRepeat" xml:space="preserve"><value>Use machine learning to avoid repeats</value></data>
|
||||
<data name="Random_ML_HistoryCount" xml:space="preserve"><value>History count for avoidance</value></data>
|
||||
<data name="Random_ML_Weight" xml:space="preserve"><value>Avoidance weight</value></data>
|
||||
<data name="Random_ML_Hint" xml:space="preserve"><value># ML analyzes recent roll-call history to avoid repeating the same students.</value></data>
|
||||
<data name="Timer_Title" xml:space="preserve"><value>Timer settings</value></data>
|
||||
<data name="Timer_UseLegacyButtons" xml:space="preserve"><value>Use legacy timer button UI</value></data>
|
||||
<data name="Timer_NewUI" xml:space="preserve"><value>New timer UI</value></data>
|
||||
<data name="Timer_EnableCountUp" xml:space="preserve"><value>Enable count-up after timeout</value></data>
|
||||
<data name="Timer_OvertimeHighlight" xml:space="preserve"><value>Highlight numbers when overtime</value></data>
|
||||
<data name="Timer_Volume" xml:space="preserve"><value>Timer alert volume</value></data>
|
||||
<data name="Timer_CustomSoundLabel" xml:space="preserve"><value>Custom alert sound:</value></data>
|
||||
<data name="Timer_SelectFile" xml:space="preserve"><value>Select file</value></data>
|
||||
<data name="Timer_Reset" xml:space="preserve"><value>Reset</value></data>
|
||||
<data name="Timer_Progressive" xml:space="preserve"><value>Progressive reminder</value></data>
|
||||
<data name="Timer_ProgressiveVolume" xml:space="preserve"><value>Progressive reminder volume</value></data>
|
||||
<data name="Timer_ProgressiveCustomLabel" xml:space="preserve"><value>Custom progressive reminder audio:</value></data>
|
||||
<data name="Timer_ProgressiveSelectFile" xml:space="preserve"><value>Select file</value></data>
|
||||
<data name="Timer_ProgressiveReset" xml:space="preserve"><value>Reset</value></data>
|
||||
<data name="About_Title" xml:space="preserve"><value>About</value></data>
|
||||
<data name="About_DeviceInfo" xml:space="preserve"><value>Device information</value></data>
|
||||
<data name="About_DeviceIdLabel" xml:space="preserve"><value>Device ID:</value></data>
|
||||
<data name="About_UsageFrequencyLabel" xml:space="preserve"><value>Usage frequency:</value></data>
|
||||
<data name="About_UpdatePriorityLabel" xml:space="preserve"><value>Update priority:</value></data>
|
||||
<data name="About_LaunchCountLabel" xml:space="preserve"><value>Launch count:</value></data>
|
||||
<data name="About_TotalUsageLabel" xml:space="preserve"><value>Total usage time:</value></data>
|
||||
<data name="About_DeviceInfo_Loading" xml:space="preserve"><value>Loading...</value></data>
|
||||
<data name="About_RefreshDeviceInfo" xml:space="preserve"><value>Refresh device info</value></data>
|
||||
<data name="About_PrivacyCheckboxPrefix" xml:space="preserve"><value>I have read and agree to the </value></data>
|
||||
<data name="About_PrivacyCheckboxSuffix" xml:space="preserve"><value> privacy statement</value></data>
|
||||
<data name="About_TelemetryLabel" xml:space="preserve"><value>Anonymous usage data upload:</value></data>
|
||||
<data name="About_Telemetry_Off" xml:space="preserve"><value>Off (no upload)</value></data>
|
||||
<data name="About_Telemetry_Basic" xml:space="preserve"><value>Upload basic data</value></data>
|
||||
<data name="About_Telemetry_Optional" xml:space="preserve"><value>Upload basic + optional data</value></data>
|
||||
<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></data>
|
||||
<data name="About_LicenseTitle" xml:space="preserve"><value>This software, ICA and Ink Canvas are all open sourced under a license</value></data>
|
||||
<data name="About_LicenseBody" xml:space="preserve"><value>The strong copyleft license requires that complete source code and modifications of the licensed work (including large works using it) be provided under the same license. Copyright and license notices must be retained. Contributors explicitly grant patent rights.</value></data>
|
||||
<data name="About_DevelopersLabel" xml:space="preserve"><value>Developers:</value></data>
|
||||
<data name="About_Dev_ICCCE" xml:space="preserve"><value>Developer of ICC CE</value></data>
|
||||
<data name="About_Dev_ICC" xml:space="preserve"><value>Developer of ICC</value></data>
|
||||
<data name="About_Dev_ICA" xml:space="preserve"><value>Developer of ICA</value></data>
|
||||
<data name="About_Dev_InkCanvas" xml:space="preserve"><value>Developer of Ink Canvas</value></data>
|
||||
<data name="About_Source_ICC" xml:space="preserve"><value>ICC repository:</value></data>
|
||||
<data name="About_Source_ICA" xml:space="preserve"><value>ICA repository:</value></data>
|
||||
<data name="About_Source_InkCanvas" xml:space="preserve"><value>Ink Canvas repository:</value></data>
|
||||
<data name="About_ThanksContributors" xml:space="preserve"><value>Thanks to the following contributors:</value></data>
|
||||
<data name="About_Copyright" xml:space="preserve"><value>© 2025-2026 CJK_mkp. All rights reserved.</value></data>
|
||||
<data name="About_OpenSourceSlogan" xml:space="preserve"><value>We love open-source forever!</value></data>
|
||||
<data name="About_VersionLabel" xml:space="preserve"><value>Version:</value></data>
|
||||
<data name="Common_Close" xml:space="preserve"><value>Close</value></data>
|
||||
<data name="Common_On" xml:space="preserve"><value>On</value></data>
|
||||
<data name="Common_Off" xml:space="preserve"><value>Off</value></data>
|
||||
<data name="Advanced_UriSchemeName" xml:space="preserve"><value>External URI scheme (icc://)</value></data>
|
||||
<data name="Advanced_NibModeBoundsWidthHeader" xml:space="preserve"><value>Nib mode BoundsWidth</value></data>
|
||||
<data name="Advanced_FingerModeBoundsWidthHeader" xml:space="preserve"><value>Finger mode BoundsWidth</value></data>
|
||||
<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></data>
|
||||
<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></data>
|
||||
<data name="Advanced_ForceFullScreen" xml:space="preserve"><value>Enable ForceFullScreen</value></data>
|
||||
<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></data>
|
||||
<data name="Advanced_DPIChangeDetection" xml:space="preserve"><value>Enable DPIChangeDetection</value></data>
|
||||
<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></data>
|
||||
<data name="Advanced_ResolutionChangeDetection" xml:space="preserve"><value>Enable ResolutionChangeDetection</value></data>
|
||||
<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></data>
|
||||
<data name="FloatingInterceptor_App_SeewoBoard3" xml:space="preserve"><value>Seewo Whiteboard 3</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoBoard5" xml:space="preserve"><value>Seewo Whiteboard 5</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoBoard5C" xml:space="preserve"><value>Seewo Whiteboard 5C</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoPinco" xml:space="preserve"><value>Seewo Pinco</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoPincoDrawing" xml:space="preserve"><value>Seewo Pinco pen</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoPPTTools" xml:space="preserve"><value>Seewo PPT Tools</value></data>
|
||||
<data name="FloatingInterceptor_App_AiClass" xml:space="preserve"><value>AiClass</value></data>
|
||||
<data name="FloatingInterceptor_App_HiteAnnotation" xml:space="preserve"><value>Hite screen writing</value></data>
|
||||
<data name="FloatingInterceptor_App_ChangYanClass" xml:space="preserve"><value>Changyan smart classroom</value></data>
|
||||
<data name="FloatingInterceptor_App_ChangYanPPT" xml:space="preserve"><value>Changyan PPT</value></data>
|
||||
<data name="FloatingInterceptor_App_IntelligentClass" xml:space="preserve"><value>Tianyu Education Cloud</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoDesktopAnnotation" xml:space="preserve"><value>Seewo desktop pen</value></data>
|
||||
<data name="FloatingInterceptor_App_SeewoDesktopSideBar" xml:space="preserve"><value>Seewo desktop sidebar</value></data>
|
||||
<data name="Board_MultiTouchWriting" xml:space="preserve"><value>Multi-touch writing</value></data>
|
||||
<data name="Board_TwoFingerMove" xml:space="preserve"><value>Two-finger move</value></data>
|
||||
<data name="Board_TwoFingerZoom" xml:space="preserve"><value>Two-finger zoom</value></data>
|
||||
<data name="Board_TwoFingerRotate" xml:space="preserve"><value>Two-finger rotate</value></data>
|
||||
<data name="Board_Background" xml:space="preserve"><value>Background</value></data>
|
||||
<data name="Board_Select" xml:space="preserve"><value>Select</value></data>
|
||||
<data name="Board_Pen" xml:space="preserve"><value>Pen</value></data>
|
||||
<data name="Board_Highlighter" xml:space="preserve"><value>Highlighter</value></data>
|
||||
<data name="Board_Eraser" xml:space="preserve"><value>Eraser</value></data>
|
||||
<data name="Board_EraserOptions" xml:space="preserve"><value>Eraser options</value></data>
|
||||
<data name="Board_Size" xml:space="preserve"><value>Size</value></data>
|
||||
<data name="Board_EraserShape" xml:space="preserve"><value>Eraser shape</value></data>
|
||||
<data name="Board_EraserShape_Circle" xml:space="preserve"><value>Circle</value></data>
|
||||
<data name="Board_EraserShape_Blackboard" xml:space="preserve"><value>Blackboard</value></data>
|
||||
<data name="Board_ClearInk" xml:space="preserve"><value>Clear ink</value></data>
|
||||
<data name="Board_ClearInkAndHistory" xml:space="preserve"><value>Clear ink & history</value></data>
|
||||
<data name="Board_StrokeEraser" xml:space="preserve"><value>Stroke eraser</value></data>
|
||||
<data name="Board_Shape" xml:space="preserve"><value>Shapes</value></data>
|
||||
<data name="Board_ShapeHintLongPress" xml:space="preserve"><value>(Long-press in first row to keep selected)</value></data>
|
||||
<data name="Board_AutoHide" xml:space="preserve"><value>Auto-hide</value></data>
|
||||
<data name="Board_InsertImage" xml:space="preserve"><value>Insert image</value></data>
|
||||
<data name="Board_SelectImage" xml:space="preserve"><value>Select image</value></data>
|
||||
<data name="Board_Screenshot" xml:space="preserve"><value>Screenshot</value></data>
|
||||
<data name="Board_Undo" xml:space="preserve"><value>Undo</value></data>
|
||||
<data name="Board_Redo" xml:space="preserve"><value>Redo</value></data>
|
||||
<data name="Board_Tools" xml:space="preserve"><value>Tools</value></data>
|
||||
<data name="Board_Exit" xml:space="preserve"><value>Exit</value></data>
|
||||
<data name="Board_NewPage" xml:space="preserve"><value>New page</value></data>
|
||||
<data name="Board_PreviousPage" xml:space="preserve"><value>Previous</value></data>
|
||||
<data name="Board_NextPage" xml:space="preserve"><value>Next</value></data>
|
||||
<data name="Board_Page" xml:space="preserve"><value>Page</value></data>
|
||||
<data name="Board_DeleteThisPage" xml:space="preserve"><value>Delete this page</value></data>
|
||||
<data name="Notification_TestText" xml:space="preserve"><value>Test text</value></data>
|
||||
<data name="OldUI_Exit" xml:space="preserve"><value>Exit</value></data>
|
||||
<data name="OldUI_Thickness" xml:space="preserve"><value>Thickness</value></data>
|
||||
<data name="OldUI_Dark" xml:space="preserve"><value>Dark</value></data>
|
||||
<data name="OldUI_Background" xml:space="preserve"><value>Background</value></data>
|
||||
<data name="OldUI_HideCanvas" xml:space="preserve"><value>Hide
canvas</value></data>
|
||||
<data name="OldUI_Check" xml:space="preserve"><value>Check</value></data>
|
||||
<data name="OldUI_SlideshowFromStart" xml:space="preserve"><value>From start
slideshow</value></data>
|
||||
<data name="OldUI_SlideshowEnd" xml:space="preserve"><value>End
slideshow</value></data>
|
||||
<data name="OldUI_SingleFingerDrag" xml:space="preserve"><value>One finger
drag</value></data>
|
||||
<data name="OldUI_Restore" xml:space="preserve"><value>Restore</value></data>
|
||||
<data name="OldUI_ClearAndHide" xml:space="preserve"><value>Clear
&
Hide</value></data>
|
||||
<data name="FloatingBar_Mouse" xml:space="preserve"><value>Mouse</value></data>
|
||||
<data name="FloatingBar_Annotate" xml:space="preserve"><value>Annotate</value></data>
|
||||
<data name="FloatingBar_Clear" xml:space="preserve"><value>Clear</value></data>
|
||||
<data name="Booth_Title" xml:space="preserve"><value>Visual presenter</value></data>
|
||||
<data name="Booth_CapturedPhotos" xml:space="preserve"><value>Captured photos</value></data>
|
||||
<data name="Booth_CameraDevices" xml:space="preserve"><value>Camera devices</value></data>
|
||||
<data name="Booth_Present" xml:space="preserve"><value>Present</value></data>
|
||||
<data name="Booth_Correction" xml:space="preserve"><value>Correct</value></data>
|
||||
<data name="Booth_Capture" xml:space="preserve"><value>Capture</value></data>
|
||||
<data name="Booth_Rotate" xml:space="preserve"><value>Rotate</value></data>
|
||||
<data name="Theme_LanguageLabel" xml:space="preserve"><value>UI language</value></data>
|
||||
<data name="Theme_Language_System" xml:space="preserve"><value>Follow system</value></data>
|
||||
<data name="Theme_Language_ChineseSimplified" xml:space="preserve"><value>Chinese (Simplified)</value></data>
|
||||
<data name="Theme_Language_English" xml:space="preserve"><value>English</value></data>
|
||||
<data name="Theme_Language_RestartHint" xml:space="preserve"><value>You need to restart the app for language changes to fully take effect.</value></data>
|
||||
<data name="FloatingBar_AreaEraser" xml:space="preserve"><value>Area eraser</value></data>
|
||||
<data name="FloatingBar_StrokeEraser" xml:space="preserve"><value>Stroke eraser</value></data>
|
||||
<data name="FloatingBar_LassoSelect" xml:space="preserve"><value>Lasso</value></data>
|
||||
<data name="FloatingBar_Geometry" xml:space="preserve"><value>Geometry</value></data>
|
||||
<data name="FloatingBar_ClearAndMouse" xml:space="preserve"><value>Clear & cursor</value></data>
|
||||
<data name="FloatingBar_Whiteboard" xml:space="preserve"><value>Board</value></data>
|
||||
<data name="FloatingBar_Hide" xml:space="preserve"><value>Hide</value></data>
|
||||
<data name="Geometry_Title" xml:space="preserve"><value>Geometry drawing</value></data>
|
||||
<data name="Geometry_DrawLine" xml:space="preserve"><value>Line</value></data>
|
||||
<data name="Geometry_DrawDashedLine" xml:space="preserve"><value>Dashed line</value></data>
|
||||
<data name="Geometry_DrawDottedLine" xml:space="preserve"><value>Dotted line</value></data>
|
||||
<data name="Geometry_DrawArrow" xml:space="preserve"><value>Arrow</value></data>
|
||||
<data name="Geometry_DrawParallelLines" xml:space="preserve"><value>4 parallel lines</value></data>
|
||||
<data name="Geometry_DrawCenteredSquare" xml:space="preserve"><value>Centered square</value></data>
|
||||
<data name="Geometry_DrawCenteredCircle" xml:space="preserve"><value>Centered circle</value></data>
|
||||
<data name="Geometry_DrawCenteredDashedCircle" xml:space="preserve"><value>Centered dashed circle</value></data>
|
||||
<data name="Geometry_DrawCenteredEllipse" xml:space="preserve"><value>Centered ellipse</value></data>
|
||||
<data name="Geometry_DrawCuboid" xml:space="preserve"><value>Cuboid</value></data>
|
||||
<data name="Geometry_DrawSquare" xml:space="preserve"><value>Square</value></data>
|
||||
<data name="Geometry_DrawCylinder" xml:space="preserve"><value>Cylinder</value></data>
|
||||
<data name="Geometry_DrawCone" xml:space="preserve"><value>Cone</value></data>
|
||||
<data name="FloatingBar_GestureButton" xml:space="preserve"><value>Gesture</value></data>
|
||||
<data name="FloatingBar_GesturePanelTitle" xml:space="preserve"><value>Gesture options</value></data>
|
||||
<data name="FloatingBar_Gesture_MultiTouchWriting" xml:space="preserve"><value>Multi-touch writing</value></data>
|
||||
<data name="FloatingBar_Gesture_TwoFingerMove" xml:space="preserve"><value>Two-finger move</value></data>
|
||||
<data name="FloatingBar_Gesture_TwoFingerZoom" xml:space="preserve"><value>Two-finger zoom</value></data>
|
||||
<data name="FloatingBar_Gesture_TwoFingerRotate" xml:space="preserve"><value>Two-finger rotate</value></data>
|
||||
<data name="Board_Gesture" xml:space="preserve"><value>Gesture</value></data>
|
||||
<data name="Board_GestureOptions" xml:space="preserve"><value>Gesture options</value></data>
|
||||
</root>
|
||||
+2509
-652
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,8 @@ namespace Ink_Canvas
|
||||
public bool RequirePasswordOnEnterSettings { get; set; } = false;
|
||||
[JsonProperty("requirePasswordOnResetConfig")]
|
||||
public bool RequirePasswordOnResetConfig { get; set; } = false;
|
||||
[JsonProperty("requirePasswordOnModifyOrClearNameList")]
|
||||
public bool RequirePasswordOnModifyOrClearNameList { get; set; } = false;
|
||||
[JsonProperty("enableProcessProtection")]
|
||||
public bool EnableProcessProtection { get; set; } = true;
|
||||
}
|
||||
@@ -67,6 +69,7 @@ namespace Ink_Canvas
|
||||
public double InkAlpha { get; set; } = 255;
|
||||
[JsonProperty("isShowCursor")]
|
||||
public bool IsShowCursor { get; set; }
|
||||
/// <summary>笔锋存储值:0 基于点集,1 基于速率,2 关闭,3 实时笔锋(速度与压感混合)。界面下拉顺序为实时笔锋、点集、速率、关闭。</summary>
|
||||
[JsonProperty("inkStyle")]
|
||||
public int InkStyle { get; set; }
|
||||
[JsonProperty("eraserSize")]
|
||||
@@ -143,6 +146,14 @@ namespace Ink_Canvas
|
||||
public bool EnableEraserAutoSwitchBack { get; set; } = false;
|
||||
[JsonProperty("eraserAutoSwitchBackDelaySeconds")]
|
||||
public int EraserAutoSwitchBackDelaySeconds { get; set; } = 10; // 默认10秒
|
||||
[JsonProperty("velocityBrushTipMix")]
|
||||
public double VelocityBrushTipMix { get; set; } = 0.45;
|
||||
[JsonProperty("enableVelocityBrushTip")]
|
||||
public bool EnableVelocityBrushTip { get; set; }
|
||||
|
||||
/// <summary>为 true 时,白板工具栏「展台」按钮启动希沃视频展台(sweclauncher),否则使用内置展台。</summary>
|
||||
[JsonProperty("launchSeewoVideoShowcaseForWhiteboardBooth")]
|
||||
public bool LaunchSeewoVideoShowcaseForWhiteboardBooth { get; set; } = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -181,6 +192,15 @@ namespace Ink_Canvas
|
||||
Beta
|
||||
}
|
||||
|
||||
/// <summary>自动更新要下载的安装包架构(与当前运行进程的位数无关)。默认 32 位包;64 位包对应发布物 ZIP 文件名在 .zip 前增加 -x64。</summary>
|
||||
public enum UpdatePackageArchitecture
|
||||
{
|
||||
/// <summary>32 位包,例如 InkCanvasForClass.CE.1.7.0.0.zip</summary>
|
||||
X86 = 0,
|
||||
/// <summary>64 位包,例如 InkCanvasForClass.CE.1.7.0.0-x64.zip</summary>
|
||||
X64 = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 遥测上传等级
|
||||
/// </summary>
|
||||
@@ -212,6 +232,8 @@ namespace Ink_Canvas
|
||||
public string AutoUpdateWithSilenceEndTime { get; set; } = "22:00";
|
||||
[JsonProperty("updateChannel")]
|
||||
public UpdateChannel UpdateChannel { get; set; } = UpdateChannel.Release;
|
||||
[JsonProperty("updatePackageArchitecture")]
|
||||
public UpdatePackageArchitecture UpdatePackageArchitecture { get; set; } = UpdatePackageArchitecture.X86;
|
||||
[JsonProperty("skippedVersion")]
|
||||
public string SkippedVersion { get; set; } = "";
|
||||
[JsonProperty("autoUpdatePauseUntilDate")]
|
||||
@@ -276,8 +298,8 @@ namespace Ink_Canvas
|
||||
public bool IsShowQuickPanel { get; set; } = true;
|
||||
[JsonProperty("chickenSoupSource")]
|
||||
public int ChickenSoupSource { get; set; } = 1;
|
||||
[JsonProperty("hitokotoCategories")]
|
||||
public List<string> HitokotoCategories { get; set; } = new List<string> { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l" }; // 默认全选所有分类
|
||||
[JsonProperty("hitokotoCategories", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<string> HitokotoCategories { get; set; }
|
||||
[JsonProperty("isShowModeFingerToggleSwitch")]
|
||||
public bool IsShowModeFingerToggleSwitch { get; set; } = true;
|
||||
[JsonProperty("theme")]
|
||||
@@ -729,6 +751,12 @@ namespace Ink_Canvas
|
||||
public double LineStraightenSensitivity { get; set; } = 0.20;
|
||||
[JsonProperty("lineNormalizationThreshold")]
|
||||
public double LineNormalizationThreshold { get; set; } = 0.5;
|
||||
[JsonProperty("shapeRecognitionEngine")]
|
||||
public int ShapeRecognitionEngine { get; set; }
|
||||
[JsonProperty("enableWinRtHandwritingStrokeBeautify")]
|
||||
public bool EnableWinRtHandwritingStrokeBeautify { get; set; }
|
||||
[JsonProperty("handwritingCorrectionFontFamily")]
|
||||
public string HandwritingCorrectionFontFamily { get; set; } = "Ink Free,KaiTi,Segoe Script";
|
||||
}
|
||||
|
||||
public class RandSettings
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
xmlns:controls="clr-namespace:Ink_Canvas.Windows.Controls"
|
||||
mc:Ignorable="d"
|
||||
WindowStyle="None"
|
||||
Title="云存储管理" Height="600" Width="900"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent">
|
||||
Topmost="True"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||
ui:WindowHelper.SystemBackdropType="Mica"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:TitleBar.Height="48">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -76,64 +79,41 @@
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Border Background="{StaticResource WindowBackground}"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="15"
|
||||
Margin="10"
|
||||
x:Name="MainBorder"
|
||||
ClipToBounds="True"
|
||||
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<controls:WinUI3CloseButton x:Name="BtnClose"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Width="46" Height="32"
|
||||
Margin="0,0,0,0" Cursor="Hand" Click="BtnClose_Click"
|
||||
Content="✕"/>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 自定义标题栏 -->
|
||||
<Border x:Name="Border_TitleBarRoot"
|
||||
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||
VerticalAlignment="Center" Margin="12,0,0,0" FontSize="12" FontWeight="SemiBold"/>
|
||||
|
||||
<!--Right Inset-->
|
||||
<Rectangle Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}"
|
||||
Grid.Column="2"/>
|
||||
|
||||
<!--Right Buttons-->
|
||||
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||
Orientation="Horizontal" Grid.Column="1" Spacing="5">
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Border Grid.Row="0"
|
||||
Height="50"
|
||||
Background="{StaticResource WindowBackground}"
|
||||
CornerRadius="15,15,0,0"
|
||||
Margin="0,0,46,0">
|
||||
<Grid x:Name="TitleBar" VerticalAlignment="Stretch">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Margin="22,0,0,0">
|
||||
<!-- 设置图标 -->
|
||||
<Path Data="M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5A3.5 3.5 0 0 1 15.5 12A3.5 3.5 0 0 1 12 15.5M19.43 12.97C19.47 12.65 19.5 12.33 19.5 12C19.5 11.67 19.47 11.34 19.43 11.03L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.54 5.05 19.27 4.96 19.05 5.05L16.56 6.05C16.04 5.65 15.5 5.32 14.87 5.07L14.5 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.5 2.42L9.13 5.07C8.5 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.73 4.96 4.46 5.05 4.35 5.27L2.35 8.73C2.23 8.95 2.27 9.22 2.46 9.37L4.57 11.03C4.53 11.34 4.5 11.67 4.5 12C4.5 12.33 4.53 12.65 4.57 12.97L2.46 14.63C2.27 14.78 2.23 15.05 2.35 15.27L4.35 18.73C4.46 18.95 4.73 19.03 4.95 18.95L7.44 17.95C7.96 18.34 8.5 18.68 9.13 18.93L9.5 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.5 21.58L14.87 18.93C15.5 18.67 16.04 18.34 16.56 17.95L19.05 18.95C19.27 19.03 19.54 18.95 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.97Z"
|
||||
Stroke="{StaticResource TitleForeground}"
|
||||
StrokeThickness="1.5"
|
||||
StrokeLineJoin="Round"
|
||||
Fill="Transparent"
|
||||
Width="24" Height="24"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<!-- 标题文字 -->
|
||||
<TextBlock Text="云储存管理"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
Foreground="{StaticResource TitleForeground}"
|
||||
x:Name="TitleText"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{StaticResource WindowBackground}"
|
||||
Padding="20,10,20,20"
|
||||
CornerRadius="0,0,15,15">
|
||||
<TabControl Background="{StaticResource WindowBackground}" BorderThickness="0" SelectionChanged="TabControl_SelectionChanged">
|
||||
<!-- 主内容区 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{StaticResource WindowBackground}"
|
||||
Padding="20,10,20,20">
|
||||
<TabControl Background="{StaticResource WindowBackground}" BorderThickness="0" SelectionChanged="TabControl_SelectionChanged">
|
||||
<!-- 样式 -->
|
||||
<TabControl.Resources>
|
||||
<Style TargetType="TabItem">
|
||||
@@ -192,7 +172,7 @@
|
||||
|
||||
<!-- 通用设置标签页 -->
|
||||
<TabItem Header="通用设置" Margin="0,0,0,0">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" IsManipulationEnabled="True" PanningMode="VerticalOnly">
|
||||
<ikw:SimpleStackPanel Spacing="16">
|
||||
<!-- 内容区域 -->
|
||||
<Border BorderThickness="1"
|
||||
@@ -360,7 +340,7 @@
|
||||
|
||||
<!-- Dlass 设置标签页 -->
|
||||
<TabItem Header="Dlass" Tag="DlassTab" Margin="0,0,0,0">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" IsManipulationEnabled="True" PanningMode="VerticalOnly">
|
||||
<ikw:SimpleStackPanel Spacing="16">
|
||||
<!-- 内容区域 -->
|
||||
<Border BorderThickness="1"
|
||||
@@ -684,7 +664,7 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" IsManipulationEnabled="True" PanningMode="VerticalOnly">
|
||||
<ikw:SimpleStackPanel Spacing="16">
|
||||
<!-- 内容区域 -->
|
||||
<Border BorderThickness="1"
|
||||
@@ -908,10 +888,8 @@
|
||||
</ikw:SimpleStackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</TabControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Window x:Class="Ink_Canvas.CountdownTimerWindow"
|
||||
<Window x:Class="Ink_Canvas.CountdownTimerWindow"
|
||||
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"
|
||||
@@ -116,7 +116,7 @@
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Symbol="Save" Foreground="White"/>
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Save}" Foreground="White"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -181,12 +181,12 @@
|
||||
<Grid>
|
||||
<Border x:Name="BtnStart" MouseUp="BtnStart_MouseUp" Background="{DynamicResource TimerWindowPrimaryButtonBackground}" Height="20" Width="20" CornerRadius="100">
|
||||
<Viewbox Margin="5">
|
||||
<ui:SymbolIcon Name="SymbolIconStart" Symbol="Play" Foreground="White"/>
|
||||
<ui:FontIcon Name="FontIconStart" Icon="{x:Static ui:SegoeFluentIcons.Play}" Foreground="White"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
<Border x:Name="BtnStartCover" Visibility="Collapsed" Background="#BFBFBF" Height="20" Width="20" CornerRadius="100">
|
||||
<Viewbox Margin="5">
|
||||
<ui:SymbolIcon Symbol="{Binding ElementName=SymbolIconStart, Path=Symbol}" Foreground="White"/>
|
||||
<ui:FontIcon Icon="{Binding ElementName=FontIconStart, Path=Icon}" Foreground="White"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -196,12 +196,12 @@
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.15" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Symbol="Refresh" Foreground="{DynamicResource TimerWindowButtonForeground}"/>
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Refresh}" Foreground="{DynamicResource TimerWindowButtonForeground}"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
<Border x:Name="BtnResetCover" Background="#F3F5F9" Height="20" Width="20" CornerRadius="100">
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Symbol="Refresh" Foreground="#9D9D9E"/>
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Refresh}" Foreground="#9D9D9E"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</Grid>
|
||||
@@ -217,7 +217,7 @@
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Name="SymbolIconMinimal" Symbol="HideBcc" Foreground="{DynamicResource TimerWindowButtonForeground}"/>
|
||||
<ui:FontIcon Name="FontIconMinimal" Icon="{x:Static ui:SegoeFluentIcons.HideBcc}" Foreground="{DynamicResource TimerWindowButtonForeground}"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
<Border x:Name="BtnFullscreen" MouseUp="BtnFullscreen_MouseUp" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
@@ -227,7 +227,7 @@
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Name="SymbolIconFullscreen" Symbol="FullScreen" Foreground="{DynamicResource TimerWindowButtonForeground}"/>
|
||||
<ui:FontIcon Name="FontIconFullscreen" Icon="{x:Static ui:SegoeFluentIcons.FullScreen}" Foreground="{DynamicResource TimerWindowButtonForeground}"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
<Border x:Name="BtnClose" MouseUp="BtnClose_MouseUp" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
@@ -237,7 +237,7 @@
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Symbol="Clear" Foreground="White"/>
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Clear}" Foreground="White"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Common.IconKeys;
|
||||
using System;
|
||||
using System.Media;
|
||||
using System.Timers;
|
||||
@@ -75,7 +76,7 @@ namespace Ink_Canvas
|
||||
TextBlockSecond.Text = "00";
|
||||
timer.Stop();
|
||||
isTimerRunning = false;
|
||||
SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Play;
|
||||
FontIconStart.Icon = SegoeFluentIcons.Play;
|
||||
BtnStartCover.Visibility = Visibility.Visible;
|
||||
var textForeground = Application.Current.FindResource("TimerWindowTextForeground") as SolidColorBrush;
|
||||
if (textForeground != null)
|
||||
@@ -264,12 +265,12 @@ namespace Ink_Canvas
|
||||
if (WindowState == WindowState.Normal)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.BackToWindow;
|
||||
FontIconFullscreen.Icon = SegoeFluentIcons.BackToWindow;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.FullScreen;
|
||||
FontIconFullscreen.Icon = SegoeFluentIcons.FullScreen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +306,7 @@ namespace Ink_Canvas
|
||||
TextBlockHour.Foreground = textForeground3;
|
||||
else
|
||||
TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F"));
|
||||
SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Play;
|
||||
FontIconStart.Icon = SegoeFluentIcons.Play;
|
||||
isTimerRunning = false;
|
||||
timer.Stop();
|
||||
isPaused = false;
|
||||
@@ -362,7 +363,7 @@ namespace Ink_Canvas
|
||||
TextBlockHour.Foreground = textForeground1;
|
||||
else
|
||||
TextBlockHour.Foreground = Brushes.Black;
|
||||
SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Pause;
|
||||
FontIconStart.Icon = SegoeFluentIcons.Pause;
|
||||
isPaused = false;
|
||||
timer.Start();
|
||||
UpdateStopTime();
|
||||
@@ -378,7 +379,7 @@ namespace Ink_Canvas
|
||||
TextBlockHour.Foreground = textForeground3;
|
||||
else
|
||||
TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F"));
|
||||
SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Play;
|
||||
FontIconStart.Icon = SegoeFluentIcons.Play;
|
||||
BorderStopTime.Visibility = Visibility.Collapsed;
|
||||
isPaused = true;
|
||||
timer.Stop();
|
||||
@@ -394,7 +395,7 @@ namespace Ink_Canvas
|
||||
TextBlockHour.Foreground = textForeground2;
|
||||
else
|
||||
TextBlockHour.Foreground = Brushes.Black;
|
||||
SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Pause;
|
||||
FontIconStart.Icon = SegoeFluentIcons.Pause;
|
||||
BtnResetCover.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (totalSeconds <= 10)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using MdXaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -500,8 +498,7 @@ namespace Ink_Canvas
|
||||
if (string.IsNullOrEmpty(downloadUrl))
|
||||
{
|
||||
// 自动更新场景下,downloadUrl为null,直接用主下载目录
|
||||
string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
downloadUrl = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip");
|
||||
downloadUrl = AutoUpdateHelper.GetLocalUpdateZipFilePath(version);
|
||||
}
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始安装版本: {version}");
|
||||
AutoUpdateHelper.InstallNewVersionApp(version, true);
|
||||
|
||||
@@ -7,122 +7,18 @@
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml"
|
||||
mc:Ignorable="d"
|
||||
ui:WindowHelper.UseModernWindowStyle="False"
|
||||
Title="历史版本回滚" Height="650" Width="900" ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="True"
|
||||
Background="{DynamicResource SettingsPageBackground}" MinHeight="550" MinWidth="800"
|
||||
SnapsToDevicePixels="True"
|
||||
TextOptions.TextRenderingMode="ClearType"
|
||||
TextOptions.TextFormattingMode="Display"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||
ui:WindowHelper.SystemBackdropType="Mica"
|
||||
Title="历史版本回滚" Height="650" Width="900" ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterScreen" WindowStyle="None"
|
||||
Background="Transparent" MinHeight="550" MinWidth="800">
|
||||
<Window.Template>
|
||||
<ControlTemplate TargetType="Window">
|
||||
<Border Background="{DynamicResource SettingsPageBackground}"
|
||||
CornerRadius="8"
|
||||
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
|
||||
BorderThickness="1">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="20" ShadowDepth="0" Opacity="0.1"/>
|
||||
</Border.Effect>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<!-- 标题栏 -->
|
||||
<Border Background="{DynamicResource SettingsPageBackground}" Height="48" VerticalAlignment="Top">
|
||||
<Grid>
|
||||
<TextBlock Text="{TemplateBinding Title}"
|
||||
FontSize="14" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource SettingsPageForeground}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="16,0,0,0"/>
|
||||
<!-- 最小化按钮 -->
|
||||
<Button Name="MinimizeButton"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,8,40,0"
|
||||
Width="32" Height="32"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="MinimizeButton_Click"
|
||||
Cursor="Hand"
|
||||
ToolTip="最小化">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}">
|
||||
<TextBlock Text="−" FontSize="14"
|
||||
Foreground="{DynamicResource SettingsPageForeground}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="#FF2563eb" Opacity="0.1"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="#FF2563eb" Opacity="0.2"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<Button Name="CloseButton"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,8,8,0"
|
||||
Width="32" Height="32"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Click="CloseButton_Click"
|
||||
Cursor="Hand"
|
||||
ToolTip="关闭">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}">
|
||||
<TextBlock Text="×" FontSize="14"
|
||||
Foreground="{DynamicResource SettingsPageForeground}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="#ef4444" Opacity="0.1"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="#ef4444" Opacity="0.2"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- 内容区域 -->
|
||||
<ContentPresenter Margin="0,48,0,0"/>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Window.Template>
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:TitleBar.Height="48">
|
||||
<Window.Resources>
|
||||
<!-- 主题相关颜色资源 -->
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="#FF2563eb"/>
|
||||
@@ -130,26 +26,60 @@
|
||||
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#FF6b7280"/>
|
||||
</Window.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<Grid Background="Transparent">
|
||||
<!-- 主内容区域 -->
|
||||
<ikw:SimpleStackPanel VerticalAlignment="Stretch" Spacing="0" Margin="20">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 自定义标题栏 -->
|
||||
<Border x:Name="Border_TitleBarRoot"
|
||||
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||
VerticalAlignment="Center" Margin="12,0,0,0" FontSize="12" FontWeight="SemiBold"/>
|
||||
|
||||
<!--Right Inset-->
|
||||
<Rectangle Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}"
|
||||
Grid.Column="2"/>
|
||||
|
||||
<!--Right Buttons-->
|
||||
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||
Orientation="Horizontal" Grid.Column="1" Spacing="5">
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<Grid Grid.Row="1" Background="{DynamicResource SettingsPageBackground}" Margin="20,20,20,20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题区域 -->
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Spacing="12" Margin="0,0,0,16">
|
||||
<ikw:SimpleStackPanel Grid.Row="0" Orientation="Horizontal" Spacing="12" Margin="0,0,0,16">
|
||||
<Border Background="{DynamicResource PrimaryBrush}" CornerRadius="16" Padding="12" Margin="0,0,0,0">
|
||||
<ui:SymbolIcon Symbol="Undo" FontSize="20" Foreground="White"
|
||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Undo}" FontSize="20" Foreground="White"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ikw:SimpleStackPanel VerticalAlignment="Center" Spacing="4">
|
||||
<TextBlock Text="历史版本回滚" FontSize="22" FontWeight="Bold"
|
||||
<TextBlock Text="选择要回滚到的历史版本" FontSize="22" FontWeight="Bold"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}"/>
|
||||
<TextBlock Text="选择要回滚到的历史版本" FontSize="14"
|
||||
Foreground="{DynamicResource TextSecondaryBrush}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
|
||||
<!-- 版本选择卡片 -->
|
||||
<Border Background="{DynamicResource SettingsPageBackground}"
|
||||
<Border Grid.Row="1"
|
||||
Background="{DynamicResource SettingsPageBackground}"
|
||||
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
@@ -169,7 +99,8 @@
|
||||
</Border>
|
||||
|
||||
<!-- 发布说明卡片 -->
|
||||
<Border Background="{DynamicResource SettingsPageBackground}"
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource SettingsPageBackground}"
|
||||
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
@@ -178,30 +109,42 @@
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="8" ShadowDepth="0" Opacity="0.05"/>
|
||||
</Border.Effect>
|
||||
<ikw:SimpleStackPanel Spacing="16">
|
||||
<TextBlock Text="版本更新说明" FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}"/>
|
||||
<Border Background="{DynamicResource SettingsPageBackground}"
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="版本更新说明" FontSize="16" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}"
|
||||
Margin="0,0,0,16"/>
|
||||
<Border Grid.Row="1"
|
||||
Background="{DynamicResource SettingsPageBackground}"
|
||||
BorderBrush="{DynamicResource SettingsPageBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
MinHeight="150" MaxHeight="250">
|
||||
CornerRadius="8">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="4" ShadowDepth="0" Opacity="0.03"/>
|
||||
</Border.Effect>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
<ScrollViewer x:Name="InnerReleaseNotesScrollViewer"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
Padding="16">
|
||||
Padding="16"
|
||||
PanningMode="VerticalOnly"
|
||||
PanningRatio="1.0"
|
||||
IsManipulationEnabled="True">
|
||||
<mdxam:MarkdownScrollViewer x:Name="ReleaseNotesViewer"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}"
|
||||
MarkdownStyleName="GithubLike"/>
|
||||
MarkdownStyleName="GithubLike"
|
||||
IsHitTestVisible="False"
|
||||
IsManipulationEnabled="False"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<ikw:SimpleStackPanel Spacing="16" Margin="0,0,0,16">
|
||||
<ikw:SimpleStackPanel Grid.Row="3" Spacing="16" Margin="0,0,0,0">
|
||||
<Button x:Name="RollbackButton"
|
||||
Content="回滚到此版本"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -234,7 +177,6 @@
|
||||
</ikw:SimpleStackPanel>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -35,19 +35,37 @@ namespace Ink_Canvas
|
||||
InitializeComponent();
|
||||
this.channel = channel;
|
||||
|
||||
// 设置窗口置顶
|
||||
this.Topmost = true;
|
||||
|
||||
// 应用当前主题
|
||||
ApplyCurrentTheme();
|
||||
|
||||
LoadVersions();
|
||||
// 隐藏主窗口
|
||||
HideMainWindow();
|
||||
|
||||
// 添加窗口拖动功能
|
||||
MouseDown += (sender, e) =>
|
||||
LoadVersions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏主窗口
|
||||
/// </summary>
|
||||
private void HideMainWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
DragMove();
|
||||
// 隐藏主窗口
|
||||
mainWindow.Visibility = Visibility.Hidden;
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"隐藏主窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -223,16 +241,16 @@ namespace Ink_Canvas
|
||||
Foreground = (Brush)Resources["TextPrimaryBrush"]
|
||||
};
|
||||
|
||||
var daysComboBox = new ComboBox
|
||||
var daysComboBox = new System.Windows.Controls.ComboBox
|
||||
{
|
||||
Width = 200,
|
||||
Height = 36,
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
for (int i = 0; i <= 7; i++)
|
||||
{
|
||||
daysComboBox.Items.Add(new ComboBoxItem
|
||||
daysComboBox.Items.Add(new System.Windows.Controls.ComboBoxItem
|
||||
{
|
||||
Content = $"{i} 天",
|
||||
Tag = i
|
||||
@@ -250,7 +268,7 @@ namespace Ink_Canvas
|
||||
if (dialogResult == ContentDialogResult.Primary)
|
||||
{
|
||||
int days = 1;
|
||||
if (daysComboBox.SelectedItem is ComboBoxItem selectedItemCombo &&
|
||||
if (daysComboBox.SelectedItem is System.Windows.Controls.ComboBoxItem selectedItemCombo &&
|
||||
selectedItemCombo.Tag != null &&
|
||||
int.TryParse(selectedItemCombo.Tag.ToString(), out int selectedDays))
|
||||
{
|
||||
@@ -323,20 +341,30 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowState = WindowState.Minimized;
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
downloadCts?.Cancel();
|
||||
|
||||
try
|
||||
{
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
// 重新显示主窗口
|
||||
mainWindow.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示主窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,70 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.HotkeyItem"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="60" d:DesignWidth="600">
|
||||
|
||||
<Border Background="White"
|
||||
BorderBrush="#E0E0E0"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5"
|
||||
Margin="0,2">
|
||||
<Grid Margin="15,10">
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="60"
|
||||
d:DesignWidth="600">
|
||||
|
||||
<Border Background="#2d2d30"
|
||||
BorderBrush="#3f3f46"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6"
|
||||
Margin="0,2"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid Margin="14,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左侧信息 -->
|
||||
<ikw:SimpleStackPanel Grid.Column="0" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TitleTextBlock"
|
||||
Text="快捷键标题"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#333333"/>
|
||||
<TextBlock x:Name="DescriptionTextBlock"
|
||||
Text="快捷键描述"
|
||||
FontSize="12"
|
||||
Foreground="#666666"
|
||||
Margin="0,2,0,0"/>
|
||||
<TextBlock x:Name="TitleTextBlock"
|
||||
Text="快捷键标题"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#fafafa" />
|
||||
<TextBlock x:Name="DescriptionTextBlock"
|
||||
Text="快捷键描述"
|
||||
FontSize="12"
|
||||
Foreground="#a1a1aa"
|
||||
Margin="0,2,0,0"
|
||||
TextWrapping="Wrap" />
|
||||
</ikw:SimpleStackPanel>
|
||||
|
||||
<!-- 当前快捷键显示 -->
|
||||
<Border Grid.Column="1"
|
||||
Background="#F5F5F5"
|
||||
BorderBrush="#D0D0D0"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3"
|
||||
Margin="10,0,0,0"
|
||||
Padding="8,4">
|
||||
<TextBlock x:Name="CurrentHotkeyTextBlock"
|
||||
Text="未设置"
|
||||
FontSize="12"
|
||||
Foreground="#666666"
|
||||
MinWidth="80"
|
||||
TextAlignment="Center"/>
|
||||
<Border Grid.Column="1"
|
||||
Background="#3f3f46"
|
||||
BorderBrush="#71717a"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Margin="10,0,0,0"
|
||||
Padding="10,6"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock x:Name="CurrentHotkeyTextBlock"
|
||||
Text="未设置"
|
||||
FontSize="12"
|
||||
FontFamily="Consolas"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="#fafafa"
|
||||
MinWidth="88"
|
||||
TextAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<!-- 设置按钮 -->
|
||||
<Button x:Name="BtnSetHotkey"
|
||||
Grid.Column="2"
|
||||
Content="设置"
|
||||
Width="60"
|
||||
Height="28"
|
||||
Background="#0066BF"
|
||||
Foreground="White"
|
||||
FontSize="12"
|
||||
<Button x:Name="BtnSetHotkey"
|
||||
Grid.Column="2"
|
||||
Content="设置"
|
||||
Width="72"
|
||||
Height="30"
|
||||
FontSize="12"
|
||||
Margin="10,0,0,0"
|
||||
Click="BtnSetHotkey_Click"/>
|
||||
VerticalAlignment="Center"
|
||||
Style="{DynamicResource PrimaryButtonStyle}"
|
||||
Padding="12,4"
|
||||
Click="BtnSetHotkey_Click" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
|
||||
@@ -11,6 +11,16 @@ namespace Ink_Canvas.Windows
|
||||
/// </summary>
|
||||
public partial class HotkeyItem : UserControl
|
||||
{
|
||||
private static readonly SolidColorBrush HotkeyValueForeground = CreateFrozenBrush(0xFA, 0xFA, 0xFA);
|
||||
private static readonly SolidColorBrush HotkeyPlaceholderForeground = CreateFrozenBrush(0xA1, 0xA1, 0xAA);
|
||||
|
||||
private static SolidColorBrush CreateFrozenBrush(byte r, byte g, byte b)
|
||||
{
|
||||
var brush = new SolidColorBrush(Color.FromRgb(r, g, b));
|
||||
brush.Freeze();
|
||||
return brush;
|
||||
}
|
||||
|
||||
#region Events
|
||||
/// <summary>
|
||||
/// 快捷键变更事件
|
||||
@@ -80,13 +90,13 @@ namespace Ink_Canvas.Windows
|
||||
if (_currentKey == Key.None)
|
||||
{
|
||||
CurrentHotkeyTextBlock.Text = "未设置";
|
||||
CurrentHotkeyTextBlock.Foreground = Brushes.Gray;
|
||||
CurrentHotkeyTextBlock.Foreground = HotkeyPlaceholderForeground;
|
||||
}
|
||||
else
|
||||
{
|
||||
var modifiersText = _currentModifiers == ModifierKeys.None ? "" : $"{_currentModifiers}+";
|
||||
CurrentHotkeyTextBlock.Text = $"{modifiersText}{_currentKey}";
|
||||
CurrentHotkeyTextBlock.Foreground = Brushes.Black;
|
||||
CurrentHotkeyTextBlock.Foreground = HotkeyValueForeground;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +116,7 @@ namespace Ink_Canvas.Windows
|
||||
private void StopHotkeyCapture()
|
||||
{
|
||||
BtnSetHotkey.Content = "设置";
|
||||
BtnSetHotkey.Background = Brushes.DodgerBlue;
|
||||
BtnSetHotkey.ClearValue(Button.BackgroundProperty);
|
||||
|
||||
// 移除键盘事件处理器
|
||||
KeyDown -= HotkeyItem_KeyDown;
|
||||
|
||||
@@ -6,211 +6,370 @@
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Windows"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
ui:ThemeManager.RequestedTheme="Light"
|
||||
Background="#F9F9F9"
|
||||
AllowsTransparency="True"
|
||||
mc:Ignorable="d"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d"
|
||||
Title="快捷键设置"
|
||||
Height="600"
|
||||
Width="800"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="CanResize"
|
||||
Title="快捷键设置"
|
||||
Height="600"
|
||||
Width="800">
|
||||
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||
ui:WindowHelper.SystemBackdropType="Mica"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:TitleBar.Height="48">
|
||||
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<SolidColorBrush x:Key="WindowBackground" Color="#1e1e1e" />
|
||||
<SolidColorBrush x:Key="BorderBrush" Color="#3f3f46" />
|
||||
<SolidColorBrush x:Key="TextForeground" Color="#fafafa" />
|
||||
<SolidColorBrush x:Key="TextSecondary" Color="#a1a1aa" />
|
||||
<SolidColorBrush x:Key="AccentColor" Color="#3b82f6" />
|
||||
<SolidColorBrush x:Key="TitleForeground" Color="#fafafa" />
|
||||
|
||||
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="{StaticResource AccentColor}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="16,8" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#2563eb" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#1d4ed8" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SecondaryButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{StaticResource TextForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Padding" Value="16,8" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#27272a" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Border Grid.Row="0" Background="#3B82F6" Height="60"
|
||||
|
||||
<!-- 标题栏(与云存储管理等新设置窗口一致) -->
|
||||
<Border x:Name="Border_TitleBarRoot"
|
||||
Grid.Row="0"
|
||||
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}"
|
||||
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0,0,0">
|
||||
<TextBlock Text="快捷键设置"
|
||||
Foreground="White"
|
||||
FontSize="22"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Button x:Name="BtnClose"
|
||||
Content=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,20,0"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
FontSize="16"
|
||||
Foreground="White"
|
||||
Click="BtnClose_Click"/>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,0,0"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TitleForeground}" />
|
||||
|
||||
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<Rectangle Grid.Column="2"
|
||||
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<ScrollViewer Grid.Row="1" Margin="20" VerticalScrollBarVisibility="Auto">
|
||||
<ikw:SimpleStackPanel Margin="20">
|
||||
<!-- 说明文字 -->
|
||||
<TextBlock Text="在这里可以自定义全局快捷键设置。全局快捷键在任何情况下都能生效,即使应用程序不在焦点状态。"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,0,0,20"
|
||||
Foreground="#666666"/>
|
||||
<!-- 主内容 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="{StaticResource WindowBackground}"
|
||||
Padding="20,10,20,16">
|
||||
<ui:ScrollViewerEx VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
PanningMode="VerticalOnly"
|
||||
ui:ThemeManager.RequestedTheme="Dark">
|
||||
<ikw:SimpleStackPanel Spacing="14">
|
||||
<TextBlock Text="在这里可以自定义全局快捷键设置。全局快捷键在任何情况下都能生效,即使应用程序不在焦点状态。"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
FontSize="14"
|
||||
LineHeight="20" />
|
||||
|
||||
<!-- 鼠标模式快捷键设置 -->
|
||||
<GroupBox Header="鼠标模式设置" Margin="0,0,0,20">
|
||||
<ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:ToggleSwitch x:Name="ToggleSwitchEnableHotkeysInMouseMode"
|
||||
Header="在鼠标模式下启用快捷键"
|
||||
Margin="0,0,10,0"
|
||||
Width="200"/>
|
||||
<Border BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="18,16"
|
||||
Background="#27272a">
|
||||
<ikw:SimpleStackPanel Spacing="10">
|
||||
<TextBlock Text="鼠标模式"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}" />
|
||||
<Border Height="1"
|
||||
Background="{StaticResource BorderBrush}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ui:ToggleSwitch x:Name="ToggleSwitchEnableHotkeysInMouseMode"
|
||||
Header="在鼠标模式下启用快捷键"
|
||||
OnContent=""
|
||||
OffContent=""
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
FontWeight="Bold"
|
||||
Margin="0,0,10,0"
|
||||
Width="220" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<TextBlock Text="开启后,即使在鼠标模式下快捷键也会生效"
|
||||
Foreground="{StaticResource TextSecondary}"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="13" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<TextBlock Text="开启后,即使在鼠标模式下快捷键也会生效"
|
||||
Foreground="#666666"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"/>
|
||||
</Border>
|
||||
|
||||
<ikw:SimpleStackPanel x:Name="HotkeyList" Spacing="12">
|
||||
<!-- 基本操作 -->
|
||||
<Border BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="18,16"
|
||||
Background="#27272a">
|
||||
<ikw:SimpleStackPanel Spacing="10">
|
||||
<TextBlock Text="基本操作"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}" />
|
||||
<Border Height="1"
|
||||
Background="{StaticResource BorderBrush}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<ikw:SimpleStackPanel Spacing="4">
|
||||
<local:HotkeyItem x:Name="UndoHotkey"
|
||||
Title="撤销"
|
||||
Description="撤销上一步操作"
|
||||
DefaultKey="Z"
|
||||
DefaultModifiers="Control" />
|
||||
<local:HotkeyItem x:Name="RedoHotkey"
|
||||
Title="重做"
|
||||
Description="重做上一步操作"
|
||||
DefaultKey="Y"
|
||||
DefaultModifiers="Control" />
|
||||
<local:HotkeyItem x:Name="ClearHotkey"
|
||||
Title="清空"
|
||||
Description="清空当前画板内容"
|
||||
DefaultKey="E"
|
||||
DefaultModifiers="Control" />
|
||||
<local:HotkeyItem x:Name="PasteHotkey"
|
||||
Title="粘贴"
|
||||
Description="粘贴剪贴板内容"
|
||||
DefaultKey="V"
|
||||
DefaultModifiers="Control" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 工具切换 -->
|
||||
<Border BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="18,16"
|
||||
Background="#27272a">
|
||||
<ikw:SimpleStackPanel Spacing="10">
|
||||
<TextBlock Text="工具切换"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}" />
|
||||
<Border Height="1"
|
||||
Background="{StaticResource BorderBrush}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<ikw:SimpleStackPanel Spacing="4">
|
||||
<local:HotkeyItem x:Name="SelectToolHotkey"
|
||||
Title="选择工具"
|
||||
Description="切换到选择工具"
|
||||
DefaultKey="S"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="DrawToolHotkey"
|
||||
Title="绘图工具"
|
||||
Description="切换到绘图工具"
|
||||
DefaultKey="D"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="EraserToolHotkey"
|
||||
Title="橡皮擦工具"
|
||||
Description="切换到橡皮擦工具"
|
||||
DefaultKey="E"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="BlackboardToolHotkey"
|
||||
Title="黑板工具"
|
||||
Description="切换到黑板工具"
|
||||
DefaultKey="B"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="QuitDrawToolHotkey"
|
||||
Title="退出绘图/白板"
|
||||
Description="退出绘图模式或白板模式"
|
||||
DefaultKey="Q"
|
||||
DefaultModifiers="Alt" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 画笔设置 -->
|
||||
<Border BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="18,16"
|
||||
Background="#27272a">
|
||||
<ikw:SimpleStackPanel Spacing="10">
|
||||
<TextBlock Text="画笔设置"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}" />
|
||||
<Border Height="1"
|
||||
Background="{StaticResource BorderBrush}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<ikw:SimpleStackPanel Spacing="4">
|
||||
<local:HotkeyItem x:Name="Pen1Hotkey"
|
||||
Title="画笔1"
|
||||
Description="选择画笔1"
|
||||
DefaultKey="D1"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="Pen2Hotkey"
|
||||
Title="画笔2"
|
||||
Description="选择画笔2"
|
||||
DefaultKey="D2"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="Pen3Hotkey"
|
||||
Title="画笔3"
|
||||
Description="选择画笔3"
|
||||
DefaultKey="D3"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="Pen4Hotkey"
|
||||
Title="画笔4"
|
||||
Description="选择画笔4"
|
||||
DefaultKey="D4"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="Pen5Hotkey"
|
||||
Title="画笔5"
|
||||
Description="选择画笔5"
|
||||
DefaultKey="D5"
|
||||
DefaultModifiers="Alt" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 功能快捷键 -->
|
||||
<Border BorderThickness="1"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
CornerRadius="8"
|
||||
Padding="18,16"
|
||||
Background="#27272a">
|
||||
<ikw:SimpleStackPanel Spacing="10">
|
||||
<TextBlock Text="功能快捷键"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextForeground}" />
|
||||
<Border Height="1"
|
||||
Background="{StaticResource BorderBrush}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<ikw:SimpleStackPanel Spacing="4">
|
||||
<local:HotkeyItem x:Name="DrawLineHotkey"
|
||||
Title="绘制直线"
|
||||
Description="绘制直线工具"
|
||||
DefaultKey="L"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="ScreenshotHotkey"
|
||||
Title="截图"
|
||||
Description="保存屏幕截图到桌面"
|
||||
DefaultKey="C"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="QuickDrawHotkey"
|
||||
Title="快抽"
|
||||
Description="打开快抽窗口(与悬浮快抽按钮相同)"
|
||||
DefaultKey="K"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="HideHotkey"
|
||||
Title="隐藏"
|
||||
Description="隐藏应用程序"
|
||||
DefaultKey="V"
|
||||
DefaultModifiers="Alt" />
|
||||
<local:HotkeyItem x:Name="ExitHotkey"
|
||||
Title="退出"
|
||||
Description="退出当前模式或应用程序"
|
||||
DefaultKey="Escape"
|
||||
DefaultModifiers="None" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 快捷键列表 -->
|
||||
<ikw:SimpleStackPanel x:Name="HotkeyList" Margin="0,0,0,20">
|
||||
<!-- 基本操作 -->
|
||||
<GroupBox Header="基本操作" Margin="0,0,0,15">
|
||||
<ikw:SimpleStackPanel>
|
||||
<local:HotkeyItem x:Name="UndoHotkey"
|
||||
Title="撤销"
|
||||
Description="撤销上一步操作"
|
||||
DefaultKey="Z"
|
||||
DefaultModifiers="Control"/>
|
||||
<local:HotkeyItem x:Name="RedoHotkey"
|
||||
Title="重做"
|
||||
Description="重做上一步操作"
|
||||
DefaultKey="Y"
|
||||
DefaultModifiers="Control"/>
|
||||
<local:HotkeyItem x:Name="ClearHotkey"
|
||||
Title="清空"
|
||||
Description="清空当前画板内容"
|
||||
DefaultKey="E"
|
||||
DefaultModifiers="Control"/>
|
||||
<local:HotkeyItem x:Name="PasteHotkey"
|
||||
Title="粘贴"
|
||||
Description="粘贴剪贴板内容"
|
||||
DefaultKey="V"
|
||||
DefaultModifiers="Control"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 工具切换 -->
|
||||
<GroupBox Header="工具切换" Margin="0,0,0,15">
|
||||
<ikw:SimpleStackPanel>
|
||||
<local:HotkeyItem x:Name="SelectToolHotkey"
|
||||
Title="选择工具"
|
||||
Description="切换到选择工具"
|
||||
DefaultKey="S"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="DrawToolHotkey"
|
||||
Title="绘图工具"
|
||||
Description="切换到绘图工具"
|
||||
DefaultKey="D"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="EraserToolHotkey"
|
||||
Title="橡皮擦工具"
|
||||
Description="切换到橡皮擦工具"
|
||||
DefaultKey="E"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="BlackboardToolHotkey"
|
||||
Title="黑板工具"
|
||||
Description="切换到黑板工具"
|
||||
DefaultKey="B"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="QuitDrawToolHotkey"
|
||||
Title="退出绘图/白板"
|
||||
Description="退出绘图模式或白板模式"
|
||||
DefaultKey="Q"
|
||||
DefaultModifiers="Alt"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 画笔设置 -->
|
||||
<GroupBox Header="画笔设置" Margin="0,0,0,15">
|
||||
<ikw:SimpleStackPanel>
|
||||
<local:HotkeyItem x:Name="Pen1Hotkey"
|
||||
Title="画笔1"
|
||||
Description="选择画笔1"
|
||||
DefaultKey="D1"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="Pen2Hotkey"
|
||||
Title="画笔2"
|
||||
Description="选择画笔2"
|
||||
DefaultKey="D2"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="Pen3Hotkey"
|
||||
Title="画笔3"
|
||||
Description="选择画笔3"
|
||||
DefaultKey="D3"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="Pen4Hotkey"
|
||||
Title="画笔4"
|
||||
Description="选择画笔4"
|
||||
DefaultKey="D4"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="Pen5Hotkey"
|
||||
Title="画笔5"
|
||||
Description="选择画笔5"
|
||||
DefaultKey="D5"
|
||||
DefaultModifiers="Alt"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 功能快捷键 -->
|
||||
<GroupBox Header="功能快捷键" Margin="0,0,0,15">
|
||||
<ikw:SimpleStackPanel>
|
||||
<local:HotkeyItem x:Name="DrawLineHotkey"
|
||||
Title="绘制直线"
|
||||
Description="绘制直线工具"
|
||||
DefaultKey="L"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="ScreenshotHotkey"
|
||||
Title="截图"
|
||||
Description="保存屏幕截图到桌面"
|
||||
DefaultKey="C"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="HideHotkey"
|
||||
Title="隐藏"
|
||||
Description="隐藏应用程序"
|
||||
DefaultKey="V"
|
||||
DefaultModifiers="Alt"/>
|
||||
<local:HotkeyItem x:Name="ExitHotkey"
|
||||
Title="退出"
|
||||
Description="退出当前模式或应用程序"
|
||||
DefaultKey="Escape"
|
||||
DefaultModifiers="None"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</GroupBox>
|
||||
</ikw:SimpleStackPanel></ikw:SimpleStackPanel>
|
||||
</ScrollViewer>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ui:ScrollViewerEx>
|
||||
</Border>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<Border Grid.Row="2" Background="#F0F0F0" Height="60">
|
||||
<Grid>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,20,0">
|
||||
<Button x:Name="BtnResetToDefault"
|
||||
Content="重置为默认"
|
||||
Width="100"
|
||||
Height="35"
|
||||
Margin="0,0,10,0"
|
||||
Click="BtnResetToDefault_Click"/>
|
||||
<Button x:Name="BtnSave"
|
||||
Content="保存设置"
|
||||
Width="100"
|
||||
Height="35"
|
||||
Background="#3B82F6"
|
||||
Foreground="White"
|
||||
Click="BtnSave_Click"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
<Border Grid.Row="2"
|
||||
Background="#18181b"
|
||||
BorderBrush="{StaticResource BorderBrush}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="16,12">
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10">
|
||||
<Button x:Name="BtnResetToDefault"
|
||||
Content="重置为默认"
|
||||
MinWidth="100"
|
||||
Height="36"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Click="BtnResetToDefault_Click" />
|
||||
<Button x:Name="BtnSave"
|
||||
Content="保存设置"
|
||||
MinWidth="100"
|
||||
Height="36"
|
||||
Style="{StaticResource PrimaryButtonStyle}"
|
||||
Click="BtnSave_Click" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
</Window>
|
||||
|
||||
@@ -135,6 +135,9 @@ namespace Ink_Canvas.Windows
|
||||
_hotkeyItems["Screenshot"] = ScreenshotHotkey;
|
||||
ScreenshotHotkey.HotkeyName = "Screenshot";
|
||||
|
||||
_hotkeyItems["QuickDraw"] = QuickDrawHotkey;
|
||||
QuickDrawHotkey.HotkeyName = "QuickDraw";
|
||||
|
||||
_hotkeyItems["Hide"] = HideHotkey;
|
||||
HideHotkey.HotkeyName = "Hide";
|
||||
|
||||
@@ -243,6 +246,9 @@ namespace Ink_Canvas.Windows
|
||||
case "Screenshot":
|
||||
hotkeyItem.SetCurrentHotkey(Key.C, ModifierKeys.Alt);
|
||||
break;
|
||||
case "QuickDraw":
|
||||
hotkeyItem.SetCurrentHotkey(Key.K, ModifierKeys.Alt);
|
||||
break;
|
||||
case "Hide":
|
||||
hotkeyItem.SetCurrentHotkey(Key.V, ModifierKeys.Alt);
|
||||
break;
|
||||
@@ -472,6 +478,8 @@ namespace Ink_Canvas.Windows
|
||||
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
||||
case "Screenshot":
|
||||
return () => _mainWindow.SaveScreenShotToDesktop();
|
||||
case "QuickDraw":
|
||||
return () => _mainWindow.OpenQuickDrawFromHotkey();
|
||||
case "Hide":
|
||||
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
||||
case "Exit":
|
||||
@@ -533,11 +541,6 @@ namespace Ink_Canvas.Windows
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标题栏拖拽事件
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
@@ -17,11 +18,16 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
private TimerControl parentControl;
|
||||
private System.Timers.Timer updateTimer;
|
||||
private readonly Dispatcher uiDispatcher;
|
||||
|
||||
public MinimizedTimerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// capture the UI dispatcher early so timer callbacks can use it even when
|
||||
// Application.Current may become null during shutdown
|
||||
uiDispatcher = this.Dispatcher;
|
||||
|
||||
updateTimer = new System.Timers.Timer(100);
|
||||
updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
updateTimer.Start();
|
||||
@@ -46,18 +52,35 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Stop();
|
||||
updateTimer.Dispose();
|
||||
// 先取消事件订阅,防止在停止/释放后仍有回调被触发
|
||||
updateTimer.Elapsed -= UpdateTimer_Elapsed;
|
||||
try
|
||||
{
|
||||
updateTimer.Stop();
|
||||
}
|
||||
catch { }
|
||||
try
|
||||
{
|
||||
updateTimer.Dispose();
|
||||
}
|
||||
catch { }
|
||||
updateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
// 当主题变化时,重新应用主题
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
var dispatcher = uiDispatcher ?? Application.Current?.Dispatcher;
|
||||
if (dispatcher == null) return;
|
||||
if (dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished) return;
|
||||
|
||||
// 使用异步调用,避免在关闭过程中同步等待导致异常
|
||||
try
|
||||
{
|
||||
RefreshTheme();
|
||||
});
|
||||
dispatcher.InvokeAsync(() => RefreshTheme(), DispatcherPriority.Normal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -99,26 +122,35 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
if (parentControl != null)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
var dispatcher = uiDispatcher ?? Application.Current?.Dispatcher;
|
||||
if (dispatcher == null) return;
|
||||
if (dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished) return;
|
||||
|
||||
// 使用异步派发,避免在 Dispatcher 关闭时同步等待导致 TaskCanceledException
|
||||
try
|
||||
{
|
||||
if (this.Visibility != Visibility.Visible)
|
||||
dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldHide())
|
||||
{
|
||||
this.Visibility = Visibility.Collapsed;
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
if (this.Visibility != Visibility.Visible)
|
||||
{
|
||||
parent.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTimeDisplay();
|
||||
});
|
||||
if (ShouldHide())
|
||||
{
|
||||
this.Visibility = Visibility.Collapsed;
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateTimeDisplay();
|
||||
}, DispatcherPriority.Normal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +246,15 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
private void ParentControl_TimerCompleted(object sender, EventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
var dispatcher = uiDispatcher ?? Application.Current?.Dispatcher;
|
||||
if (dispatcher == null) return;
|
||||
if (dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished) return;
|
||||
|
||||
try
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
});
|
||||
dispatcher.InvokeAsync(() => { Visibility = Visibility.Collapsed; }, DispatcherPriority.Normal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void SetDigitDisplay(string pathName, int digit, bool isRed = false)
|
||||
@@ -391,7 +428,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
try
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
var mainWindow = Application.Current?.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var currentModeField = mainWindow.GetType().GetField("currentMode",
|
||||
|
||||
@@ -4,11 +4,18 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
mc:Ignorable="d" FontFamily="Microsoft YaHei UI" ui:WindowHelper.UseModernWindowStyle="True"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
mc:Ignorable="d" FontFamily="Microsoft YaHei UI"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" Topmost="True"
|
||||
Topmost="True"
|
||||
Title="Ink Canvas 抽奖 - 名单导入" Height="500" Width="400"
|
||||
Loaded="Window_Loaded" Closing="Window_Closing">
|
||||
Loaded="Window_Loaded" Closing="Window_Closing"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||
ui:WindowHelper.SystemBackdropType="Mica"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:TitleBar.Height="48">
|
||||
<Window.Resources>
|
||||
<!-- 主题资源 -->
|
||||
<SolidColorBrush x:Key="NamesInputWindowBackground" Color="White"/>
|
||||
@@ -17,28 +24,60 @@
|
||||
<SolidColorBrush x:Key="NamesInputWindowButtonForeground" Color="Black"/>
|
||||
<SolidColorBrush x:Key="NamesInputWindowBorderBrush" Color="#E4E4E7"/>
|
||||
</Window.Resources>
|
||||
<Grid Background="{DynamicResource NamesInputWindowBackground}">
|
||||
<Label Content="请在下方输入名单,每行一人(建议直接粘贴表格姓名列)"
|
||||
Margin="10"
|
||||
Foreground="{DynamicResource NamesInputWindowForeground}"
|
||||
FontFamily="Microsoft YaHei UI"/>
|
||||
<TextBox Name="TextBoxNames"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AcceptsReturn="True"
|
||||
Margin="10,40,10,50"
|
||||
Background="{DynamicResource NamesInputWindowBackground}"
|
||||
Foreground="{DynamicResource NamesInputWindowForeground}"
|
||||
BorderBrush="{DynamicResource NamesInputWindowBorderBrush}"/>
|
||||
<Button Margin="10"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Content="关闭"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
Width="100"
|
||||
Click="Button_Click"
|
||||
Background="{DynamicResource NamesInputWindowButtonBackground}"
|
||||
Foreground="{DynamicResource NamesInputWindowButtonForeground}"
|
||||
BorderBrush="{DynamicResource NamesInputWindowBorderBrush}"/>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 自定义标题栏 -->
|
||||
<Border x:Name="Border_TitleBarRoot"
|
||||
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||
VerticalAlignment="Center" Margin="12,0,0,0" FontSize="12" FontWeight="SemiBold"/>
|
||||
|
||||
<!--Right Inset-->
|
||||
<Rectangle Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}"
|
||||
Grid.Column="2"/>
|
||||
|
||||
<!--Right Buttons-->
|
||||
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||
Orientation="Horizontal" Grid.Column="1" Spacing="5">
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<Grid Grid.Row="1" Background="{DynamicResource NamesInputWindowBackground}" Margin="10,10,10,10">
|
||||
<Label Content="请在下方输入名单,每行一人(建议直接粘贴表格姓名列)"
|
||||
Margin="10"
|
||||
Foreground="{DynamicResource NamesInputWindowForeground}"
|
||||
FontFamily="Microsoft YaHei UI"/>
|
||||
<TextBox Name="TextBoxNames"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AcceptsReturn="True"
|
||||
Margin="10,40,10,50"
|
||||
Background="{DynamicResource NamesInputWindowBackground}"
|
||||
Foreground="{DynamicResource NamesInputWindowForeground}"
|
||||
BorderBrush="{DynamicResource NamesInputWindowBorderBrush}"/>
|
||||
<Button Margin="10"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Content="关闭"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
Width="100"
|
||||
Click="Button_Click"
|
||||
Background="{DynamicResource NamesInputWindowButtonBackground}"
|
||||
Foreground="{DynamicResource NamesInputWindowButtonForeground}"
|
||||
BorderBrush="{DynamicResource NamesInputWindowBorderBrush}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
@@ -38,7 +38,11 @@ namespace Ink_Canvas
|
||||
var result = MessageBox.Show("是否保存?", "名单导入", MessageBoxButton.YesNo);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
File.WriteAllText(App.RootPath + "Names.txt", TextBoxNames.Text);
|
||||
var path = App.RootPath + "Names.txt";
|
||||
ProcessProtectionManager.WithWriteAccess(path, () =>
|
||||
{
|
||||
File.WriteAllText(path, TextBoxNames.Text);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -1200,10 +1200,19 @@ namespace Ink_Canvas
|
||||
UpdateCountDisplay();
|
||||
}
|
||||
|
||||
private void ImportList_Click(object sender, RoutedEventArgs e)
|
||||
private async void ImportList_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SecurityManager.IsPasswordRequiredForModifyOrClearNameList(MainWindow.Settings))
|
||||
{
|
||||
bool ok = await SecurityManager.PromptAndVerifyAsync(
|
||||
MainWindow.Settings,
|
||||
this,
|
||||
"名单修改验证",
|
||||
"请输入安全密码以修改点名名单。");
|
||||
if (!ok) return;
|
||||
}
|
||||
// 打开名单导入窗口,与老点名UI保持一致
|
||||
var namesInputWindow = new NamesInputWindow();
|
||||
namesInputWindow.ShowDialog();
|
||||
@@ -1260,10 +1269,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearList_Click(object sender, RoutedEventArgs e)
|
||||
private async void ClearList_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (SecurityManager.IsPasswordRequiredForModifyOrClearNameList(MainWindow.Settings))
|
||||
{
|
||||
bool ok = await SecurityManager.PromptAndVerifyAsync(
|
||||
MainWindow.Settings,
|
||||
this,
|
||||
"名单清空验证",
|
||||
"请输入安全密码以清空点名名单。");
|
||||
if (!ok) return;
|
||||
}
|
||||
// 清空名单
|
||||
nameList.Clear();
|
||||
UpdateListCountDisplay();
|
||||
@@ -1505,28 +1523,27 @@ namespace Ink_Canvas
|
||||
|
||||
try
|
||||
{
|
||||
string protocol = "";
|
||||
string[] protocols;
|
||||
switch (selectedExternalCaller)
|
||||
{
|
||||
case "ClassIsland":
|
||||
protocol = "classisland://plugins/IslandCaller/Simple/1";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByName("ClassIsland");
|
||||
break;
|
||||
case "SecRandom":
|
||||
protocol = "secrandom://direct_extraction";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByName("SecRandom");
|
||||
break;
|
||||
case "NamePicker":
|
||||
protocol = "namepicker://";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByName("NamePicker");
|
||||
break;
|
||||
default:
|
||||
protocol = "classisland://plugins/IslandCaller/Simple/1";
|
||||
protocols = ExternalCallerLauncher.GetProtocolsByName("ClassIsland");
|
||||
break;
|
||||
}
|
||||
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
if (!ExternalCallerLauncher.TryLaunch(protocols, out Exception lastException))
|
||||
{
|
||||
FileName = protocol,
|
||||
UseShellExecute = true
|
||||
});
|
||||
throw lastException ?? new InvalidOperationException("external caller protocols are unavailable");
|
||||
}
|
||||
|
||||
UpdateStatusDisplay($"已启动外部点名: {selectedExternalCaller}");
|
||||
}
|
||||
|
||||
+581
-505
File diff suppressed because it is too large
Load Diff
@@ -474,6 +474,8 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前步骤索引更新向导界面:显示对应步骤面板,播放切换动画,并刷新步骤指示、标题、子标题和按钮文本/可见性。
|
||||
/// </summary>
|
||||
|
||||
@@ -1,101 +1,218 @@
|
||||
<Window x:Class="Ink_Canvas.OperatingGuideWindow"
|
||||
<Window x:Class="Ink_Canvas.OperatingGuideWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
ui:ThemeManager.RequestedTheme="Light" Topmost="True" Background="Transparent"
|
||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d"
|
||||
Title="Ink Canvas Annotation 使用指南"
|
||||
Height="600" Width="500">
|
||||
<Border Background="{DynamicResource OperatingGuideWindowBackground}" CornerRadius="10" BorderThickness="1" BorderBrush="{DynamicResource OperatingGuideWindowBorderBrush}" Margin="10,10,10,50">
|
||||
<Grid>
|
||||
<Border MouseMove="WindowDragMove" Visibility="Visible" Width="64" Height="15" CornerRadius="8" Background="Gray" Margin="0,0,0,5" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
|
||||
<ScrollViewer Margin="0,0,0,30" VerticalScrollBarVisibility="Auto"
|
||||
PanningMode="VerticalOnly" ui:ThemeManager.RequestedTheme="Light"
|
||||
ManipulationBoundaryFeedback="SCManipulationBoundaryFeedback">
|
||||
<ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20,20,20,0" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Margin="5, 0" Source="{DynamicResource OperatingGuideWindowKeyboardIcon}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Height="40" Width="40"/>
|
||||
<TextBlock FontSize="22" Text="软件快捷键" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
Height="600"
|
||||
Width="520"
|
||||
MinHeight="400"
|
||||
MinWidth="400"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="True"
|
||||
ResizeMode="CanResize"
|
||||
FontFamily="Microsoft YaHei UI"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||
ui:WindowHelper.SystemBackdropType="Mica"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
ui:TitleBar.Height="48">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 自定义标题栏(与 OOBE / 隐私说明等 Fluent 窗口一致) -->
|
||||
<Border x:Name="Border_TitleBarRoot"
|
||||
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,0,0"
|
||||
FontSize="12"
|
||||
FontWeight="SemiBold" />
|
||||
|
||||
<Rectangle Grid.Column="2"
|
||||
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}" />
|
||||
|
||||
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="1" Background="{DynamicResource SettingsPageBackground}">
|
||||
<ScrollViewer Padding="0"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
PanningMode="VerticalOnly"
|
||||
ManipulationBoundaryFeedback="SCManipulationBoundaryFeedback">
|
||||
<ikw:SimpleStackPanel Margin="24,20,24,28" Spacing="4">
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,12"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
Spacing="12">
|
||||
<Image Height="32"
|
||||
Width="32"
|
||||
Source="{DynamicResource OperatingGuideWindowKeyboardIcon}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<TextBlock FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Text="软件快捷键"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
|
||||
<Viewbox Height="320" HorizontalAlignment="Left">
|
||||
<ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon1}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Height="20" Width="20"/>
|
||||
<TextBlock FontSize="10" Text=" + Z —— 撤销" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon2}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Height="20" Width="20"/>
|
||||
<TextBlock FontSize="10" Text=" + Y —— 重做" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon3}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" Height="20" Width="20"/>
|
||||
<TextBlock FontSize="10" Text=" + E —— 清屏" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + V —— 显示/隐藏笑脸右侧工具栏(Visibility)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + C —— 截屏(Capture)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + S —— 切换至选择模式(Select)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + D —— 切换至批注模式 / 墨迹颜色选择器(Draw)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + Q —— 退出批注模式(Quit)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + B —— 切换/退出画板模式(Board)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + E —— 切换至面积擦/墨迹擦功能(Eraser)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text=" Alt + L —— 切换至单次直线绘制功能(Line)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="10" Text="Shift + Esc —— 退出 PPT 放映" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Viewbox>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
Spacing="8">
|
||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon1}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="20"
|
||||
Width="20"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock FontSize="14"
|
||||
Text=" + Z —— 撤销"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
Spacing="8">
|
||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon2}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="20"
|
||||
Width="20"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock FontSize="14"
|
||||
Text=" + Y —— 重做"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
Spacing="8">
|
||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon3}"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Height="20"
|
||||
Width="20"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock FontSize="14"
|
||||
Text=" + E —— 清屏"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + V —— 显示/隐藏笑脸右侧工具栏(Visibility)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + C —— 截屏(Capture)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + S —— 切换至选择模式(Select)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + D —— 切换至批注模式 / 墨迹颜色选择器(Draw)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + Q —— 退出批注模式(Quit)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + B —— 切换/退出画板模式(Board)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + E —— 切换至面积擦/墨迹擦功能(Eraser)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||
MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text=" Alt + L —— 切换至单次直线绘制功能(Line)"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
<ikw:SimpleStackPanel MinHeight="28"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock FontSize="14"
|
||||
Text="Shift + Esc —— 退出 PPT 放映"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</ikw:SimpleStackPanel>
|
||||
</ScrollViewer>
|
||||
<Viewbox Visibility="{Binding ElementName=BigViewController, Path=Visibility}" Margin="20,20,20,20" HorizontalAlignment="Right">
|
||||
<ikw:SimpleStackPanel Height="180" Orientation="Horizontal">
|
||||
<Border x:Name="BtnFullscreen" MouseUp="BtnFullscreen_MouseUp" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="5"
|
||||
Background="{DynamicResource OperatingGuideWindowFullscreenButtonBackground}" Height="20" Width="20" CornerRadius="100">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Name="SymbolIconFullscreen" Symbol="FullScreen" Foreground="{DynamicResource OperatingGuideWindowFullscreenButtonForeground}"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
<Border x:Name="BtnClose" MouseUp="BtnClose_MouseUp" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="5"
|
||||
Background="{DynamicResource OperatingGuideWindowCloseButtonBackground}" Height="20" Width="20" CornerRadius="100">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
||||
</Border.Effect>
|
||||
<Viewbox Margin="5.5">
|
||||
<ui:SymbolIcon Symbol="Clear" Foreground="{DynamicResource OperatingGuideWindowCloseButtonForeground}"/>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@@ -14,33 +14,10 @@ namespace Ink_Canvas
|
||||
public OperatingGuideWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
RefreshTheme();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
|
||||
}
|
||||
|
||||
private void BtnClose_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void WindowDragMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed) DragMove();
|
||||
}
|
||||
|
||||
private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (WindowState == WindowState.Normal)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.BackToWindow;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.FullScreen;
|
||||
}
|
||||
}
|
||||
|
||||
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
@@ -101,7 +102,7 @@ namespace Ink_Canvas.Windows
|
||||
private double _collapsedOffset = 200; // 折叠时的偏移量(隐藏内容区域)
|
||||
private MainWindow _mainWindow;
|
||||
|
||||
private Dictionary<System.Windows.Controls.Image, int> _pptImages = new Dictionary<System.Windows.Controls.Image, int>();
|
||||
private Dictionary<FrameworkElement, int> _pptImages = new Dictionary<FrameworkElement, int>();
|
||||
|
||||
private Dictionary<int, List<string>> _pptImagePaths = new Dictionary<int, List<string>>();
|
||||
|
||||
@@ -183,9 +184,9 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
if (item is System.Windows.Controls.Image image)
|
||||
if (item is FrameworkElement fe && _pptImages.ContainsKey(fe))
|
||||
{
|
||||
RemoveImageFromPPT(image);
|
||||
RemoveImageFromPPT(fe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,20 +197,20 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveImageFromPPT(System.Windows.Controls.Image image)
|
||||
private void RemoveImageFromPPT(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (image == null) return;
|
||||
if (element == null) return;
|
||||
|
||||
if (_pptImages.ContainsKey(image))
|
||||
if (_pptImages.ContainsKey(element))
|
||||
{
|
||||
int slideNumber = _pptImages[image];
|
||||
_pptImages.Remove(image);
|
||||
int slideNumber = _pptImages[element];
|
||||
_pptImages.Remove(element);
|
||||
|
||||
if (_pptImagePaths.ContainsKey(slideNumber))
|
||||
{
|
||||
string imagePath = image.Tag as string;
|
||||
string imagePath = element.Tag as string;
|
||||
if (!string.IsNullOrEmpty(imagePath) && _pptImagePaths[slideNumber].Contains(imagePath))
|
||||
{
|
||||
_pptImagePaths[slideNumber].Remove(imagePath);
|
||||
@@ -929,7 +930,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
||||
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
@@ -941,14 +942,14 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
if (createImageMethod != null)
|
||||
{
|
||||
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { filePath }) as System.Threading.Tasks.Task<System.Windows.Controls.Image>;
|
||||
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { filePath }) as System.Threading.Tasks.Task<FrameworkElement>;
|
||||
if (imageTask != null)
|
||||
{
|
||||
var image = await imageTask;
|
||||
if (image != null)
|
||||
var inserted = await imageTask;
|
||||
if (inserted != null)
|
||||
{
|
||||
image.Tag = filePath;
|
||||
await InsertImageToMainWindow(image, filePath);
|
||||
inserted.Tag = filePath;
|
||||
await InsertImageToMainWindow(inserted, filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1185,9 +1186,9 @@ namespace Ink_Canvas.Windows
|
||||
}, DispatcherPriority.Normal);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task InsertImageToMainWindow(System.Windows.Controls.Image image, string originalFilePath = null, bool saveToJson = true)
|
||||
private async System.Threading.Tasks.Task InsertImageToMainWindow(FrameworkElement element, string originalFilePath = null, bool saveToJson = true)
|
||||
{
|
||||
if (_mainWindow == null || image == null) return;
|
||||
if (_mainWindow == null || element == null) return;
|
||||
|
||||
// 确保在UI线程上执行
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
@@ -1196,11 +1197,11 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
// 生成唯一名称
|
||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
element.Name = timestamp;
|
||||
|
||||
// 设置图片属性
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
element.IsHitTestVisible = true;
|
||||
element.Focusable = false;
|
||||
|
||||
System.Windows.Controls.InkCanvas inkCanvas = null;
|
||||
var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas",
|
||||
@@ -1274,7 +1275,7 @@ namespace Ink_Canvas.Windows
|
||||
// 如果在PPT模式下,记录图片和页面编号的关联,并保存图片路径
|
||||
if (currentSlideNumber > 0 && !string.IsNullOrEmpty(originalFilePath) && saveToJson)
|
||||
{
|
||||
_pptImages[image] = currentSlideNumber;
|
||||
_pptImages[element] = currentSlideNumber;
|
||||
|
||||
// 添加到页面图片路径列表
|
||||
if (!_pptImagePaths.ContainsKey(currentSlideNumber))
|
||||
@@ -1289,14 +1290,14 @@ namespace Ink_Canvas.Windows
|
||||
else if (currentSlideNumber > 0)
|
||||
{
|
||||
// 即使不保存到JSON,也要记录图片和页面编号的关联(用于翻页显示/隐藏)
|
||||
_pptImages[image] = currentSlideNumber;
|
||||
_pptImages[element] = currentSlideNumber;
|
||||
}
|
||||
|
||||
// 先添加到画布(与MainWindow的实现保持一致)
|
||||
inkCanvas.Children.Add(image);
|
||||
inkCanvas.Children.Add(element);
|
||||
|
||||
// 等待图片加载完成后再进行后续处理
|
||||
image.Loaded += (s, args) =>
|
||||
element.Loaded += (s, args) =>
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
@@ -1305,19 +1306,19 @@ namespace Ink_Canvas.Windows
|
||||
// 初始化TransformGroup
|
||||
var initializeTransformMethod = _mainWindow.GetType().GetMethod("InitializeElementTransform",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
initializeTransformMethod?.Invoke(_mainWindow, new object[] { image });
|
||||
initializeTransformMethod?.Invoke(_mainWindow, new object[] { element });
|
||||
|
||||
// 居中缩放
|
||||
var centerMethod = _mainWindow.GetType().GetMethod("CenterAndScaleElement",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
centerMethod?.Invoke(_mainWindow, new object[] { image });
|
||||
centerMethod?.Invoke(_mainWindow, new object[] { element });
|
||||
|
||||
// 绑定事件处理器
|
||||
var bindEventsMethod = _mainWindow.GetType().GetMethod("BindElementEvents",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
bindEventsMethod?.Invoke(_mainWindow, new object[] { image });
|
||||
bindEventsMethod?.Invoke(_mainWindow, new object[] { element });
|
||||
|
||||
LogHelper.WriteLogToFile($"图片插入完成: {image.Name}, PPT页面: {currentSlideNumber}");
|
||||
LogHelper.WriteLogToFile($"图片插入完成: {element.Name}, PPT页面: {currentSlideNumber}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1333,7 +1334,7 @@ namespace Ink_Canvas.Windows
|
||||
if (timeMachine != null)
|
||||
{
|
||||
var commitMethod = timeMachine.GetType().GetMethod("CommitElementInsertHistory");
|
||||
commitMethod?.Invoke(timeMachine, new object[] { image });
|
||||
commitMethod?.Invoke(timeMachine, new object[] { element });
|
||||
}
|
||||
|
||||
// 切换到选择模式
|
||||
@@ -1699,12 +1700,16 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
// 检查已存在的图片路径(通过Tag)
|
||||
var existingImagePaths = new HashSet<string>();
|
||||
foreach (System.Windows.Controls.Image existingImage in inkCanvas.Children.OfType<System.Windows.Controls.Image>())
|
||||
foreach (var existingImage in inkCanvas.Children.OfType<System.Windows.Controls.Image>())
|
||||
{
|
||||
if (existingImage.Tag is string tagPath && !string.IsNullOrEmpty(tagPath))
|
||||
{
|
||||
existingImagePaths.Add(tagPath);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var existingPdf in inkCanvas.Children.OfType<PdfEmbeddedView>())
|
||||
{
|
||||
if (existingPdf.Tag is string tagPath && !string.IsNullOrEmpty(tagPath))
|
||||
existingImagePaths.Add(tagPath);
|
||||
}
|
||||
|
||||
// 使用反射调用MainWindow的CreateAndCompressImageAsync方法
|
||||
@@ -1733,17 +1738,17 @@ namespace Ink_Canvas.Windows
|
||||
continue;
|
||||
}
|
||||
|
||||
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { imagePath }) as System.Threading.Tasks.Task<System.Windows.Controls.Image>;
|
||||
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { imagePath }) as System.Threading.Tasks.Task<FrameworkElement>;
|
||||
if (imageTask != null)
|
||||
{
|
||||
var image = await imageTask;
|
||||
if (image != null)
|
||||
var inserted = await imageTask;
|
||||
if (inserted != null)
|
||||
{
|
||||
// 保存原始文件路径到Tag
|
||||
image.Tag = imagePath;
|
||||
inserted.Tag = imagePath;
|
||||
|
||||
// 插入图片(不保存路径,因为已经存在)
|
||||
await InsertImageToMainWindow(image, imagePath, false);
|
||||
await InsertImageToMainWindow(inserted, imagePath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Ink_Canvas.Windows
|
||||
private double originalCapsuleWidth = 0;
|
||||
private string lastCountdownText = ""; // 上次的倒计时文本,用于检测文本变化
|
||||
private Storyboard currentWidthAnimation; // 当前正在运行的宽度动画
|
||||
private volatile bool isDisposed = false;
|
||||
|
||||
public PPTTimeCapsule()
|
||||
{
|
||||
@@ -72,10 +73,43 @@ namespace Ink_Canvas.Windows
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
StopTimeUpdate();
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
timeUpdateTimer?.Dispose();
|
||||
countdownUpdateTimer?.Dispose();
|
||||
if (isDisposed) return;
|
||||
isDisposed = true;
|
||||
|
||||
// 先取消系统事件订阅,防止在释放过程中再次触发
|
||||
try
|
||||
{
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 停止并释放定时器,确保不再触发回调
|
||||
try
|
||||
{
|
||||
if (timeUpdateTimer != null)
|
||||
{
|
||||
timeUpdateTimer.Elapsed -= TimeUpdateTimer_Elapsed;
|
||||
try { timeUpdateTimer.Stop(); } catch { }
|
||||
try { timeUpdateTimer.Dispose(); } catch { }
|
||||
timeUpdateTimer = null;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
try
|
||||
{
|
||||
if (countdownUpdateTimer != null)
|
||||
{
|
||||
countdownUpdateTimer.Elapsed -= CountdownUpdateTimer_Elapsed;
|
||||
try { countdownUpdateTimer.Stop(); } catch { }
|
||||
try { countdownUpdateTimer.Dispose(); } catch { }
|
||||
countdownUpdateTimer = null;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 停止动画
|
||||
try { StopColonBlinkAnimation(); } catch { }
|
||||
}
|
||||
|
||||
private void InitializeTimers()
|
||||
@@ -91,23 +125,39 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
private void TimeUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
if (isDisposed) return;
|
||||
|
||||
var dispatcher = this.Dispatcher;
|
||||
if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished) return;
|
||||
|
||||
try
|
||||
{
|
||||
UpdateTimeDisplay();
|
||||
}), DispatcherPriority.Normal);
|
||||
dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (isDisposed) return;
|
||||
UpdateTimeDisplay();
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void CountdownUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (this.Visibility != Visibility.Visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (isDisposed) return;
|
||||
|
||||
UpdateCountdownDisplay();
|
||||
}), DispatcherPriority.Normal);
|
||||
var dispatcher = this.Dispatcher;
|
||||
if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished) return;
|
||||
|
||||
try
|
||||
{
|
||||
dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (isDisposed) return;
|
||||
if (this.Visibility != Visibility.Visible) return;
|
||||
UpdateCountdownDisplay();
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void StartTimeUpdate()
|
||||
@@ -724,10 +774,20 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
if (isDisposed) return;
|
||||
|
||||
var dispatcher = this.Dispatcher;
|
||||
if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished) return;
|
||||
|
||||
try
|
||||
{
|
||||
ApplyTheme();
|
||||
}), DispatcherPriority.Normal);
|
||||
dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (isDisposed) return;
|
||||
ApplyTheme();
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user