Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa7b593b65 | |||
| fabac7e2bb | |||
| 9b5ee56a09 | |||
| c78d3d74c2 | |||
| b1459901d0 | |||
| aba56ac340 | |||
| 3d4e1872f1 | |||
| 7a4d33b7da | |||
| afb65eb908 | |||
| df5daeeb75 | |||
| 792779e5b2 | |||
| 3313a0a182 | |||
| 88dd53302f | |||
| e972adfbcb | |||
| ab6425e0d9 | |||
| a81ee5b3db | |||
| fae08a5285 | |||
| eeb4a25d7a | |||
| fdf07180dd | |||
| 6387a6fcda | |||
| a6e400629a | |||
| 8172b7c776 | |||
| ca4d2ac4a2 | |||
| abd9f850eb | |||
| 3aef6f5e05 | |||
| 2f957374e3 | |||
| 3f8cabc7e0 | |||
| 73d1dc8f48 | |||
| 2bfb78a257 | |||
| 783e5c43a5 | |||
| d7e8330016 | |||
| 80d3836e9e | |||
| c26d1c348e | |||
| 95c307cc0b | |||
| 6e7299e445 | |||
| 01fa047591 | |||
| 8c741c1fb7 | |||
| b0bfc8d5ed | |||
| ad8d8f94ff | |||
| c99bd2bb63 | |||
| 468df7dad7 | |||
| 3c2e4a0990 | |||
| 87b717f6a9 | |||
| 51dc45988e | |||
| 2f8b986f1f | |||
| 7f01e7acb6 | |||
| d011d2ba8a | |||
| a922654c17 | |||
| e70a486362 | |||
| 1683bc8418 | |||
| b6368fb0e4 | |||
| ddfa9c2676 | |||
| f3ddd5a11a | |||
| 39e8b2359e | |||
| 4fb73c155b | |||
| 3287603d0a | |||
| 1abb317054 | |||
| 719e37c26b | |||
| 45d2f99fd7 | |||
| f75fdded98 | |||
| 22a6d87771 | |||
| 2479da4bbc | |||
| c22424d798 | |||
| bbb30b7c25 | |||
| 71cbe12cee | |||
| 011effa047 | |||
| b1648dd702 | |||
| 81621cb9d0 | |||
| 3f460d7a5c | |||
| 6fe34c1250 | |||
| d5cac938c2 | |||
| 6222fabdd4 | |||
| 8ed4f25499 | |||
| 3afd4641cd | |||
| e9fde97453 | |||
| 441f8b6e26 | |||
| 8042b917a0 | |||
| 343e7281fe | |||
| c64e6a4554 | |||
| 8190bf275c | |||
| e792f2637d | |||
| 3cd26323dc | |||
| 40e1c4d467 | |||
| 86c22d373a | |||
| cb7a76efc5 | |||
| 545425c4d3 | |||
| eb1aaa10e4 | |||
| daf0db312b | |||
| 8b2bc2f064 | |||
| 2e343cbbf9 | |||
| a75f0470bc | |||
| 287d31a3a9 | |||
| 430fff0515 | |||
| fbbb7b8ad7 | |||
| 54b74d7411 | |||
| 82dba31b2a | |||
| 38d7e782e0 |
+11
-1
@@ -16,7 +16,8 @@
|
||||
"contributions": [
|
||||
"maintenance",
|
||||
"doc",
|
||||
"code"
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -98,6 +99,15 @@
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "doudou0720",
|
||||
"name": "doudou0720",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/98651603?v=4",
|
||||
"profile": "https://github.com/doudou0720",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,35 +1,383 @@
|
||||
name: .NET Build
|
||||
name: .NET Build & PR Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main,beta ]
|
||||
branches: [ main, beta ]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches: [ main ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- 'docs/**'
|
||||
- 'Images/**'
|
||||
- 'Manual.md'
|
||||
- 'README.md'
|
||||
- 'UpdateLog.md'
|
||||
- 'CODE_OF_CONDUCT.md'
|
||||
- 'LICENSE'
|
||||
- 'privacy.txt'
|
||||
- 'icc.png'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
find-or-create-pr-comment:
|
||||
name: Find or Create PR Comment
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
outputs:
|
||||
comment_id: ${{ steps.find-comment.outputs.comment_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Find existing bot comment
|
||||
id: find-comment
|
||||
run: |
|
||||
# 查找包含特定标记的现有评论
|
||||
COMMENTS=$(gh api \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
||||
--jq '.[] | select(.body | contains("<!-- github-action-pr-build -->")) | .id' | head -1)
|
||||
|
||||
if [ -n "$COMMENTS" ]; then
|
||||
echo "📝 找到现有评论 ID: $COMMENTS"
|
||||
echo "comment_id=$COMMENTS" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "📝 未找到现有评论,将创建新评论"
|
||||
echo "comment_id=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Setup MSbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
pr-preview-comment:
|
||||
name: PR Preview (Building)
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
needs: find-or-create-pr-comment
|
||||
permissions:
|
||||
pull-requests: write
|
||||
outputs:
|
||||
comment_id: ${{ steps.create-preview-comment.outputs.comment-id }}
|
||||
steps:
|
||||
- name: Prepare Preview Comment
|
||||
id: prepare-preview
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_RUN_ID: ${{ github.run_id }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
IS_READY_FOR_REVIEW: ${{ github.event.action == 'ready_for_review' }}
|
||||
run: |
|
||||
# 构建预览评论内容
|
||||
{
|
||||
if [ "$IS_READY_FOR_REVIEW" = "true" ]; then
|
||||
echo "# 🚀 PR 准备审查 - 构建预览"
|
||||
echo ""
|
||||
echo "**状态:** 🟡 正在构建(准备审查状态)..."
|
||||
else
|
||||
echo "# 🏗️ PR 构建预览"
|
||||
echo ""
|
||||
echo "**状态:** 🟡 正在构建..."
|
||||
fi
|
||||
|
||||
echo "**分支提交:** \`$PR_HEAD_SHA\`"
|
||||
echo "**操作:** [查看运行详情](https://github.com/$GH_REPO/actions/runs/$GH_RUN_ID)"
|
||||
echo ""
|
||||
|
||||
if [ "$IS_READY_FOR_REVIEW" = "true" ]; then
|
||||
echo "> 📋 此 PR 已标记为 **准备审查**,正在进行构建验证"
|
||||
fi
|
||||
|
||||
echo "---"
|
||||
echo "<!-- github-action-pr-build -->"
|
||||
echo "<!-- build-id: $GH_RUN_ID -->"
|
||||
echo "<!-- event-type: ${{ github.event.action }} -->"
|
||||
echo "<!-- pr-head-sha: $PR_HEAD_SHA -->"
|
||||
echo "<!-- merge-sha: ${{ github.sha }} -->"
|
||||
echo ""
|
||||
echo "🤖 构建完成后,状态将自动更新"
|
||||
} > preview_comment.txt
|
||||
|
||||
preview_content=$(cat preview_comment.txt)
|
||||
echo "preview_body<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$preview_content" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore "Ink Canvas.sln"
|
||||
- name: Post/Update Preview Comment
|
||||
id: create-preview-comment
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ needs.find-or-create-pr-comment.outputs.comment_id }}
|
||||
body: ${{ steps.prepare-preview.outputs.preview_body }}
|
||||
edit-mode: replace
|
||||
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
msbuild -t:restore /p:GitFlow="Github Action"
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Debug" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
|
||||
build-and-package:
|
||||
name: Build & Package
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
archive_name: ${{ steps.create-archive.outputs.archive_name }}
|
||||
build_result: ${{ steps.check-exe.outputs.build_success }}
|
||||
artifact_url: ${{ steps.upload-artifact.outputs.artifact-url }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Upload to artifact
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: InkCanvasForClass
|
||||
path: "Ink Canvas/bin/Debug/net472"
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Cache NuGet global packages
|
||||
id: cache-nuget
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
~\AppData\Local\NuGet\Cache
|
||||
key: ${{ runner.os }}-nuget-v2-${{ hashFiles('**/*.csproj', '**/packages.config', '**/*.sln', '**/nuget.config') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-v2-
|
||||
|
||||
|
||||
|
||||
# Removed caching of obj/ folders to avoid cross-version incremental build issues
|
||||
# NuGet packages will still be cached; always run restore to ensure packages are present
|
||||
- name: Restore NuGet packages
|
||||
run: |
|
||||
Write-Host "📥 正在恢复 NuGet 包(始终运行以确保依赖可用)..." -ForegroundColor Yellow
|
||||
|
||||
# 恢复解决方案级别的包
|
||||
nuget restore "Ink Canvas.sln" -Verbosity minimal
|
||||
|
||||
# 恢复项目级别的包(兼容 packages.config)
|
||||
msbuild -t:restore "Ink Canvas/InkCanvasForClass.csproj" /p:GitFlow="Github Action" /p:RestorePackagesConfig=true /verbosity:minimal
|
||||
|
||||
Write-Host "✅ NuGet 包恢复完成" -ForegroundColor Green
|
||||
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
Write-Host "🔨 正在构建项目..." -ForegroundColor Cyan
|
||||
|
||||
# 如果是 ready_for_review 事件,添加特殊标记
|
||||
if ("${{ github.event.action }}" -eq "ready_for_review") {
|
||||
Write-Host "🚀 PR 准备审查状态构建 - 进行完整验证" -ForegroundColor Magenta
|
||||
$GITFLOW = "Github Action - Ready For Review"
|
||||
} else {
|
||||
$GITFLOW = "Github Action"
|
||||
}
|
||||
|
||||
# 执行构建
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Debug" /p:GitFlow="$GITFLOW" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal
|
||||
|
||||
Write-Host "🏗️ 构建命令执行完成" -ForegroundColor Cyan
|
||||
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
Write-Host "🔍 检查是否生成可执行文件..." -ForegroundColor Cyan
|
||||
|
||||
$exePath = "Ink Canvas\bin\Debug\net472\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
Write-Host "✅ 找到可执行文件: $exePath" -ForegroundColor Green
|
||||
$fileInfo = Get-Item $exePath
|
||||
Write-Host " 文件大小: $($fileInfo.Length) 字节" -ForegroundColor Gray
|
||||
Write-Host " 创建时间: $($fileInfo.CreationTime)" -ForegroundColor Gray
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
Write-Host "❌ 未找到可执行文件: $exePath" -ForegroundColor Red
|
||||
Write-Host " 检查目录内容:" -ForegroundColor Yellow
|
||||
if (Test-Path "Ink Canvas\bin\Debug\net472\") {
|
||||
Get-ChildItem "Ink Canvas\bin\Debug\net472\" -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Write-Host " - $($_.Name)" -ForegroundColor Gray
|
||||
}
|
||||
} else {
|
||||
Write-Host " bin\Debug\net472 目录不存在" -ForegroundColor Red
|
||||
}
|
||||
echo "build_success=false" >> $env:GITHUB_OUTPUT
|
||||
|
||||
# 如果是直接触发,则抛出错误
|
||||
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
|
||||
Write-Host "🚨 工作流手动触发 - 构建失败,抛出错误!" -ForegroundColor Red -BackgroundColor Black
|
||||
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"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.zip"
|
||||
Write-Host "📦 正在创建归档包: $archiveName" -ForegroundColor Cyan
|
||||
Write-Host " 使用合并提交哈希: $env:GITHUB_SHA" -ForegroundColor Gray
|
||||
Compress-Archive -Path "Ink Canvas\bin\Debug\net472\*" -DestinationPath $archiveName -Force
|
||||
echo "archive_name=$archiveName" >> $env:GITHUB_OUTPUT
|
||||
Write-Host "✅ 已创建归档包: $archiveName" -ForegroundColor Green
|
||||
|
||||
- name: Upload Artifact (if build succeeded)
|
||||
id: upload-artifact
|
||||
if: steps.check-exe.outputs.build_success == 'true'
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: app-package
|
||||
path: "*.zip"
|
||||
|
||||
pr-check-comment:
|
||||
name: PR Check & Comment (Final)
|
||||
needs: [build-and-package, pr-preview-comment]
|
||||
if: always() && github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Prepare Final Comment Content
|
||||
id: prepare-final-comment
|
||||
run: |
|
||||
# 从构建作业获取构建结果
|
||||
BUILD_SUCCESS="${{ needs.build-and-package.outputs.build_result }}"
|
||||
ARTIFACT_URL="${{ needs.build-and-package.outputs.artifact_url }}"
|
||||
|
||||
# 使用 PR 分支的实际提交哈希
|
||||
PR_HEAD_SHA="${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
# 确定构建状态
|
||||
if [ "$BUILD_SUCCESS" = "true" ]; then
|
||||
STATUS_ICON="✅"
|
||||
STATUS_TEXT="构建成功"
|
||||
COLOR="#00d26a"
|
||||
else
|
||||
STATUS_ICON="❌"
|
||||
STATUS_TEXT="构建失败"
|
||||
COLOR="#f85149"
|
||||
fi
|
||||
|
||||
# 检查是否是 ready_for_review 事件
|
||||
READY_FOR_REVIEW="${{ github.event.action == 'ready_for_review' }}"
|
||||
|
||||
# 构建最终评论内容
|
||||
{
|
||||
if [ "$READY_FOR_REVIEW" = "true" ]; then
|
||||
echo "# 🚀 PR 准备审查 - 构建结果"
|
||||
echo ""
|
||||
echo "> 📋 此 PR 已标记为 **准备审查**,构建验证完成"
|
||||
echo ""
|
||||
else
|
||||
echo "# 📋 构建结果摘要"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "## 本次构建状态"
|
||||
echo ""
|
||||
echo "| 项目 | 结果 |"
|
||||
echo "|------|------|"
|
||||
echo "| 状态 | $STATUS_ICON **$STATUS_TEXT** |"
|
||||
echo "| 事件类型 | \`${{ github.event.action }}\` |"
|
||||
echo "| 分支提交 | \`$PR_HEAD_SHA\` |"
|
||||
echo "| 工作流 | [运行 #${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |"
|
||||
|
||||
# 如果有构建产物,显示下载链接
|
||||
if [ "$BUILD_SUCCESS" = "true" ]; then
|
||||
NIGHTLY_LINK="https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/app-package.zip"
|
||||
echo ""
|
||||
echo "## 构建产物"
|
||||
if [ -n "$ARTIFACT_URL" ]; then
|
||||
echo "- 📦 [下载构建产物]($ARTIFACT_URL)"
|
||||
else
|
||||
echo "- 📦 [下载构建产物](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
fi
|
||||
echo "- 🌙 [直链下载 (nightly.link)]($NIGHTLY_LINK)"
|
||||
|
||||
if [ "$READY_FOR_REVIEW" = "true" ]; then
|
||||
echo ""
|
||||
echo "## 🎉 审查建议"
|
||||
echo "- ✅ 构建验证通过,代码可以正常编译"
|
||||
echo "- 🔍 请进行代码审查"
|
||||
echo "- 🧪 建议测试构建产物功能"
|
||||
fi
|
||||
else
|
||||
if [ "$READY_FOR_REVIEW" = "true" ]; then
|
||||
echo ""
|
||||
echo "## ⚠️ 审查阻塞"
|
||||
echo "- ❌ 构建失败,需要修复后才能继续审查"
|
||||
echo "- 🔧 请检查构建错误并修复"
|
||||
echo "- 📝 修复后重新标记为准备审查"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "---"
|
||||
echo "<!-- github-action-pr-build -->"
|
||||
echo "<!-- build-id: ${{ github.run_id }} -->"
|
||||
echo "<!-- event-type: ${{ github.event.action }} -->"
|
||||
echo "<!-- pr-head-sha: $PR_HEAD_SHA -->"
|
||||
echo "<!-- merge-sha: ${{ github.sha }} -->"
|
||||
echo ""
|
||||
echo "<sub>🤖 GitHub Actions 自动生成 • 最后更新: $(date -u +'%Y-%m-%d %H:%M:%S UTC')</sub>"
|
||||
} > final_comment.txt
|
||||
|
||||
# 输出多行内容
|
||||
final_content=$(cat final_comment.txt)
|
||||
echo "final_body<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$final_content" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "最终评论内容已生成"
|
||||
|
||||
- name: Update Final Comment
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ needs.pr-preview-comment.outputs.comment_id }}
|
||||
body: ${{ steps.prepare-final-comment.outputs.final_body }}
|
||||
edit-mode: replace
|
||||
|
||||
final-check:
|
||||
name: Final Check (Manual Trigger)
|
||||
if: always() && github.event_name == 'workflow_dispatch'
|
||||
needs: [build-and-package]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Build Result
|
||||
id: check-build
|
||||
run: |
|
||||
BUILD_SUCCESS="${{ needs.build-and-package.outputs.build_result }}"
|
||||
|
||||
if [ "$BUILD_SUCCESS" = "true" ]; then
|
||||
echo "✅ 构建成功 - 工作流完成"
|
||||
echo "status=success" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ 构建失败 - 抛出错误"
|
||||
echo "status=failure" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create Summary (if successful)
|
||||
if: steps.check-build.outputs.status == 'success'
|
||||
run: |
|
||||
echo "# 🎉 手动构建完成" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**构建状态:** ✅ 成功" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**提交:** \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**运行编号:** #${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[📦 下载构建产物](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**直链下载 (nightly.link):**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[🌙 nightly.link 下载链接](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/app-package.zip)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
+553
-275
@@ -1,6 +1,9 @@
|
||||
name: Pre-release and Changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_type:
|
||||
@@ -12,291 +15,566 @@ on:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
- build
|
||||
prerelease:
|
||||
description: 'Create as pre-release'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-${{ github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
prepare:
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
tag_name: ${{ steps.get_tag.outputs.tag_name }}
|
||||
version: ${{ steps.get_tag.outputs.version }}
|
||||
is_prerelease: ${{ steps.release_type.outputs.is_prerelease }}
|
||||
changelog: ${{ steps.read_changelog.outputs.changelog }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
# ========== 获取当前版本 ==========
|
||||
- name: Get current version from Git tag
|
||||
id: get_version
|
||||
run: |
|
||||
# 获取最新的tag
|
||||
$latestTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($latestTag) {
|
||||
$version = $latestTag
|
||||
echo "Found latest tag: $latestTag"
|
||||
} else {
|
||||
# 如果没有tag,使用默认值
|
||||
$version = "1.0.0.0"
|
||||
echo "No tag found, using default version: $version"
|
||||
}
|
||||
echo "current_version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "Current version: $version"
|
||||
|
||||
# ========== 处理版本号和标签名 ==========
|
||||
- name: Get tag name and version
|
||||
id: get_tag
|
||||
run: |
|
||||
if ("${{ github.event_name }}" -eq "push") {
|
||||
# 从 push tag 事件获取原始标签名
|
||||
$tagName = "${{ github.ref }}".Replace("refs/tags/", "")
|
||||
$cleanVersion = $tagName
|
||||
|
||||
echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT
|
||||
echo "version=$cleanVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "Using pushed tag: $tagName, version: $cleanVersion"
|
||||
} else {
|
||||
# 从 workflow_dispatch 计算新版本(4位格式)
|
||||
$currentVersion = "${{ steps.get_version.outputs.current_version }}"
|
||||
$versionParts = $currentVersion.Split('.')
|
||||
|
||||
# 确保版本号格式正确(至少4部分)
|
||||
if ($versionParts.Length -ge 4) {
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
$build = [int]$versionParts[3]
|
||||
} else {
|
||||
# 如果版本号格式不正确,补充为4位
|
||||
if ($versionParts.Length -ge 3) {
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
$build = 0
|
||||
} else {
|
||||
# 如果版本号格式不正确,抛出错误
|
||||
echo "Error: Invalid version format. Expected format: x.y.z.w (e.g., 1.7.18.0)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$versionType = "${{ github.event.inputs.version_type }}"
|
||||
$isPrerelease = "${{ github.event.inputs.prerelease }}" -eq "true"
|
||||
|
||||
switch ($versionType) {
|
||||
"major" {
|
||||
$major++
|
||||
$minor = 0
|
||||
$patch = 0
|
||||
$build = 0
|
||||
}
|
||||
"minor" {
|
||||
$minor++
|
||||
$patch = 0
|
||||
$build = 0
|
||||
}
|
||||
"patch" {
|
||||
$patch++
|
||||
$build = 0
|
||||
}
|
||||
"build" {
|
||||
$build++
|
||||
}
|
||||
}
|
||||
|
||||
# 生成新版本号(4位格式,如1.7.18.0)
|
||||
$newVersion = "$major.$minor.$patch.$build"
|
||||
|
||||
# 根据是否为预发布决定版本号最后一位
|
||||
# 如果是预发布,确保最后一位不为0(使用1)
|
||||
if ($isPrerelease -and $build -eq 0) {
|
||||
$build = 1
|
||||
$newVersion = "$major.$minor.$patch.$build"
|
||||
}
|
||||
$tagName = $newVersion
|
||||
|
||||
echo "tag_name=$tagName" >> $env:GITHUB_OUTPUT
|
||||
echo "version=$newVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "New tag: $tagName, version: $newVersion"
|
||||
}
|
||||
|
||||
- name: Determine release type
|
||||
id: release_type
|
||||
run: |
|
||||
if ("${{ github.event_name }}" -eq "push") {
|
||||
# 根据版本号最后一位确定是否为预发布版本
|
||||
# 最后一位为0表示正式版本,非0表示预发布版本
|
||||
$version = "${{ steps.get_tag.outputs.version }}"
|
||||
$versionParts = $version.Split('.')
|
||||
if ($versionParts.Length -ge 4) {
|
||||
$build = [int]$versionParts[3]
|
||||
if ($build -eq 0) {
|
||||
echo "is_prerelease=false" >> $env:GITHUB_OUTPUT
|
||||
echo "This is a release"
|
||||
} else {
|
||||
echo "is_prerelease=true" >> $env:GITHUB_OUTPUT
|
||||
echo "This is a pre-release (beta)"
|
||||
}
|
||||
} else {
|
||||
echo "is_prerelease=false" >> $env:GITHUB_OUTPUT
|
||||
echo "This is a release (invalid version format)"
|
||||
}
|
||||
} else {
|
||||
# workflow_dispatch 方式
|
||||
echo "is_prerelease=${{ github.event.inputs.prerelease }}" >> $env:GITHUB_OUTPUT
|
||||
}
|
||||
|
||||
# ========== 使用 git-cliff 生成变更日志 ==========
|
||||
- name: Generate changelog with git-cliff (for pushed tag)
|
||||
if: github.event_name == 'push'
|
||||
id: git_cliff_tag
|
||||
uses: orhun/git-cliff-action@v4
|
||||
with:
|
||||
config: build/cliff.toml # 使用项目build目录的 cliff.toml 配置
|
||||
args: --latest --tag ${{ steps.get_tag.outputs.tag_name }} --output CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate changelog with git-cliff (for workflow_dispatch)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
id: git_cliff_unreleased
|
||||
uses: orhun/git-cliff-action@v4
|
||||
with:
|
||||
config: build/cliff.toml
|
||||
args: --unreleased --tag ${{ steps.get_tag.outputs.tag_name }} --output CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Read changelog content
|
||||
id: read_changelog
|
||||
run: |
|
||||
$changelogContent = Get-Content -Path CHANGELOG.md -Raw
|
||||
echo "changelog<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $changelogContent >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
build:
|
||||
needs: prepare
|
||||
if: success()
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
archive_name: ${{ steps.create_archive.outputs.archive_name }}
|
||||
zip_size: ${{ steps.calculate_size.outputs.zip_size }}
|
||||
zip_hash: ${{ steps.calculate_size.outputs.zip_hash }}
|
||||
installer_size: ${{ steps.calculate_installer_size.outputs.installer_size }}
|
||||
installer_hash: ${{ steps.calculate_installer_size.outputs.installer_hash }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Install Inno Setup Unofficial Language Files
|
||||
run: |
|
||||
# 创建临时目录用于下载文件
|
||||
New-Item -ItemType Directory -Path "temp_lang" -Force
|
||||
|
||||
# 下载英语英国版语言文件
|
||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/refs/heads/main/Files/Languages/Unofficial/EnglishBritish.isl" -OutFile "temp_lang\EnglishBritish.isl"
|
||||
|
||||
# 下载简体中文版语言文件
|
||||
Invoke-WebRequest -Uri "https://github.com/jrsoftware/issrc/raw/refs/heads/main/Files/Languages/Unofficial/ChineseSimplified.isl" -OutFile "temp_lang\ChineseSimplified.isl"
|
||||
|
||||
# 将文件移动到 Inno Setup 的语言目录
|
||||
Move-Item -Path "temp_lang\EnglishBritish.isl" -Destination "C:\Program Files (x86)\Inno Setup 6\Languages\EnglishBritish.isl" -Force
|
||||
Move-Item -Path "temp_lang\ChineseSimplified.isl" -Destination "C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl" -Force
|
||||
|
||||
# 清理临时目录
|
||||
Remove-Item -Path "temp_lang" -Recurse -Force
|
||||
|
||||
Write-Host "✅ Inno Setup unofficial language files installed successfully" -ForegroundColor Green
|
||||
|
||||
- name: Cache NuGet packages and obj files
|
||||
id: cache-nuget
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
# NuGet 全局包缓存(已下载的包)
|
||||
~/.nuget/packages
|
||||
# NuGet 缓存目录(包索引)
|
||||
~\AppData\Local\NuGet\Cache
|
||||
# 项目 obj 目录(包含 project.assets.json)
|
||||
Ink Canvas/obj/
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.config', '**/*.sln') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
- name: Restore NuGet packages (if cache missed)
|
||||
if: steps.cache-nuget.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
Write-Host "📥 缓存未命中,正在恢复 NuGet 包..." -ForegroundColor Yellow
|
||||
|
||||
# 恢复解决方案级别的包
|
||||
nuget restore "Ink Canvas.sln" -Verbosity minimal
|
||||
|
||||
# 恢复项目级别的包
|
||||
msbuild -t:restore "Ink Canvas/InkCanvasForClass.csproj" /p:GitFlow="Github Action" /p:RestorePackagesConfig=true /verbosity:minimal
|
||||
|
||||
Write-Host "✅ NuGet 包恢复完成" -ForegroundColor Green
|
||||
|
||||
- name: Display version info
|
||||
run: |
|
||||
echo "Building version: ${{ needs.prepare.outputs.version }}"
|
||||
echo "Tag: ${{ needs.prepare.outputs.tag_name }}"
|
||||
echo "Release type: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'Pre-release' || 'Release' }}"
|
||||
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
Write-Host "🔨 正在构建项目..." -ForegroundColor Cyan
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" /m /p:UseMultiToolTask=true /p:EnforceProcessCountAcrossBuilds=true /verbosity:minimal
|
||||
|
||||
Write-Host "🏗️ 构建命令执行完成" -ForegroundColor Cyan
|
||||
|
||||
- name: Create Release Archive
|
||||
id: create_archive
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.zip"
|
||||
|
||||
# 创建发布目录
|
||||
New-Item -ItemType Directory -Path "release" -Force
|
||||
|
||||
# 复制发布文件
|
||||
Copy-Item "Ink Canvas\bin\Release\net472\*" "release\" -Recurse -Force
|
||||
|
||||
# 创建压缩包
|
||||
Compress-Archive -Path "release\*" -DestinationPath $archiveName -Force
|
||||
|
||||
echo "archive_name=$archiveName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Prepare Inno Setup script
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
|
||||
# 更新 ISS 文件中的版本信息
|
||||
$issPath = "build\InkCanvasForClass CE.iss"
|
||||
$issContent = Get-Content -Path $issPath -Raw
|
||||
|
||||
# 替换版本信息
|
||||
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
|
||||
|
||||
# 替换源文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
||||
$issContent = $issContent -replace 'Source: ".*\\{#MyAppExeName}";', 'Source: "..\release\{#MyAppExeName}";'
|
||||
$issContent = $issContent -replace 'Source: ".*\\InkCanvasForClass.exe.config";', 'Source: "..\release\InkCanvasForClass.exe.config";'
|
||||
|
||||
# 更新输出目录为当前目录
|
||||
$issContent = $issContent -replace 'OutputDir=.*', 'OutputDir=.'
|
||||
|
||||
# 更新默认安装目录
|
||||
$issContent = $issContent -replace 'DefaultDirName=.*', 'DefaultDirName={autopf}\{#MyAppName}'
|
||||
|
||||
# 更新许可证文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
||||
$issContent = $issContent -replace 'LicenseFile=.*', 'LicenseFile=..\LICENSE'
|
||||
|
||||
# 保存修改后的 ISS 文件
|
||||
$issContent | Set-Content -Path $issPath -Encoding UTF8
|
||||
|
||||
# 显示修改后的 ISS 文件内容
|
||||
Write-Host "Modified ISS file content:"
|
||||
Write-Host $issContent
|
||||
|
||||
- name: Build MSI installer with Inno Setup
|
||||
uses: Minionguyjpro/Inno-Setup-Action@v1.2.2
|
||||
with:
|
||||
path: build\InkCanvasForClass CE.iss
|
||||
options: /O.
|
||||
|
||||
- name: Rename installer file
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$setupFile = "InkCanvasForClass CE Setup.exe"
|
||||
$newSetupName = "InkCanvasForClass.CE.$version.Setup.exe"
|
||||
|
||||
if (Test-Path $setupFile) {
|
||||
Rename-Item -Path $setupFile -NewName $newSetupName
|
||||
Write-Host "Renamed setup file to: $newSetupName"
|
||||
} else {
|
||||
Write-Host "Setup file not found: $setupFile"
|
||||
}
|
||||
|
||||
- name: Calculate archive size and hash
|
||||
id: calculate_size
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.zip"
|
||||
|
||||
# 获取文件大小(字节)
|
||||
$fileSize = (Get-Item $archiveName).Length
|
||||
|
||||
# 计算SHA256哈希
|
||||
$hash = (Get-FileHash $archiveName -Algorithm SHA256).Hash
|
||||
|
||||
echo "zip_size=$fileSize" >> $env:GITHUB_OUTPUT
|
||||
echo "zip_hash=$hash" >> $env:GITHUB_OUTPUT
|
||||
|
||||
echo "Archive size: $fileSize bytes"
|
||||
echo "SHA256 hash: $hash"
|
||||
|
||||
- name: Calculate installer size and hash
|
||||
id: calculate_installer_size
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$installerName = "InkCanvasForClass.CE.$version.Setup.exe"
|
||||
|
||||
if (Test-Path $installerName) {
|
||||
# 获取文件大小(字节)
|
||||
$fileSize = (Get-Item $installerName).Length
|
||||
|
||||
# 计算SHA256哈希
|
||||
$hash = (Get-FileHash $installerName -Algorithm SHA256).Hash
|
||||
|
||||
echo "installer_size=$fileSize" >> $env:GITHUB_OUTPUT
|
||||
echo "installer_hash=$hash" >> $env:GITHUB_OUTPUT
|
||||
|
||||
echo "Installer size: $fileSize bytes"
|
||||
echo "SHA256 hash: $hash"
|
||||
} else {
|
||||
echo "Installer file not found: $installerName"
|
||||
}
|
||||
|
||||
- name: Upload Build Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
path: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
|
||||
sign:
|
||||
needs: [prepare, build]
|
||||
if: success()
|
||||
runs-on: ubuntu-latest # 改为 Ubuntu 以使用 Python 签名工具
|
||||
outputs:
|
||||
signatures_created: ${{ steps.sign_artifacts.outputs.signatures_created }}
|
||||
zip_sigstore_file: "InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json"
|
||||
installer_sigstore_file: "InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe.sigstore.json"
|
||||
zip_sigstore_hash: ${{ steps.calculate_zip_sig_hash.outputs.sigstore_hash }}
|
||||
installer_sigstore_hash: ${{ steps.calculate_installer_sig_hash.outputs.sigstore_hash }}
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write # 需要这个权限来验证签名
|
||||
steps:
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Sign release artifacts with sigstore-python
|
||||
id: sign_artifacts
|
||||
uses: sigstore/gh-action-sigstore-python@v3.2.0
|
||||
with:
|
||||
inputs: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
release-signing-artifacts: true
|
||||
upload-signing-artifacts: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check generated signature files
|
||||
run: |
|
||||
version="${{ needs.prepare.outputs.version }}"
|
||||
echo "Checking for generated signature files..."
|
||||
ls -la *.sig* || true
|
||||
echo "Current directory contents:"
|
||||
pwd
|
||||
ls -la
|
||||
|
||||
- name: Calculate ZIP signature hash
|
||||
id: calculate_zip_sig_hash
|
||||
run: |
|
||||
version="${{ needs.prepare.outputs.version }}"
|
||||
sigstoreFile="InkCanvasForClass.CE.$version.zip.sigstore.json"
|
||||
|
||||
if [ -f "$sigstoreFile" ]; then
|
||||
# 计算SHA256哈希
|
||||
sigstoreHash=$(sha256sum "$sigstoreFile" | cut -d' ' -f1)
|
||||
|
||||
echo "sigstore_hash=$sigstoreHash" >> $GITHUB_OUTPUT
|
||||
echo "Sigstore JSON file hash: $sigstoreHash"
|
||||
echo "Sigstore file size: $(stat -c%s "$sigstoreFile") bytes"
|
||||
else
|
||||
echo "Warning: Sigstore file not found: $sigstoreFile"
|
||||
echo "sigstore_hash=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Calculate Installer signature hash
|
||||
id: calculate_installer_sig_hash
|
||||
run: |
|
||||
version="${{ needs.prepare.outputs.version }}"
|
||||
sigstoreFile="InkCanvasForClass.CE.$version.Setup.exe.sigstore.json"
|
||||
|
||||
if [ -f "$sigstoreFile" ]; then
|
||||
# 计算SHA256哈希
|
||||
sigstoreHash=$(sha256sum "$sigstoreFile" | cut -d' ' -f1)
|
||||
|
||||
echo "sigstore_hash=$sigstoreHash" >> $GITHUB_OUTPUT
|
||||
echo "Sigstore JSON file hash: $sigstoreHash"
|
||||
echo "Sigstore file size: $(stat -c%s "$sigstoreFile") bytes"
|
||||
else
|
||||
echo "Warning: Sigstore file not found: $sigstoreFile"
|
||||
echo "sigstore_hash=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Upload Signed Artifacts
|
||||
if: steps.calculate_zip_sig_hash.outputs.sigstore_hash != '' || steps.calculate_installer_sig_hash.outputs.sigstore_hash != ''
|
||||
uses: actions/upload-artifact@v4
|
||||
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
|
||||
|
||||
release:
|
||||
needs: [prepare, build, sign]
|
||||
if: success()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
fetch-depth: 0 # 获取所有历史记录用于生成changelog
|
||||
|
||||
- name: Setup MSbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore "Ink Canvas.sln"
|
||||
|
||||
- name: Get current version from Git tag
|
||||
id: get_version
|
||||
run: |
|
||||
# 获取最新的tag
|
||||
$latestTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($latestTag) {
|
||||
# 移除v前缀(如果有的话)
|
||||
$version = $latestTag -replace "^v", ""
|
||||
echo "Found latest tag: $latestTag"
|
||||
} else {
|
||||
# 如果没有tag,使用默认版本
|
||||
$version = "1.0.0"
|
||||
echo "No tags found, using default version"
|
||||
}
|
||||
echo "current_version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "Current version: $version"
|
||||
|
||||
- name: Calculate new version
|
||||
id: calc_version
|
||||
run: |
|
||||
$currentVersion = "${{ steps.get_version.outputs.current_version }}"
|
||||
$versionParts = $currentVersion.Split('.')
|
||||
|
||||
# 确保版本号格式正确(至少3部分)
|
||||
if ($versionParts.Length -ge 3) {
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
} else {
|
||||
# 如果版本号格式不正确,使用默认值
|
||||
$major = 1
|
||||
$minor = 0
|
||||
$patch = 0
|
||||
}
|
||||
|
||||
$versionType = "${{ github.event.inputs.version_type }}"
|
||||
|
||||
switch ($versionType) {
|
||||
"major" {
|
||||
$major++
|
||||
$minor = 0
|
||||
$patch = 0
|
||||
}
|
||||
"minor" {
|
||||
$minor++
|
||||
$patch = 0
|
||||
}
|
||||
"patch" {
|
||||
$patch++
|
||||
}
|
||||
}
|
||||
|
||||
# 生成新版本号(保持3位格式,如1.7.13)
|
||||
$newVersion = "$major.$minor.$patch"
|
||||
echo "new_version=$newVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "New version: $newVersion"
|
||||
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
run: |
|
||||
# 获取上次tag到现在的所有commit
|
||||
$lastTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($lastTag) {
|
||||
$commits = git log --pretty=format:"%h|%s|%an|%ad" --date=short "$lastTag..HEAD"
|
||||
} else {
|
||||
$commits = git log --pretty=format:"%h|%s|%an|%ad" --date=short
|
||||
}
|
||||
|
||||
# 初始化分类数组
|
||||
$fixes = @()
|
||||
$improvements = @()
|
||||
$additions = @()
|
||||
$deletions = @()
|
||||
$versionChanges = @()
|
||||
$others = @()
|
||||
|
||||
# 解析每个commit
|
||||
foreach ($commit in $commits) {
|
||||
if ($commit -match "^([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)$") {
|
||||
$hash = $matches[1]
|
||||
$message = $matches[2]
|
||||
$author = $matches[3]
|
||||
$date = $matches[4]
|
||||
|
||||
$commitInfo = @{
|
||||
Hash = $hash
|
||||
Message = $message
|
||||
Author = $author
|
||||
Date = $date
|
||||
}
|
||||
|
||||
# 根据commit消息分类
|
||||
if ($message -match "^(fix|修复)") {
|
||||
$fixes += $commitInfo
|
||||
} elseif ($message -match "^(improve|改进|优化)") {
|
||||
$improvements += $commitInfo
|
||||
} elseif ($message -match "^(add|新增|添加)") {
|
||||
$additions += $commitInfo
|
||||
} elseif ($message -match "^(delete|删除|移除)") {
|
||||
$deletions += $commitInfo
|
||||
} elseif ($message -match "(版本|version|更新版本号)") {
|
||||
$versionChanges += $commitInfo
|
||||
} else {
|
||||
$others += $commitInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 生成changelog内容
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
$changelog = "# ICC CE $version.0 更新日志`n`n## 修复 (Fixes)"
|
||||
|
||||
if ($fixes.Count -gt 0) {
|
||||
foreach ($fix in $fixes) {
|
||||
$changelog += "`n- $($fix.Message) ($($fix.Author), $($fix.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 改进 (Improvements)"
|
||||
|
||||
if ($improvements.Count -gt 0) {
|
||||
foreach ($improvement in $improvements) {
|
||||
$changelog += "`n- $($improvement.Message) ($($improvement.Author), $($improvement.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 新增功能 (New Features)"
|
||||
|
||||
if ($additions.Count -gt 0) {
|
||||
foreach ($addition in $additions) {
|
||||
$changelog += "`n- $($addition.Message) ($($addition.Author), $($addition.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 删除功能 (Removed Features)"
|
||||
|
||||
if ($deletions.Count -gt 0) {
|
||||
foreach ($deletion in $deletions) {
|
||||
$changelog += "`n- $($deletion.Message) ($($deletion.Author), $($deletion.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 版本更新 (Version Updates)"
|
||||
|
||||
if ($versionChanges.Count -gt 0) {
|
||||
foreach ($versionChange in $versionChanges) {
|
||||
$changelog += "`n- $($versionChange.Message) ($($versionChange.Author), $($versionChange.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 其他更改 (Other Changes)"
|
||||
|
||||
if ($others.Count -gt 0) {
|
||||
foreach ($other in $others) {
|
||||
$changelog += "`n- $($other.Message) ($($other.Author), $($other.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n---`n*此更新日志由GitHub Actions自动生成*"
|
||||
|
||||
# 保存changelog到文件
|
||||
$changelog | Out-File -FilePath "CHANGELOG_${{ steps.calc_version.outputs.new_version }}.md" -Encoding UTF8
|
||||
|
||||
# 输出changelog内容到步骤输出
|
||||
echo "changelog<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $changelog >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
echo "Changelog generated successfully"
|
||||
|
||||
- name: Display version info
|
||||
run: |
|
||||
echo "Current version: ${{ steps.get_version.outputs.current_version }}"
|
||||
echo "New version: ${{ steps.calc_version.outputs.new_version }}"
|
||||
echo "Note: Version will not be automatically updated in repository files"
|
||||
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
msbuild -t:restore /p:GitFlow="Github Action"
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
|
||||
|
||||
- name: Create Release Archive
|
||||
run: |
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.0.zip"
|
||||
|
||||
# 创建发布目录
|
||||
New-Item -ItemType Directory -Path "release" -Force
|
||||
|
||||
# 复制发布文件
|
||||
Copy-Item "Ink Canvas\bin\Release\net472\*" "release\" -Recurse -Force
|
||||
|
||||
# 创建压缩包
|
||||
Compress-Archive -Path "release\*" -DestinationPath $archiveName -Force
|
||||
|
||||
echo "archive_name=$archiveName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload Release Assets
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: release-files-${{ steps.calc_version.outputs.new_version }}
|
||||
path: |
|
||||
InkCanvasForClass.CE.${{ steps.calc_version.outputs.new_version }}.0.zip
|
||||
CHANGELOG_${{ steps.calc_version.outputs.new_version }}.md
|
||||
- name: Download Build Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-files-${{ needs.prepare.outputs.version }}
|
||||
|
||||
- name: Prepare Release Info
|
||||
run: |
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
echo "Preparing release for version: $version"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.calc_version.outputs.new_version }}.0
|
||||
name: ICC CE ${{ steps.calc_version.outputs.new_version }}.0
|
||||
body: |
|
||||
${{ steps.changelog.outputs.changelog }}
|
||||
draft: false
|
||||
prerelease: ${{ github.event.inputs.prerelease }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ steps.calc_version.outputs.new_version }}.0.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate UpdateLog preview
|
||||
run: |
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
$changelogFile = "CHANGELOG_$version.md"
|
||||
|
||||
# 读取生成的changelog
|
||||
$changelogContent = Get-Content $changelogFile -Raw
|
||||
|
||||
# 生成预览内容
|
||||
$previewContent = "ICC CE $version.0 更新日志`n" + $changelogContent
|
||||
|
||||
echo "UpdateLog preview generated (not written to file):"
|
||||
echo $previewContent
|
||||
|
||||
- name: Display Summary
|
||||
run: |
|
||||
echo "=== Release Summary ==="
|
||||
echo "Version: ${{ steps.calc_version.outputs.new_version }}"
|
||||
echo "Pre-release: ${{ github.event.inputs.prerelease }}"
|
||||
echo "Changelog: Generated and attached to release"
|
||||
echo "Archive: ICC_CE_${{ steps.calc_version.outputs.new_version }}.zip"
|
||||
echo ""
|
||||
echo "Note: No repository files were modified"
|
||||
echo "You can manually update version numbers and changelog as needed"
|
||||
- name: Download Signed Artifacts (if exists)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: signed-files-${{ needs.prepare.outputs.version }}
|
||||
continue-on-error: true
|
||||
|
||||
|
||||
- name: Create enhanced changelog with file table
|
||||
id: enhanced_changelog
|
||||
run: |
|
||||
version="${{ needs.prepare.outputs.version }}"
|
||||
|
||||
# 读取git-cliff生成的changelog内容
|
||||
originalChangelog="${{ needs.prepare.outputs.changelog }}"
|
||||
|
||||
# 构建文件信息表格
|
||||
fileTable=$'\n## 文件信息 (File Information)\n'
|
||||
fileTable+=$'| 文件名 | 大小 | SHA256 哈希 |\n'
|
||||
fileTable+=$'|--------|------|-------------|\n'
|
||||
|
||||
# ZIP 文件信息
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"
|
||||
fileTable+=$'.zip | ${{ needs.build.outputs.zip_size }} bytes | ${{ needs.build.outputs.zip_hash }} |\n'
|
||||
|
||||
# 安装包文件信息
|
||||
installerSize="${{ needs.build.outputs.installer_size }}"
|
||||
installerHash="${{ needs.build.outputs.installer_hash }}"
|
||||
if [ -n "$installerSize" ] && [ -n "$installerHash" ]; then
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe | '"$installerSize"' bytes | '"$installerHash"
|
||||
fileTable+=$' |\n'
|
||||
fi
|
||||
|
||||
# 检查是否有签名文件
|
||||
if [ -f "InkCanvasForClass.CE.$version.zip.sigstore.json" ]; then
|
||||
sigstoreSize=$(stat -c%s "InkCanvasForClass.CE.$version.zip.sigstore.json")
|
||||
sigstoreHash=$(sha256sum "InkCanvasForClass.CE.$version.zip.sigstore.json" | cut -d' ' -f1)
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.zip.sigstore.json | '"$sigstoreSize"' bytes | '"$sigstoreHash"
|
||||
fileTable+=$' |\n'
|
||||
fi
|
||||
|
||||
# 检查安装程序签名文件
|
||||
if [ -f "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json" ]; then
|
||||
sigstoreSize=$(stat -c%s "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json")
|
||||
sigstoreHash=$(sha256sum "InkCanvasForClass.CE.$version.Setup.exe.sigstore.json" | cut -d' ' -f1)
|
||||
fileTable+=$'| InkCanvasForClass.CE.'"$version"'.Setup.exe.sigstore.json | '"$sigstoreSize"' bytes | '"$sigstoreHash"
|
||||
fileTable+=$' |\n'
|
||||
fi
|
||||
|
||||
fileTable+=$'\n*文件哈希和大小信息由GitHub Actions自动生成*\n'
|
||||
|
||||
# 将表格附加到原始changelog
|
||||
enhancedChangelog="${originalChangelog}${fileTable}"
|
||||
|
||||
# 输出增强版changelog内容
|
||||
echo "enhanced_changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$enhancedChangelog" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Enhanced changelog created with file information table"
|
||||
|
||||
- name: Display Release Info
|
||||
run: |
|
||||
echo "=== Creating Release ==="
|
||||
echo "Version: ${{ needs.prepare.outputs.version }}"
|
||||
echo "Tag: ${{ needs.prepare.outputs.tag_name }}"
|
||||
echo "Pre-release: ${{ needs.prepare.outputs.is_prerelease }}"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.prepare.outputs.tag_name }}
|
||||
name: ICC CE ${{ needs.prepare.outputs.version }}
|
||||
body: |
|
||||
${{ steps.enhanced_changelog.outputs.enhanced_changelog }}
|
||||
draft: false
|
||||
prerelease: ${{ needs.prepare.outputs.is_prerelease == 'true' }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.zip.sigstore.json
|
||||
InkCanvasForClass.CE.${{ needs.prepare.outputs.version }}.Setup.exe.sigstore.json
|
||||
fail_on_unmatched_files: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+25
-14
@@ -59,8 +59,19 @@ namespace Ink_Canvas
|
||||
private static SplashScreen _splashScreen;
|
||||
private static bool _isSplashScreenShown = false;
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int SetCurrentProcessExplicitAppUserModelID(string appId);
|
||||
|
||||
public App()
|
||||
{
|
||||
try
|
||||
{
|
||||
SetCurrentProcessExplicitAppUserModelID("InkCanvasForClass.CE");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
// 配置TLS协议以支持Windows 7
|
||||
ConfigureTlsForWindows7();
|
||||
|
||||
@@ -342,13 +353,13 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
var exception = e.ExceptionObject as Exception;
|
||||
|
||||
|
||||
if (exception is InvalidOperationException invalidOpEx)
|
||||
{
|
||||
string exceptionMessage = invalidOpEx.Message ?? "";
|
||||
string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
|
||||
|
||||
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
||||
|
||||
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
||||
exceptionMessage.Contains("because another thread owns it") ||
|
||||
exceptionStackTrace.Contains("DynamicRenderer") ||
|
||||
exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
|
||||
@@ -360,7 +371,7 @@ namespace Ink_Canvas
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string errorMessage = exception?.ToString() ?? "未知异常";
|
||||
lastErrorMessage = errorMessage;
|
||||
|
||||
@@ -376,8 +387,8 @@ namespace Ink_Canvas
|
||||
// 尝试在最后时刻记录错误
|
||||
try
|
||||
{
|
||||
string timeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
||||
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
||||
string timeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
||||
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
||||
: DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
File.AppendAllText(
|
||||
Path.Combine(crashLogFile, $"Crash_{timeStr}.txt"),
|
||||
@@ -518,8 +529,8 @@ namespace Ink_Canvas
|
||||
Directory.CreateDirectory(crashLogFile);
|
||||
}
|
||||
|
||||
string appStartTimeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
||||
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
||||
string appStartTimeStr = (appStartTime != default(DateTime) && appStartTime != DateTime.MinValue)
|
||||
? appStartTime.ToString("yyyy-MM-dd-HH-mm-ss")
|
||||
: DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
string logFileName = Path.Combine(crashLogFile, $"Crash_{appStartTimeStr}.txt");
|
||||
|
||||
@@ -575,9 +586,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
string exceptionMessage = invalidOpEx.Message ?? "";
|
||||
string exceptionStackTrace = invalidOpEx.StackTrace ?? "";
|
||||
|
||||
|
||||
// 检查是否是DynamicRenderer相关的线程访问问题
|
||||
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
||||
if (exceptionMessage.Contains("调用线程无法访问此对象") ||
|
||||
exceptionMessage.Contains("because another thread owns it") ||
|
||||
exceptionStackTrace.Contains("DynamicRenderer") ||
|
||||
exceptionStackTrace.Contains("CompositionTarget.get_RootVisual"))
|
||||
@@ -588,13 +599,13 @@ namespace Ink_Canvas
|
||||
$"检测到DynamicRenderer线程访问异常(已安全处理): {invalidOpEx.Message}",
|
||||
LogHelper.LogType.Warning
|
||||
);
|
||||
|
||||
|
||||
// 标记为已处理,不显示错误消息,不触发重启
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。");
|
||||
LogHelper.NewLog(e.Exception.ToString());
|
||||
|
||||
@@ -1133,12 +1144,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
// 主进程异常退出,自动重启前判断崩溃后操作
|
||||
SyncCrashActionFromSettings(); // 同步设置
|
||||
|
||||
|
||||
if (IsUIAccessTopMostEnabled)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
|
||||
if (CrashAction == CrashActionType.SilentRestart)
|
||||
{
|
||||
StartupCount.Increment();
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.18.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.0")]
|
||||
[assembly: AssemblyVersion("1.7.18.3")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.3")]
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Ink_Canvas.Windows;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using VerticalAlignment = System.Windows.VerticalAlignment;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
@@ -33,7 +32,7 @@ namespace Ink_Canvas.Controls
|
||||
{
|
||||
// 如果正在拖动,不触发点击事件
|
||||
if (_isDragging) return;
|
||||
|
||||
|
||||
// 打开快抽窗口
|
||||
var quickDrawWindow = new QuickDrawWindow();
|
||||
quickDrawWindow.ShowDialog();
|
||||
@@ -50,10 +49,10 @@ namespace Ink_Canvas.Controls
|
||||
private void DragArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_isDragging = false;
|
||||
|
||||
|
||||
// 记录鼠标在屏幕上的初始位置
|
||||
_dragStartPoint = this.PointToScreen(e.GetPosition(this));
|
||||
|
||||
|
||||
// 记录控件的初始位置
|
||||
var parent = this.Parent as FrameworkElement;
|
||||
if (parent != null)
|
||||
@@ -69,7 +68,7 @@ namespace Ink_Canvas.Controls
|
||||
double.IsNaN(currentMargin.Left) ? 0 : currentMargin.Left,
|
||||
double.IsNaN(currentMargin.Top) ? 0 : currentMargin.Top);
|
||||
}
|
||||
|
||||
|
||||
((UIElement)sender).CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -84,7 +83,7 @@ namespace Ink_Canvas.Controls
|
||||
// 获取鼠标在屏幕上的当前位置
|
||||
Point currentScreenPoint = this.PointToScreen(e.GetPosition(this));
|
||||
Vector diff = currentScreenPoint - _dragStartPoint;
|
||||
|
||||
|
||||
if (!_isDragging && (Math.Abs(diff.X) > 3 || Math.Abs(diff.Y) > 3))
|
||||
{
|
||||
_isDragging = true;
|
||||
@@ -92,7 +91,7 @@ namespace Ink_Canvas.Controls
|
||||
this.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
this.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
// 计算新位置
|
||||
@@ -102,19 +101,19 @@ namespace Ink_Canvas.Controls
|
||||
// 计算屏幕坐标相对于父容器的位置
|
||||
var parentPoint = parent.PointFromScreen(currentScreenPoint);
|
||||
var startParentPoint = parent.PointFromScreen(_dragStartPoint);
|
||||
|
||||
|
||||
// 计算相对于初始位置的偏移
|
||||
double offsetX = parentPoint.X - startParentPoint.X;
|
||||
double offsetY = parentPoint.Y - startParentPoint.Y;
|
||||
|
||||
|
||||
// 新位置 = 初始位置 + 偏移
|
||||
double newLeft = _controlStartPoint.X + offsetX;
|
||||
double newTop = _controlStartPoint.Y + offsetY;
|
||||
|
||||
|
||||
// 限制在父容器范围内
|
||||
newLeft = Math.Max(0, Math.Min(newLeft, parent.ActualWidth - this.ActualWidth));
|
||||
newTop = Math.Max(0, Math.Min(newTop, parent.ActualHeight - this.ActualHeight));
|
||||
|
||||
|
||||
// 更新Margin
|
||||
this.Margin = new Thickness(newLeft, newTop, 0, 0);
|
||||
}
|
||||
@@ -131,17 +130,17 @@ namespace Ink_Canvas.Controls
|
||||
{
|
||||
((UIElement)sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => { _isDragging = false; }),
|
||||
Dispatcher.BeginInvoke(new Action(() => { _isDragging = false; }),
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,34 @@ namespace Ink_Canvas.Helpers
|
||||
GroupName = "inkeys",
|
||||
DownloadUrlFormat = "https://iccce.inkeys.top/Release/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "gh-proxy",
|
||||
VersionUrl = "https://gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://gh-proxy.org/https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "hk.gh-proxy",
|
||||
VersionUrl = "https://hk.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://hk.gh-proxy.org/https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://hk.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "cdn.gh-proxy",
|
||||
VersionUrl = "https://cdn.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://cdn.gh-proxy.org/https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://cdn.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "edgeone.gh-proxy",
|
||||
VersionUrl = "https://edgeone.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://edgeone.gh-proxy.org/https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://edgeone.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community/refs/heads/main/UpdateLog.md"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -111,6 +139,34 @@ namespace Ink_Canvas.Helpers
|
||||
GroupName = "inkeys",
|
||||
DownloadUrlFormat = "https://iccce.inkeys.top/Beta/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "gh-proxy",
|
||||
VersionUrl = "https://gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://gh-proxy.org/https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "hk.gh-proxy",
|
||||
VersionUrl = "https://hk.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://hk.gh-proxy.org/https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://hk.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "cdn.gh-proxy",
|
||||
VersionUrl = "https://cdn.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://cdn.gh-proxy.org/https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://cdn.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/UpdateLog.md"
|
||||
},
|
||||
new UpdateLineGroup
|
||||
{
|
||||
GroupName = "edgeone.gh-proxy",
|
||||
VersionUrl = "https://edgeone.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/AutomaticUpdateVersionControl.txt",
|
||||
DownloadUrlFormat = "https://edgeone.gh-proxy.org/https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip",
|
||||
LogUrl = "https://edgeone.gh-proxy.org/https://raw.githubusercontent.com/InkCanvasForClass/community-beta/refs/heads/main/UpdateLog.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,14 +244,45 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
// 跳过"智教联盟"和"inkeys"线路组,不参与延迟检测和排序
|
||||
string testUrl = null;
|
||||
if (group.GroupName == "智教联盟" || group.GroupName == "inkeys")
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 跳过{group.GroupName}线路组延迟检测");
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(group.DownloadUrlFormat))
|
||||
{
|
||||
testUrl = group.DownloadUrlFormat.Replace("{0}", "test");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
testUrl = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
testUrl = group.VersionUrl;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(testUrl))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 缺少可用测速地址,跳过", LogHelper.LogType.Warning);
|
||||
continue;
|
||||
}
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 检测线路组: {group.GroupName} ({group.VersionUrl})");
|
||||
var delay = await GetUrlDelay(group.VersionUrl);
|
||||
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 检测线路组: {group.GroupName} ({testUrl})");
|
||||
|
||||
long delay;
|
||||
|
||||
if (group.GroupName == "智教联盟" || group.GroupName == "inkeys")
|
||||
{
|
||||
delay = await GetDownloadUrlDelay(testUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
delay = await GetUrlDelay(testUrl);
|
||||
}
|
||||
|
||||
if (delay >= 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 延迟: {delay}ms");
|
||||
@@ -213,20 +300,12 @@ namespace Ink_Canvas.Helpers
|
||||
.Select(x => x.group)
|
||||
.ToList();
|
||||
|
||||
// 将"inkeys"线路组插入到最前面(如果存在)
|
||||
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
var inkeysGroup = orderedGroups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
orderedGroups.Remove(inkeysGroup);
|
||||
orderedGroups.Insert(0, inkeysGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | inkeys线路组已插入到首位");
|
||||
}
|
||||
|
||||
// 将"智教联盟"线路组插入到第二位(如果存在)
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
orderedGroups.Insert(1, zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 智教联盟线路组已插入到第二位");
|
||||
LogHelper.WriteLogToFile("AutoUpdate | inkeys线路组已默认优先");
|
||||
}
|
||||
|
||||
if (orderedGroups.Count > 0)
|
||||
@@ -245,6 +324,47 @@ namespace Ink_Canvas.Helpers
|
||||
return orderedGroups;
|
||||
}
|
||||
|
||||
private static async Task<long> GetDownloadUrlDelay(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var osVersion = Environment.OSVersion;
|
||||
bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1;
|
||||
|
||||
if (isWindows7)
|
||||
{
|
||||
using (var handler = new HttpClientHandler())
|
||||
{
|
||||
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true;
|
||||
|
||||
using (var client = new HttpClient(handler))
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(5);
|
||||
var sw = Stopwatch.StartNew();
|
||||
var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
|
||||
sw.Stop();
|
||||
return sw.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(5);
|
||||
var sw = Stopwatch.StartNew();
|
||||
var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, url));
|
||||
sw.Stop();
|
||||
return sw.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取远程版本号
|
||||
private static async Task<string> GetRemoteVersion(string fileUrl)
|
||||
{
|
||||
@@ -671,23 +791,13 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
SaveDownloadStatus(false);
|
||||
|
||||
// 优先尝试"inkeys"线路组和"智教联盟"线路组
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
// 优先尝试"inkeys"线路组
|
||||
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
if (inkeysGroup != null || zhiJiaoGroup != null)
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
var priorityGroups = new List<UpdateLineGroup>();
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
priorityGroups.Add(inkeysGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试inkeys线路组");
|
||||
}
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
priorityGroups.Add(zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试智教联盟线路组");
|
||||
}
|
||||
groups = priorityGroups.Concat(groups.Where(g => g.GroupName != "智教联盟" && g.GroupName != "inkeys")).ToList();
|
||||
groups.Remove(inkeysGroup);
|
||||
groups.Insert(0, inkeysGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试inkeys线路组");
|
||||
}
|
||||
|
||||
// 依次尝试每个线路组
|
||||
|
||||
@@ -283,8 +283,8 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
int targetWidth = _resolutionWidth;
|
||||
int targetHeight = _resolutionHeight;
|
||||
|
||||
if (_rotationAngle == 1 || _rotationAngle == 3)
|
||||
|
||||
if (_rotationAngle == 1 || _rotationAngle == 3)
|
||||
{
|
||||
targetWidth = _resolutionHeight;
|
||||
targetHeight = _resolutionWidth;
|
||||
|
||||
@@ -135,4 +135,21 @@ namespace Ink_Canvas.Converter
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public class RippleEffectTranslationConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double d)
|
||||
{
|
||||
return -d / 2;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -14,7 +13,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public class DlassApiClient : IDisposable
|
||||
{
|
||||
private const string DEFAULT_BASE_URL = "https://dlass.tech";
|
||||
private const string DEFAULT_BASE_URL = "https://dlass.tech";
|
||||
private readonly string _appId;
|
||||
private readonly string _appSecret;
|
||||
private readonly string _baseUrl;
|
||||
@@ -37,13 +36,13 @@ namespace Ink_Canvas.Helpers
|
||||
_appSecret = appSecret ?? throw new ArgumentNullException(nameof(appSecret));
|
||||
_userToken = userToken;
|
||||
_baseUrl = baseUrl ?? DEFAULT_BASE_URL;
|
||||
|
||||
|
||||
_baseUrl = _baseUrl.TrimEnd('/');
|
||||
if (!_baseUrl.StartsWith("http://") && !_baseUrl.StartsWith("https://"))
|
||||
{
|
||||
_baseUrl = "https://" + _baseUrl;
|
||||
}
|
||||
|
||||
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(_baseUrl),
|
||||
@@ -122,7 +121,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
|
||||
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
@@ -179,7 +178,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
@@ -242,7 +241,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, endpoint);
|
||||
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
// 如果是用户token,使用X-User-Token header
|
||||
@@ -306,7 +305,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Delete, endpoint);
|
||||
|
||||
|
||||
if (requireAuth && !string.IsNullOrEmpty(token))
|
||||
{
|
||||
// 如果是用户token,使用X-User-Token header
|
||||
@@ -365,14 +364,14 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
|
||||
|
||||
// 设置白板认证头
|
||||
request.Headers.Add("X-Board-ID", boardId);
|
||||
request.Headers.Add("X-Secret-Key", secretKey);
|
||||
|
||||
// 创建multipart/form-data内容
|
||||
var content = new MultipartFormDataContent();
|
||||
|
||||
|
||||
// 添加文件
|
||||
var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -19,7 +18,7 @@ namespace Ink_Canvas.Helpers
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
private const int BATCH_SIZE = 10; // 批量上传大小
|
||||
private const int MAX_RETRY_COUNT = 3; // 最大重试次数
|
||||
private const string QUEUE_FILE_NAME = "DlassUploadQueue.json";
|
||||
private const string QUEUE_FILE_NAME = "DlassUploadQueue.json";
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
@@ -189,7 +188,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
var queueData = new List<UploadQueueItemData>();
|
||||
|
||||
|
||||
// 将队列转换为可序列化的格式
|
||||
foreach (var item in _uploadQueue)
|
||||
{
|
||||
@@ -202,7 +201,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
|
||||
|
||||
// 如果队列为空,清空文件
|
||||
if (queueData.Count == 0)
|
||||
{
|
||||
@@ -211,17 +210,17 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented);
|
||||
|
||||
|
||||
// 使用临时文件写入,然后替换,确保原子性
|
||||
var tempFilePath = queueFilePath + ".tmp";
|
||||
File.WriteAllText(tempFilePath, jsonContent);
|
||||
|
||||
|
||||
// 如果原文件存在,先删除
|
||||
if (File.Exists(queueFilePath))
|
||||
{
|
||||
File.Delete(queueFilePath);
|
||||
}
|
||||
|
||||
|
||||
// 重命名临时文件
|
||||
File.Move(tempFilePath, queueFilePath);
|
||||
}
|
||||
@@ -431,7 +430,7 @@ namespace Ink_Canvas.Helpers
|
||||
WhiteboardInfo sharedWhiteboard = null;
|
||||
string apiBaseUrl = null;
|
||||
string userToken = null;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
@@ -538,11 +537,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 检查是否是可重试的错误(超时、网络错误等)
|
||||
var errorMessage = ex.Message.ToLower();
|
||||
bool isRetryable = errorMessage.Contains("超时") ||
|
||||
bool isRetryable = errorMessage.Contains("超时") ||
|
||||
errorMessage.Contains("timeout") ||
|
||||
errorMessage.Contains("网络错误") ||
|
||||
errorMessage.Contains("network");
|
||||
|
||||
|
||||
if (isRetryable && IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
|
||||
@@ -36,6 +36,24 @@ namespace Ink_Canvas.Helpers
|
||||
public int Height => Bottom - Top;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MONITORINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public RECT rcMonitor;
|
||||
public RECT rcWork;
|
||||
public uint dwFlags;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromRect(ref RECT lprc, uint dwFlags);
|
||||
|
||||
public static string WindowTitle()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
@@ -106,10 +124,28 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
public static double GetTaskbarHeight(Screen screen, double dpiScaleY)
|
||||
{
|
||||
// 获取工作区和屏幕高度的差值
|
||||
var workingArea = screen.WorkingArea;
|
||||
var bounds = screen.Bounds;
|
||||
int taskbarHeight = bounds.Height - workingArea.Height;
|
||||
// 创建RECT结构体表示屏幕边界
|
||||
RECT screenRect = new RECT
|
||||
{
|
||||
Left = screen.Bounds.Left,
|
||||
Top = screen.Bounds.Top,
|
||||
Right = screen.Bounds.Right,
|
||||
Bottom = screen.Bounds.Bottom
|
||||
};
|
||||
|
||||
// 获取屏幕句柄
|
||||
const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
|
||||
IntPtr hMonitor = MonitorFromRect(ref screenRect, MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
// 初始化MONITORINFO结构体
|
||||
MONITORINFO monitorInfo = new MONITORINFO();
|
||||
monitorInfo.cbSize = (uint)Marshal.SizeOf(typeof(MONITORINFO));
|
||||
|
||||
// 获取监视器信息
|
||||
GetMonitorInfo(hMonitor, ref monitorInfo);
|
||||
|
||||
// 计算任务栏高度:monitorInfo.rcMonitor.bottom减去monitorInfo.rcWork.bottom的值
|
||||
int taskbarHeight = monitorInfo.rcMonitor.Bottom - monitorInfo.rcWork.Bottom;
|
||||
// 考虑 DPI 缩放
|
||||
return taskbarHeight / dpiScaleY;
|
||||
}
|
||||
|
||||
@@ -392,6 +392,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_registeredHotkeys.Count == 0)
|
||||
{
|
||||
if (ShouldEnableHotkeysBasedOnContext())
|
||||
{
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -447,6 +454,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 如果设置允许,则在鼠标模式下也启用快捷键
|
||||
EnableHotkeyRegistration();
|
||||
|
||||
if (_hotkeysShouldBeRegistered && _registeredHotkeys.Count == 0)
|
||||
{
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -458,6 +470,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 非鼠标模式下启用快捷键
|
||||
EnableHotkeyRegistration();
|
||||
|
||||
if (_hotkeysShouldBeRegistered && _registeredHotkeys.Count == 0)
|
||||
{
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
@@ -8,30 +9,52 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class VisualCanvas : FrameworkElement
|
||||
{
|
||||
private readonly List<DrawingVisual> _visuals = new List<DrawingVisual>();
|
||||
|
||||
protected override Visual GetVisualChild(int index)
|
||||
{
|
||||
return Visual;
|
||||
if (index < 0 || index >= _visuals.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return _visuals[index];
|
||||
}
|
||||
|
||||
protected override int VisualChildrenCount => 1;
|
||||
protected override int VisualChildrenCount => _visuals.Count;
|
||||
|
||||
public VisualCanvas(DrawingVisual visual)
|
||||
public VisualCanvas()
|
||||
{
|
||||
Visual = visual;
|
||||
CacheMode = new BitmapCache();
|
||||
|
||||
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
|
||||
RenderOptions.SetCachingHint(this, CachingHint.Cache);
|
||||
}
|
||||
|
||||
public void AddVisual(DrawingVisual visual)
|
||||
{
|
||||
if (visual == null) return;
|
||||
_visuals.Add(visual);
|
||||
AddVisualChild(visual);
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var visual in _visuals)
|
||||
{
|
||||
RemoveVisualChild(visual);
|
||||
}
|
||||
_visuals.Clear();
|
||||
}
|
||||
|
||||
public DrawingVisual Visual { get; }
|
||||
public IReadOnlyList<DrawingVisual> Visuals => _visuals;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于显示笔迹的类
|
||||
/// </summary>
|
||||
public class StrokeVisual : DrawingVisual
|
||||
public class StrokeVisual
|
||||
{
|
||||
private bool _needsRedraw = true;
|
||||
private int _lastPointCount = 0;
|
||||
private const int REDRAW_THRESHOLD = 3;
|
||||
private int _lastDrawnPointCount = 0;
|
||||
private const int INCREMENTAL_DRAW_THRESHOLD = 2;
|
||||
private VisualCanvas _visualCanvas;
|
||||
|
||||
/// <summary>
|
||||
/// 创建显示笔迹的类
|
||||
@@ -47,17 +70,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建显示笔迹的类
|
||||
/// 创建显示笔迹的类
|
||||
/// </summary>
|
||||
/// <param name="drawingAttributes"></param>
|
||||
public StrokeVisual(DrawingAttributes drawingAttributes)
|
||||
{
|
||||
_drawingAttributes = drawingAttributes;
|
||||
|
||||
// 启用硬件加速
|
||||
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
|
||||
RenderOptions.SetCachingHint(this, CachingHint.Cache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,6 +83,14 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public Stroke Stroke { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置关联的VisualCanvas
|
||||
/// </summary>
|
||||
public void SetVisualCanvas(VisualCanvas visualCanvas)
|
||||
{
|
||||
_visualCanvas = visualCanvas;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在笔迹中添加点
|
||||
/// </summary>
|
||||
@@ -75,16 +101,61 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
var collection = new StylusPointCollection { point };
|
||||
Stroke = new Stroke(collection) { DrawingAttributes = _drawingAttributes };
|
||||
_lastPointCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Stroke.StylusPoints.Add(point);
|
||||
_lastPointCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制点段到新的DrawingVisual
|
||||
/// </summary>
|
||||
private void DrawSegmentToNewVisual(int startIndex, int endIndex)
|
||||
{
|
||||
if (Stroke == null || Stroke.StylusPoints.Count == 0 || _visualCanvas == null) return;
|
||||
if (startIndex >= endIndex || startIndex < 0 || endIndex > Stroke.StylusPoints.Count) return;
|
||||
|
||||
var points = Stroke.StylusPoints;
|
||||
var drawingAttributes = Stroke.DrawingAttributes;
|
||||
|
||||
// 创建新的DrawingVisual用于绘制这个点段
|
||||
var segmentVisual = new DrawingVisual();
|
||||
|
||||
RenderOptions.SetBitmapScalingMode(segmentVisual, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(segmentVisual, EdgeMode.Aliased);
|
||||
RenderOptions.SetCachingHint(segmentVisual, CachingHint.Cache);
|
||||
|
||||
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)
|
||||
{
|
||||
// 多个点,绘制线段
|
||||
for (int i = startIndex; i < endIndex - 1 && i < points.Count - 1; i++)
|
||||
{
|
||||
var startPoint = new Point(points[i].X, points[i].Y);
|
||||
var endPoint = new Point(points[i + 1].X, points[i + 1].Y);
|
||||
dc.DrawLine(pen, startPoint, endPoint);
|
||||
}
|
||||
}
|
||||
else if (endIndex - startIndex == 1 && startIndex < points.Count)
|
||||
{
|
||||
// 只有一个点,绘制圆点
|
||||
var brush = new SolidColorBrush(drawingAttributes.Color);
|
||||
var point = points[startIndex];
|
||||
dc.DrawEllipse(brush, null, new Point(point.X, point.Y),
|
||||
drawingAttributes.Width / 2, drawingAttributes.Height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 标记需要重绘
|
||||
_needsRedraw = true;
|
||||
// 将新的DrawingVisual添加到VisualCanvas中
|
||||
_visualCanvas.AddVisual(segmentVisual);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,22 +163,35 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public void Redraw()
|
||||
{
|
||||
if (!_needsRedraw || Stroke == null) return;
|
||||
if (Stroke == null || _visualCanvas == null) return;
|
||||
|
||||
if (_lastPointCount % REDRAW_THRESHOLD != 0 && _lastPointCount > REDRAW_THRESHOLD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var currentPointCount = Stroke.StylusPoints.Count;
|
||||
if (currentPointCount == 0) return;
|
||||
|
||||
try
|
||||
// 计算新增的点数
|
||||
int newPointCount = currentPointCount - _lastDrawnPointCount;
|
||||
|
||||
// 如果新增点数达到阈值,才进行增量绘制
|
||||
if (newPointCount >= INCREMENTAL_DRAW_THRESHOLD || _lastDrawnPointCount == 0)
|
||||
{
|
||||
using (var dc = RenderOpen())
|
||||
try
|
||||
{
|
||||
Stroke.Draw(dc);
|
||||
if (_lastDrawnPointCount == 0)
|
||||
{
|
||||
// 首次绘制:绘制所有点
|
||||
DrawSegmentToNewVisual(0, currentPointCount);
|
||||
_lastDrawnPointCount = currentPointCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从上次绘制的最后一个点开始
|
||||
int startIndex = Math.Max(0, _lastDrawnPointCount - 1);
|
||||
DrawSegmentToNewVisual(startIndex, currentPointCount);
|
||||
_lastDrawnPointCount = currentPointCount;
|
||||
}
|
||||
}
|
||||
_needsRedraw = false;
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,7 +199,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public void ForceRedraw()
|
||||
{
|
||||
_needsRedraw = true;
|
||||
if (_visualCanvas != null)
|
||||
{
|
||||
_visualCanvas.Clear();
|
||||
}
|
||||
_lastDrawnPointCount = 0;
|
||||
Redraw();
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,9 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存所有墨迹到文件
|
||||
/// </summary>
|
||||
public void SaveAllStrokesToFile(Presentation presentation)
|
||||
/// <param name="presentation">演示文稿对象</param>
|
||||
/// <param name="currentSlideIndex">当前播放的页码,如果提供则使用此值保存位置,否则使用_lockedSlideIndex</param>
|
||||
public void SaveAllStrokesToFile(Presentation presentation, int currentSlideIndex = -1)
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
|
||||
|
||||
@@ -290,7 +292,18 @@ namespace Ink_Canvas.Helpers
|
||||
// 保存位置信息
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(folderPath, "Position"), _lockedSlideIndex.ToString());
|
||||
// 优先使用传入的当前页码,否则使用锁定的页码
|
||||
int positionToSave = currentSlideIndex > 0 ? currentSlideIndex : _lockedSlideIndex;
|
||||
// 如果都没有有效值,尝试使用最后切换的页码
|
||||
if (positionToSave <= 0 && _lastSwitchSlideIndex > 0)
|
||||
{
|
||||
positionToSave = _lastSwitchSlideIndex;
|
||||
}
|
||||
|
||||
if (positionToSave > 0)
|
||||
{
|
||||
File.WriteAllText(Path.Combine(folderPath, "Position"), positionToSave.ToString());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using Microsoft.Office.Core;
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -718,7 +719,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryNavigateNext()
|
||||
public bool TryNavigateNext(bool skipAnimations = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -729,8 +730,54 @@ namespace Ink_Canvas.Helpers
|
||||
if (slideShowWindow?.View != null)
|
||||
{
|
||||
slideShowWindow.Activate();
|
||||
slideShowWindow.View.Next();
|
||||
return true;
|
||||
|
||||
var view = slideShowWindow.View;
|
||||
var currentPosition = 0;
|
||||
var totalSlides = 0;
|
||||
|
||||
try
|
||||
{
|
||||
currentPosition = view.CurrentShowPosition;
|
||||
totalSlides = slideShowWindow.Presentation?.Slides?.Count ?? 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (skipAnimations && currentPosition > 0 && totalSlides > 0 && currentPosition < totalSlides)
|
||||
{
|
||||
try
|
||||
{
|
||||
view.GotoSlide(currentPosition + 1, MsoTriState.msoFalse);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"跳过转场动画跳转到下一页失败: {ex}", LogHelper.LogType.Warning);
|
||||
try
|
||||
{
|
||||
view.Next();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
view.Next();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调用下一页失败: {ex}", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ namespace Ink_Canvas.Helpers
|
||||
public int PPTRBButtonPosition { get; set; } = 0;
|
||||
public bool EnablePPTButtonPageClickable { get; set; } = true;
|
||||
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
|
||||
public double PPTLSButtonOpacity { get; set; } = 0.5;
|
||||
public double PPTRSButtonOpacity { get; set; } = 0.5;
|
||||
public double PPTLBButtonOpacity { get; set; } = 0.5;
|
||||
public double PPTRBButtonOpacity { get; set; } = 0.5;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
@@ -97,6 +101,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
UpdateNavigationPanelsVisibility();
|
||||
UpdateNavigationButtonStyles();
|
||||
_mainWindow.UpdatePPTTimeCapsuleVisibility();
|
||||
if (MainWindow.Settings.Advanced.IsEnableAvoidFullScreenHelper)
|
||||
{
|
||||
// 设置为画板模式,允许全屏操作
|
||||
@@ -107,6 +112,8 @@ namespace Ink_Canvas.Helpers
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
_mainWindow.isFullScreenApplied = true; // 标记已应用全屏处理
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -114,6 +121,7 @@ namespace Ink_Canvas.Helpers
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
|
||||
HideAllNavigationPanels();
|
||||
_mainWindow.UpdatePPTTimeCapsuleVisibility();
|
||||
if (MainWindow.Settings.Advanced.IsEnableAvoidFullScreenHelper)
|
||||
{
|
||||
// 恢复为非画板模式,重新启用全屏限制
|
||||
@@ -127,6 +135,8 @@ namespace Ink_Canvas.Helpers
|
||||
workingArea.X, workingArea.Y,
|
||||
workingArea.Width, workingArea.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
_mainWindow.isFullScreenApplied = false; // 标记全屏处理已还原
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,10 +387,9 @@ namespace Ink_Canvas.Helpers
|
||||
_mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility;
|
||||
|
||||
// 透明度设置
|
||||
var opacity = options[1] == '2' ? 0.5 : 1.0;
|
||||
_mainWindow.PPTBtnLSBorder.Opacity = opacity;
|
||||
_mainWindow.PPTBtnRSBorder.Opacity = opacity;
|
||||
// 透明度设置 - 直接使用用户设置的透明度值
|
||||
_mainWindow.PPTBtnLSBorder.Opacity = PPTLSButtonOpacity;
|
||||
_mainWindow.PPTBtnRSBorder.Opacity = PPTRSButtonOpacity;
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
@@ -406,10 +415,9 @@ namespace Ink_Canvas.Helpers
|
||||
_mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility;
|
||||
|
||||
// 透明度设置
|
||||
var opacity = options[1] == '2' ? 0.5 : 1.0;
|
||||
_mainWindow.PPTBtnLBBorder.Opacity = opacity;
|
||||
_mainWindow.PPTBtnRBBorder.Opacity = opacity;
|
||||
// 透明度设置 - 直接使用用户设置的透明度值
|
||||
_mainWindow.PPTBtnLBBorder.Opacity = PPTLBButtonOpacity;
|
||||
_mainWindow.PPTBtnRBBorder.Opacity = PPTRBButtonOpacity;
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 矩形结构体(用于窗口位置和大小)
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WindowRect
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public int Width => Right - Left;
|
||||
public int Height => Bottom - Top;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口信息结构
|
||||
/// </summary>
|
||||
public class WindowInfo
|
||||
{
|
||||
public IntPtr Handle { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string ProcessName { get; set; }
|
||||
public string ProcessPath { get; set; }
|
||||
public WindowRect Rect { get; set; }
|
||||
public bool IsVisible { get; set; }
|
||||
public bool IsMinimized { get; set; }
|
||||
public bool IsMaximized { get; set; }
|
||||
public int ZOrder { get; set; }
|
||||
public uint ProcessId { get; set; }
|
||||
public bool IsFullScreen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 计算窗口是否覆盖指定区域
|
||||
/// </summary>
|
||||
public bool CoversArea(WindowRect area)
|
||||
{
|
||||
if (!IsVisible || IsMinimized) return false;
|
||||
|
||||
// 计算交集
|
||||
int left = Math.Max(Rect.Left, area.Left);
|
||||
int top = Math.Max(Rect.Top, area.Top);
|
||||
int right = Math.Min(Rect.Right, area.Right);
|
||||
int bottom = Math.Min(Rect.Bottom, area.Bottom);
|
||||
|
||||
// 如果有交集,说明窗口覆盖了该区域
|
||||
return left < right && top < bottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算窗口覆盖指定区域的比例
|
||||
/// </summary>
|
||||
public double GetCoverageRatio(WindowRect area)
|
||||
{
|
||||
if (!IsVisible || IsMinimized) return 0.0;
|
||||
|
||||
// 计算交集
|
||||
int left = Math.Max(Rect.Left, area.Left);
|
||||
int top = Math.Max(Rect.Top, area.Top);
|
||||
int right = Math.Min(Rect.Right, area.Right);
|
||||
int bottom = Math.Min(Rect.Bottom, area.Bottom);
|
||||
|
||||
if (left >= right || top >= bottom) return 0.0;
|
||||
|
||||
// 计算交集面积
|
||||
double intersectionArea = (right - left) * (bottom - top);
|
||||
// 计算目标区域面积
|
||||
double targetArea = area.Width * area.Height;
|
||||
|
||||
if (targetArea == 0) return 0.0;
|
||||
|
||||
return intersectionArea / targetArea;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口概览模型 - 实时监控桌面所有可见窗口并计算遮挡情况
|
||||
/// </summary>
|
||||
public class WindowOverviewModel : IDisposable
|
||||
{
|
||||
#region Win32 API Declarations
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetWindowRect(IntPtr hWnd, out WindowRect lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool IsZoomed(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
|
||||
|
||||
private const uint GW_HWNDNEXT = 2;
|
||||
private const uint GW_HWNDPREV = 3;
|
||||
|
||||
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
||||
#endregion
|
||||
|
||||
private readonly object _lockObject = new object();
|
||||
private List<WindowInfo> _windows = new List<WindowInfo>();
|
||||
private Timer _updateTimer;
|
||||
private bool _isDisposed = false;
|
||||
private readonly int _updateInterval = 200; // 更新间隔(毫秒)
|
||||
|
||||
/// <summary>
|
||||
/// 窗口列表更新事件
|
||||
/// </summary>
|
||||
public event EventHandler<List<WindowInfo>> WindowsUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// 当前窗口列表(按Z顺序排序,最上层在前)
|
||||
/// </summary>
|
||||
public List<WindowInfo> Windows
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return new List<WindowInfo>(_windows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public WindowOverviewModel()
|
||||
{
|
||||
// 立即执行一次更新
|
||||
UpdateWindows();
|
||||
|
||||
// 启动定时器,定期更新窗口列表
|
||||
_updateTimer = new Timer(OnUpdateTimer, null, _updateInterval, _updateInterval);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时器回调
|
||||
/// </summary>
|
||||
private void OnUpdateTimer(object state)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
UpdateWindows();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口概览模型更新失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新窗口列表
|
||||
/// </summary>
|
||||
public void UpdateWindows()
|
||||
{
|
||||
var windows = new List<WindowInfo>();
|
||||
var zOrder = 0;
|
||||
|
||||
EnumWindows((hWnd, lParam) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查窗口是否可见
|
||||
if (!IsWindowVisible(hWnd)) return true;
|
||||
|
||||
// 检查窗口是否最小化
|
||||
bool isMinimized = IsIconic(hWnd);
|
||||
if (isMinimized) return true;
|
||||
|
||||
// 获取窗口矩形
|
||||
if (!GetWindowRect(hWnd, out WindowRect rect)) return true;
|
||||
|
||||
// 过滤掉无效的窗口(太小或位置异常的窗口)
|
||||
if (rect.Width <= 0 || rect.Height <= 0) return true;
|
||||
if (rect.Right < rect.Left || rect.Bottom < rect.Top) return true;
|
||||
|
||||
// 获取窗口标题
|
||||
const int nChars = 256;
|
||||
StringBuilder windowTitle = new StringBuilder(nChars);
|
||||
GetWindowText(hWnd, windowTitle, nChars);
|
||||
string title = windowTitle.ToString();
|
||||
|
||||
// 获取窗口类名
|
||||
StringBuilder className = new StringBuilder(nChars);
|
||||
GetClassName(hWnd, className, nChars);
|
||||
string classNameStr = className.ToString();
|
||||
|
||||
// 获取进程信息
|
||||
GetWindowThreadProcessId(hWnd, out uint processId);
|
||||
string processName = "Unknown";
|
||||
string processPath = "Unknown";
|
||||
|
||||
try
|
||||
{
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
processName = process.ProcessName;
|
||||
try
|
||||
{
|
||||
processPath = process.MainModule?.FileName ?? "Unknown";
|
||||
}
|
||||
catch
|
||||
{
|
||||
processPath = "Unknown";
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 进程可能已退出
|
||||
}
|
||||
|
||||
// 检查是否最大化
|
||||
bool isMaximized = IsZoomed(hWnd);
|
||||
|
||||
// 检查是否全屏(窗口大小接近屏幕大小)
|
||||
bool isFullScreen = false;
|
||||
try
|
||||
{
|
||||
var screen = System.Windows.Forms.Screen.FromHandle(hWnd);
|
||||
var screenBounds = screen.Bounds;
|
||||
// 如果窗口大小接近屏幕大小(允许10像素误差),认为是全屏
|
||||
isFullScreen = rect.Width >= screenBounds.Width - 10 &&
|
||||
rect.Height >= screenBounds.Height - 10 &&
|
||||
Math.Abs(rect.Left - screenBounds.Left) <= 10 &&
|
||||
Math.Abs(rect.Top - screenBounds.Top) <= 10;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 无法获取屏幕信息,使用默认值
|
||||
}
|
||||
|
||||
// 跳过当前应用程序的窗口(避免检测到自己)
|
||||
if (processName == "InkCanvasForClass" || processName == "Ink Canvas")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var windowInfo = new WindowInfo
|
||||
{
|
||||
Handle = hWnd,
|
||||
Title = title,
|
||||
ClassName = classNameStr,
|
||||
ProcessName = processName,
|
||||
ProcessPath = processPath,
|
||||
Rect = rect,
|
||||
IsVisible = true,
|
||||
IsMinimized = false,
|
||||
IsMaximized = isMaximized,
|
||||
ZOrder = zOrder++,
|
||||
ProcessId = processId,
|
||||
IsFullScreen = isFullScreen
|
||||
};
|
||||
|
||||
windows.Add(windowInfo);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略单个窗口的错误,继续枚举其他窗口
|
||||
}
|
||||
|
||||
return true; // 继续枚举
|
||||
}, IntPtr.Zero);
|
||||
|
||||
// 按Z顺序排序(从最上层到最下层)
|
||||
// 注意:EnumWindows返回的顺序可能不是严格的Z顺序,但我们可以通过GetWindow来获取更准确的顺序
|
||||
windows = windows.OrderByDescending(w => w.ZOrder).ToList();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_windows = windows;
|
||||
}
|
||||
|
||||
// 触发更新事件
|
||||
WindowsUpdated?.Invoke(this, windows);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定区域是否被其他窗口覆盖
|
||||
/// </summary>
|
||||
/// <param name="area">要检查的区域</param>
|
||||
/// <param name="excludeProcessNames">要排除的进程名列表(例如当前应用程序)</param>
|
||||
/// <param name="coverageThreshold">覆盖阈值(0.0-1.0),超过此阈值认为被覆盖</param>
|
||||
/// <returns>如果被覆盖返回true</returns>
|
||||
public bool IsAreaCovered(WindowRect area, List<string> excludeProcessNames = null, double coverageThreshold = 0.5)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
excludeProcessNames = excludeProcessNames ?? new List<string>();
|
||||
|
||||
// 从最上层窗口开始检查
|
||||
foreach (var window in _windows)
|
||||
{
|
||||
// 跳过排除的进程
|
||||
if (excludeProcessNames.Contains(window.ProcessName)) continue;
|
||||
|
||||
// 计算覆盖比例
|
||||
double coverage = window.GetCoverageRatio(area);
|
||||
if (coverage >= coverageThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定区域是否被全屏窗口覆盖
|
||||
/// </summary>
|
||||
/// <param name="area">要检查的区域</param>
|
||||
/// <param name="excludeProcessNames">要排除的进程名列表</param>
|
||||
/// <returns>如果被全屏窗口覆盖返回true</returns>
|
||||
public bool IsAreaCoveredByFullScreenWindow(WindowRect area, List<string> excludeProcessNames = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
excludeProcessNames = excludeProcessNames ?? new List<string>();
|
||||
|
||||
// 查找全屏窗口
|
||||
foreach (var window in _windows)
|
||||
{
|
||||
// 跳过排除的进程
|
||||
if (excludeProcessNames.Contains(window.ProcessName)) continue;
|
||||
|
||||
// 只检查全屏窗口
|
||||
if (window.IsFullScreen && window.CoversArea(area))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取覆盖指定区域的所有窗口
|
||||
/// </summary>
|
||||
/// <param name="area">要检查的区域</param>
|
||||
/// <param name="excludeProcessNames">要排除的进程名列表</param>
|
||||
/// <param name="coverageThreshold">覆盖阈值</param>
|
||||
/// <returns>覆盖该区域的窗口列表(按Z顺序,最上层在前)</returns>
|
||||
public List<WindowInfo> GetCoveringWindows(WindowRect area, List<string> excludeProcessNames = null, double coverageThreshold = 0.1)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
excludeProcessNames = excludeProcessNames ?? new List<string>();
|
||||
var coveringWindows = new List<WindowInfo>();
|
||||
|
||||
foreach (var window in _windows)
|
||||
{
|
||||
// 跳过排除的进程
|
||||
if (excludeProcessNames.Contains(window.ProcessName)) continue;
|
||||
|
||||
// 计算覆盖比例
|
||||
double coverage = window.GetCoverageRatio(area);
|
||||
if (coverage >= coverageThreshold)
|
||||
{
|
||||
coveringWindows.Add(window);
|
||||
}
|
||||
}
|
||||
|
||||
return coveringWindows;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有全屏窗口
|
||||
/// </summary>
|
||||
/// <param name="excludeProcessNames">要排除的进程名列表</param>
|
||||
/// <returns>如果有全屏窗口返回true</returns>
|
||||
public bool HasFullScreenWindow(List<string> excludeProcessNames = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
excludeProcessNames = excludeProcessNames ?? new List<string>();
|
||||
|
||||
return _windows.Any(w => !excludeProcessNames.Contains(w.ProcessName) && w.IsFullScreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有全屏窗口
|
||||
/// </summary>
|
||||
/// <param name="excludeProcessNames">要排除的进程名列表</param>
|
||||
/// <returns>全屏窗口列表</returns>
|
||||
public List<WindowInfo> GetFullScreenWindows(List<string> excludeProcessNames = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
excludeProcessNames = excludeProcessNames ?? new List<string>();
|
||||
|
||||
return _windows.Where(w => !excludeProcessNames.Contains(w.ProcessName) && w.IsFullScreen).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据进程名查找窗口
|
||||
/// </summary>
|
||||
/// <param name="processName">进程名</param>
|
||||
/// <returns>匹配的窗口列表</returns>
|
||||
public List<WindowInfo> FindWindowsByProcessName(string processName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _windows.Where(w => w.ProcessName.Equals(processName, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据窗口标题查找窗口
|
||||
/// </summary>
|
||||
/// <param name="title">窗口标题(支持部分匹配)</param>
|
||||
/// <returns>匹配的窗口列表</returns>
|
||||
public List<WindowInfo> FindWindowsByTitle(string title)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _windows.Where(w => w.Title.IndexOf(title, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
_isDisposed = true;
|
||||
_updateTimer?.Dispose();
|
||||
_updateTimer = null;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_windows.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal static class WindowsNotificationHelper
|
||||
{
|
||||
private const string APP_ID = "InkCanvasForClass.CE";
|
||||
|
||||
public static void ShowNewVersionToast(string version)
|
||||
{
|
||||
try
|
||||
{
|
||||
var os = Environment.OSVersion.Version;
|
||||
|
||||
if (os.Major == 6 && os.Minor == 1)
|
||||
{
|
||||
ShowBalloonForWin7(version);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowToastForModernWindows(version);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowBalloonForWin7(string version)
|
||||
{
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var taskbar = Application.Current.Resources["TaskbarTrayIcon"] as TaskbarIcon;
|
||||
if (taskbar == null) return;
|
||||
|
||||
taskbar.Visibility = Visibility.Visible;
|
||||
|
||||
taskbar.ShowBalloonTip(
|
||||
"InkCanvasForClass CE",
|
||||
$"发现新版本!:{version}",
|
||||
BalloonIcon.Info);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void ShowToastForModernWindows(string version)
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText("InkCanvasForClass CE")
|
||||
.AddText($"发现新版本!:{version}")
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,8 @@
|
||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
+377
-11
@@ -12,7 +12,6 @@
|
||||
AllowsTransparency="True"
|
||||
WindowStyle="None"
|
||||
ResizeMode="NoResize"
|
||||
WindowState="Maximized"
|
||||
Loaded="Window_Loaded"
|
||||
Background="Transparent"
|
||||
ShowInTaskbar="False"
|
||||
@@ -605,6 +604,13 @@
|
||||
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchNoFocusMode_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="窗口无边框模式" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchWindowMode"
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchWindowMode_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="窗口置顶" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
@@ -1209,6 +1215,7 @@
|
||||
<ComboBoxItem Content="osu!玩家语录" FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="励志立志的名言警句" FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="高考祝福语" FontFamily="Microsoft YaHei UI" />
|
||||
<ComboBoxItem Content="一言(Hitokoto API)" FontFamily="Microsoft YaHei UI" />
|
||||
</ComboBox>
|
||||
</ui:SimpleStackPanel>
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0"
|
||||
@@ -1636,6 +1643,82 @@
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,8,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
|
||||
Name="PPTLSButtonOpacityPanel"
|
||||
Visibility="{Binding ElementName=CheckboxSPPTHalfOpacity, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock Foreground="#fafafa" Text="左侧透明度" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,12,0" />
|
||||
<Slider x:Name="PPTLSButtonOpacityValueSlider" Minimum="0.1"
|
||||
Maximum="1.0" Width="138" FontFamily="Microsoft YaHei UI"
|
||||
FontSize="20" IsSnapToTickEnabled="True" Value="0.5"
|
||||
TickFrequency="0.1"
|
||||
TickPlacement="None" AutoToolTipPlacement="None"
|
||||
ValueChanged="PPTLSButtonOpacityValueSlider_ValueChanged" />
|
||||
<Button Padding="5" Margin="8,0,0,0" Name="PPTLSOpacityPlusBtn"
|
||||
Click="PPTLSOpacityPlusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V16 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,16z M0,0z M9,1C9,0.447715 8.55229,0 8,0 7.44772,0 7,0.447715 7,1L7,7 1,7C0.447715,7 0,7.44772 0,8 0,8.55229 0.447715,9 1,9L7,9 7,15C7,15.5523 7.44772,16 8,16 8.55229,16 9,15.5523 9,15L9,9 15,9C15.5523,9 16,8.55229 16,8 16,7.44772 15.5523,7 15,7L9,7 9,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTLSOpacityMinusBtn"
|
||||
Click="PPTLSOpacityMinusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V2 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,2z M0,0z M0,1C0,0.447715,0.447715,0,1,0L15,0C15.5523,0 16,0.447715 16,1 16,1.55228 15.5523,2 15,2L1,2C0.447715,2,0,1.55228,0,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTLSOpacitySyncBtn"
|
||||
Click="PPTLSOpacitySyncBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,6z M0,0z M1,0C0.447715,0 0,0.447715 0,1 0,1.55228 0.447715,2 1,2L15,2C15.5523,2 16,1.55228 16,1 16,0.447715 15.5523,0 15,0L1,0z M1,4C0.447715,4 0,4.44772 0,5 0,5.55228 0.447715,6 1,6L15,6C15.5523,6 16,5.55228 16,5 16,4.44772 15.5523,4 15,4L1,4z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTLSOpacityResetBtn"
|
||||
Click="PPTLSOpacityResetBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M24,24z M0,0z M12,2.01123C10.5428,2.01123 9.14528,2.5901 8.11488,3.6205 7.08449,4.65089 6.50562,6.04841 6.50562,7.50561L6.50562,16.4944C6.50562,17.9516 7.08449,19.3491 8.11488,20.3795 9.14528,21.4099 10.5428,21.9888 12,21.9888 13.4572,21.9888 14.8547,21.4099 15.8851,20.3795 16.9135,19.3511 17.4922,17.9569 17.4944,16.5027 17.4944,16.4999 17.4944,16.4972 17.4944,16.4944L17.4944,7.50563C17.4944,7.50285 17.4944,7.50007 17.4944,7.4973 17.4922,6.0431 16.9135,4.64893 15.8851,3.6205 14.8547,2.5901 13.4572,2.01123 12,2.01123z M9.5291,5.03471C10.1844,4.37939 11.0732,4.01123 12,4.01123 12.9268,4.01123 13.8156,4.37939 14.4709,5.03471 15.1262,5.69003 15.4944,6.57884 15.4944,7.50561L15.4944,16.4944C15.4944,17.4211 15.1262,18.31 14.4709,18.9653 13.8156,19.6206 12.9268,19.9888 12,19.9888 11.0732,19.9888 10.1844,19.6206 9.5291,18.9653 8.87377,18.31 8.50562,17.4211 8.50562,16.4944L8.50562,7.50561C8.50562,6.57884,8.87377,5.69003,9.5291,5.03471z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<TextBlock
|
||||
Text="{Binding ElementName=PPTLSButtonOpacityValueSlider, Path=Value, StringFormat='{}{0:F1}'}"
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,0,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="右侧偏移" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,12,0" />
|
||||
@@ -1710,6 +1793,82 @@
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,16,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
|
||||
Name="PPTRSButtonOpacityPanel"
|
||||
Visibility="{Binding ElementName=CheckboxSPPTHalfOpacity, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock Foreground="#fafafa" Text="右侧透明度" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,12,0" />
|
||||
<Slider x:Name="PPTRSButtonOpacityValueSlider" Minimum="0.1"
|
||||
Maximum="1.0" Width="138" FontFamily="Microsoft YaHei UI"
|
||||
FontSize="20" IsSnapToTickEnabled="True" Value="0.5"
|
||||
TickFrequency="0.1"
|
||||
TickPlacement="None" AutoToolTipPlacement="None"
|
||||
ValueChanged="PPTRSButtonOpacityValueSlider_ValueChanged" />
|
||||
<Button Padding="5" Margin="8,0,0,0" Name="PPTRSOpacityPlusBtn"
|
||||
Click="PPTRSOpacityPlusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V16 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,16z M0,0z M9,1C9,0.447715 8.55229,0 8,0 7.44772,0 7,0.447715 7,1L7,7 1,7C0.447715,7 0,7.44772 0,8 0,8.55229 0.447715,9 1,9L7,9 7,15C7,15.5523 7.44772,16 8,16 8.55229,16 9,15.5523 9,15L9,9 15,9C15.5523,9 16,8.55229 16,8 16,7.44772 15.5523,7 15,7L9,7 9,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTRSOpacityMinusBtn"
|
||||
Click="PPTRSOpacityMinusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V2 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,2z M0,0z M0,1C0,0.447715,0.447715,0,1,0L15,0C15.5523,0 16,0.447715 16,1 16,1.55228 15.5523,2 15,2L1,2C0.447715,2,0,1.55228,0,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTRSOpacitySyncBtn"
|
||||
Click="PPTRSOpacitySyncBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,6z M0,0z M1,0C0.447715,0 0,0.447715 0,1 0,1.55228 0.447715,2 1,2L15,2C15.5523,2 16,1.55228 16,1 16,0.447715 15.5523,0 15,0L1,0z M1,4C0.447715,4 0,4.44772 0,5 0,5.55228 0.447715,6 1,6L15,6C15.5523,6 16,5.55228 16,5 16,4.44772 15.5523,4 15,4L1,4z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTRSOpacityResetBtn"
|
||||
Click="PPTRSOpacityResetBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M24,24z M0,0z M12,2.01123C10.5428,2.01123 9.14528,2.5901 8.11488,3.6205 7.08449,4.65089 6.50562,6.04841 6.50562,7.50561L6.50562,16.4944C6.50562,17.9516 7.08449,19.3491 8.11488,20.3795 9.14528,21.4099 10.5428,21.9888 12,21.9888 13.4572,21.9888 14.8547,21.4099 15.8851,20.3795 16.9135,19.3511 17.4922,17.9569 17.4944,16.5027 17.4944,16.4999 17.4944,16.4972 17.4944,16.4944L17.4944,7.50563C17.4944,7.50285 17.4944,7.50007 17.4944,7.4973 17.4922,6.0431 16.9135,4.64893 15.8851,3.6205 14.8547,2.5901 13.4572,2.01123 12,2.01123z M9.5291,5.03471C10.1844,4.37939 11.0732,4.01123 12,4.01123 12.9268,4.01123 13.8156,4.37939 14.4709,5.03471 15.1262,5.69003 15.4944,6.57884 15.4944,7.50561L15.4944,16.4944C15.4944,17.4211 15.1262,18.31 14.4709,18.9653 13.8156,19.6206 12.9268,19.9888 12,19.9888 11.0732,19.9888 10.1844,19.6206 9.5291,18.9653 8.87377,18.31 8.50562,17.4211 8.50562,16.4944L8.50562,7.50561C8.50562,6.57884,8.87377,5.69003,9.5291,5.03471z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<TextBlock
|
||||
Text="{Binding ElementName=PPTRSButtonOpacityValueSlider, Path=Value, StringFormat='{}{0:F1}'}"
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,0,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock
|
||||
Text="# 调大往上偏移,调小往下偏移,修改为0为不偏移,居中放置"
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
@@ -1789,6 +1948,82 @@
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,8,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
|
||||
Name="PPTLBButtonOpacityPanel"
|
||||
Visibility="{Binding ElementName=CheckboxBPPTHalfOpacity, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock Foreground="#fafafa" Text="左下透明度" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,12,0" />
|
||||
<Slider x:Name="PPTLBButtonOpacityValueSlider" Minimum="0.1"
|
||||
Maximum="1.0" Width="138" FontFamily="Microsoft YaHei UI"
|
||||
FontSize="20" IsSnapToTickEnabled="True" Value="0.5"
|
||||
TickFrequency="0.1"
|
||||
TickPlacement="None" AutoToolTipPlacement="None"
|
||||
ValueChanged="PPTLBButtonOpacityValueSlider_ValueChanged" />
|
||||
<Button Padding="5" Margin="8,0,0,0" Name="PPTLBOpacityPlusBtn"
|
||||
Click="PPTLBOpacityPlusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V16 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,16z M0,0z M9,1C9,0.447715 8.55229,0 8,0 7.44772,0 7,0.447715 7,1L7,7 1,7C0.447715,7 0,7.44772 0,8 0,8.55229 0.447715,9 1,9L7,9 7,15C7,15.5523 7.44772,16 8,16 8.55229,16 9,15.5523 9,15L9,9 15,9C15.5523,9 16,8.55229 16,8 16,7.44772 15.5523,7 15,7L9,7 9,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTLBOpacityMinusBtn"
|
||||
Click="PPTLBOpacityMinusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V2 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,2z M0,0z M0,1C0,0.447715,0.447715,0,1,0L15,0C15.5523,0 16,0.447715 16,1 16,1.55228 15.5523,2 15,2L1,2C0.447715,2,0,1.55228,0,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTLBOpacitySyncBtn"
|
||||
Click="PPTLBOpacitySyncBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,6z M0,0z M1,0C0.447715,0 0,0.447715 0,1 0,1.55228 0.447715,2 1,2L15,2C15.5523,2 16,1.55228 16,1 16,0.447715 15.5523,0 15,0L1,0z M1,4C0.447715,4 0,4.44772 0,5 0,5.55228 0.447715,6 1,6L15,6C15.5523,6 16,5.55228 16,5 16,4.44772 15.5523,4 15,4L1,4z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTLBOpacityResetBtn"
|
||||
Click="PPTLBOpacityResetBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M24,24z M0,0z M12,2.01123C10.5428,2.01123 9.14528,2.5901 8.11488,3.6205 7.08449,4.65089 6.50562,6.04841 6.50562,7.50561L6.50562,16.4944C6.50562,17.9516 7.08449,19.3491 8.11488,20.3795 9.14528,21.4099 10.5428,21.9888 12,21.9888 13.4572,21.9888 14.8547,21.4099 15.8851,20.3795 16.9135,19.3511 17.4922,17.9569 17.4944,16.5027 17.4944,16.4999 17.4944,16.4972 17.4944,16.4944L17.4944,7.50563C17.4944,7.50285 17.4944,7.50007 17.4944,7.4973 17.4922,6.0431 16.9135,4.64893 15.8851,3.6205 14.8547,2.5901 13.4572,2.01123 12,2.01123z M9.5291,5.03471C10.1844,4.37939 11.0732,4.01123 12,4.01123 12.9268,4.01123 13.8156,4.37939 14.4709,5.03471 15.1262,5.69003 15.4944,6.57884 15.4944,7.50561L15.4944,16.4944C15.4944,17.4211 15.1262,18.31 14.4709,18.9653 13.8156,19.6206 12.9268,19.9888 12,19.9888 11.0732,19.9888 10.1844,19.6206 9.5291,18.9653 8.87377,18.31 8.50562,17.4211 8.50562,16.4944L8.50562,7.50561C8.50562,6.57884,8.87377,5.69003,9.5291,5.03471z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<TextBlock
|
||||
Text="{Binding ElementName=PPTLBButtonOpacityValueSlider, Path=Value, StringFormat='{}{0:F1}'}"
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,0,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="右下偏移" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,12,0" />
|
||||
@@ -1865,6 +2100,82 @@
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,16,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
|
||||
Name="PPTRBButtonOpacityPanel"
|
||||
Visibility="{Binding ElementName=CheckboxBPPTHalfOpacity, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<TextBlock Foreground="#fafafa" Text="右下透明度" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,12,0" />
|
||||
<Slider x:Name="PPTRBButtonOpacityValueSlider" Minimum="0.1"
|
||||
Maximum="1.0" Width="138" FontFamily="Microsoft YaHei UI"
|
||||
FontSize="20" IsSnapToTickEnabled="True" Value="0.5"
|
||||
TickFrequency="0.1"
|
||||
TickPlacement="None" AutoToolTipPlacement="None"
|
||||
ValueChanged="PPTRBButtonOpacityValueSlider_ValueChanged" />
|
||||
<Button Padding="5" Margin="8,0,0,0" Name="PPTRBOpacityPlusBtn"
|
||||
Click="PPTRBOpacityPlusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V16 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,16z M0,0z M9,1C9,0.447715 8.55229,0 8,0 7.44772,0 7,0.447715 7,1L7,7 1,7C0.447715,7 0,7.44772 0,8 0,8.55229 0.447715,9 1,9L7,9 7,15C7,15.5523 7.44772,16 8,16 8.55229,16 9,15.5523 9,15L9,9 15,9C15.5523,9 16,8.55229 16,8 16,7.44772 15.5523,7 15,7L9,7 9,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTRBOpacityMinusBtn"
|
||||
Click="PPTRBOpacityMinusBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V2 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,2z M0,0z M0,1C0,0.447715,0.447715,0,1,0L15,0C15.5523,0 16,0.447715 16,1 16,1.55228 15.5523,2 15,2L1,2C0.447715,2,0,1.55228,0,1z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTRBOpacitySyncBtn"
|
||||
Click="PPTRBOpacitySyncBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V6 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M16,6z M0,0z M1,0C0.447715,0 0,0.447715 0,1 0,1.55228 0.447715,2 1,2L15,2C15.5523,2 16,1.55228 16,1 16,0.447715 15.5523,0 15,0L1,0z M1,4C0.447715,4 0,4.44772 0,5 0,5.55228 0.447715,6 1,6L15,6C15.5523,6 16,5.55228 16,5 16,4.44772 15.5523,4 15,4L1,4z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<Button Margin="6,0,0,0" Padding="5" Name="PPTRBOpacityResetBtn"
|
||||
Click="PPTRBOpacityResetBtn_Clicked">
|
||||
<Image Width="16" Height="16">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#f4f4f5"
|
||||
Geometry="F0 M24,24z M0,0z M12,2.01123C10.5428,2.01123 9.14528,2.5901 8.11488,3.6205 7.08449,4.65089 6.50562,6.04841 6.50562,7.50561L6.50562,16.4944C6.50562,17.9516 7.08449,19.3491 8.11488,20.3795 9.14528,21.4099 10.5428,21.9888 12,21.9888 13.4572,21.9888 14.8547,21.4099 15.8851,20.3795 16.9135,19.3511 17.4922,17.9569 17.4944,16.5027 17.4944,16.4999 17.4944,16.4972 17.4944,16.4944L17.4944,7.50563C17.4944,7.50285 17.4944,7.50007 17.4944,7.4973 17.4922,6.0431 16.9135,4.64893 15.8851,3.6205 14.8547,2.5901 13.4572,2.01123 12,2.01123z M9.5291,5.03471C10.1844,4.37939 11.0732,4.01123 12,4.01123 12.9268,4.01123 13.8156,4.37939 14.4709,5.03471 15.1262,5.69003 15.4944,6.57884 15.4944,7.50561L15.4944,16.4944C15.4944,17.4211 15.1262,18.31 14.4709,18.9653 13.8156,19.6206 12.9268,19.9888 12,19.9888 11.0732,19.9888 10.1844,19.6206 9.5291,18.9653 8.87377,18.31 8.50562,17.4211 8.50562,16.4944L8.50562,7.50561C8.50562,6.57884,8.87377,5.69003,9.5291,5.03471z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Button>
|
||||
<TextBlock
|
||||
Text="{Binding ElementName=PPTRBButtonOpacityValueSlider, Path=Value, StringFormat='{}{0:F1}'}"
|
||||
VerticalAlignment="Center" FontSize="13"
|
||||
Margin="8,0,0,0" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock
|
||||
Text="# 调大往右偏移,调小往左偏移,修改为0为不偏移,居中放置"
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
@@ -1938,6 +2249,19 @@
|
||||
<TextBlock
|
||||
Text="# 开启该选项后,长按PPT翻页按钮可以连续翻页,提高翻页效率。"
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Left" Margin="0,6,0,0">
|
||||
<TextBlock Foreground="#fafafa" Text="点击翻页时跳过转场动画,直接切到下一张"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchSkipAnimationsWhenGoNext"
|
||||
IsOn="False"
|
||||
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchSkipAnimationsWhenGoNext_OnToggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock
|
||||
Text="# 开启该选项后,点击下一页按钮时会跳过幻灯片之间的转场动画,直接跳转到下一张幻灯片。不会影响幻灯片内的元素动画。"
|
||||
TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
</ui:SimpleStackPanel>
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
|
||||
StrokeThickness="1" Margin="0,4,0,4" />
|
||||
@@ -1999,6 +2323,27 @@
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后在 PPT 放映模式下也显示手势按钮" TextWrapping="Wrap"
|
||||
Foreground="#a1a1aa" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="PPT时间显示胶囊" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchEnablePPTTimeCapsule" IsOn="True"
|
||||
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchEnablePPTTimeCapsule_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后在 PPT 放映模式下显示时间胶囊,可替代最小化计时器窗口" TextWrapping="Wrap"
|
||||
Foreground="#a1a1aa" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0">
|
||||
<TextBlock Foreground="#fafafa" Text="时间胶囊位置:" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<ComboBox Name="ComboBoxPPTTimeCapsulePosition"
|
||||
Width="150"
|
||||
SelectionChanged="ComboBoxPPTTimeCapsulePosition_SelectionChanged">
|
||||
<ComboBoxItem Content="左上角" />
|
||||
<ComboBoxItem Content="右上角" />
|
||||
<ComboBoxItem Content="顶部居中" />
|
||||
</ComboBox>
|
||||
</ui:SimpleStackPanel>
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
|
||||
StrokeThickness="1" Margin="0,4,0,4" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
@@ -2238,7 +2583,7 @@
|
||||
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Foreground="#a1a1aa"
|
||||
Text="# 避免画布全屏,重启icc后生效。" />
|
||||
Text="# 避免画布全屏,可解决开启dock栏软件后浮动工具栏偏高的问题(但是进入批注也仍然会偏高),重启icc后生效。" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
|
||||
<TextBlock Foreground="#fafafa" Text="启用EdgeGestureUtil"
|
||||
@@ -3113,6 +3458,16 @@
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后自动保存和手动保存墨迹时将以全屏模式保存。如果存在多个画布和墨迹,将把所有页面的墨迹按照每页为单位保存进一个压缩包中(注意,白板的墨迹只能在白板模式下打开,PPT的墨迹只能在PPT放映模式下打开)" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="保存为XML格式" VerticalAlignment="Center"
|
||||
FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchSaveStrokesAsXML" IsOn="False"
|
||||
FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchSaveStrokesAsXML_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后保存墨迹时将使用XML格式(ISF格式),便于查看和编辑墨迹数据" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
|
||||
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
|
||||
StrokeThickness="1" Margin="0,4,0,4" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
@@ -3214,14 +3569,14 @@
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后,退出收纳模式时将自动切换至批注模式,便于快速批注" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="退出PPT放映后自动恢复浮动栏状态"
|
||||
<TextBlock Foreground="#fafafa" Text="退出PPT放映后自动收纳浮动栏"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
<ui:ToggleSwitch OnContent="" OffContent=""
|
||||
Name="ToggleSwitchAutoFoldAfterPPTSlideShow"
|
||||
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||
Toggled="ToggleSwitchAutoFoldAfterPPTSlideShow_Toggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
<TextBlock Text="# 开启后,如果进入PPT放映前为收纳模式则退出后也为收纳模式,如果进入前不是收纳模式则退出后也不是收纳模式" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
<TextBlock Text="# 开启后,退出PPT放映后会自动收纳浮动栏" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Foreground="#fafafa" Text="退出白板时自动收纳"
|
||||
VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" />
|
||||
@@ -3249,7 +3604,7 @@
|
||||
Toggled="ToggleSwitchDisplayRandWindowNamesInputBtn_OnToggled" />
|
||||
</ui:SimpleStackPanel>
|
||||
|
||||
<TextBlock Foreground="#fafafa" Text="点名窗口背景设置"
|
||||
<TextBlock Foreground="#fafafa" Text="点名窗口背景设置(仅老版点名UI有效)"
|
||||
FontSize="16" FontWeight="Bold" Margin="0,10,0,5" />
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,5,0,0">
|
||||
@@ -3927,18 +4282,20 @@
|
||||
TouchMove="inkCanvas_TouchMove"
|
||||
ManipulationDelta="Main_Grid_ManipulationDelta"
|
||||
ManipulationCompleted="Main_Grid_ManipulationCompleted"
|
||||
ManipulationInertiaStarting="inkCanvas_ManipulationInertiaStarting"
|
||||
ManipulationInertiaStarting="InkCanvas_ManipulationInertiaStarting"
|
||||
IsManipulationEnabled="True"
|
||||
EditingModeChanged="inkCanvas_EditingModeChanged"
|
||||
PreviewTouchDown="inkCanvas_PreviewTouchDown"
|
||||
PreviewTouchMove="inkCanvas_PreviewTouchMove"
|
||||
PreviewTouchUp="inkCanvas_PreviewTouchUp"
|
||||
PreviewTouchDown="InkCanvas_PreviewTouchDown"
|
||||
PreviewTouchMove="InkCanvas_PreviewTouchMove"
|
||||
PreviewTouchUp="InkCanvas_PreviewTouchUp"
|
||||
MouseDown="inkCanvas_MouseDown"
|
||||
MouseMove="inkCanvas_MouseMove"
|
||||
MouseUp="inkCanvas_MouseUp"
|
||||
ManipulationStarting="inkCanvas_ManipulationStarting"
|
||||
ManipulationStarting="InkCanvas_ManipulationStarting"
|
||||
SelectionChanged="inkCanvas_SelectionChanged"
|
||||
StrokeCollected="inkCanvas_StrokeCollected" ClipToBounds="False" Background="Transparent" />
|
||||
StrokeCollected="inkCanvas_StrokeCollected"
|
||||
ClipToBounds="False"
|
||||
Background="Transparent" />
|
||||
|
||||
<Canvas x:Name="EraserOverlayCanvas"
|
||||
Background="Transparent"
|
||||
@@ -9972,6 +10329,15 @@
|
||||
Height="200">
|
||||
<Windows:MinimizedTimerControl x:Name="MinimizedTimerControl"/>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="PPTTimeCapsuleContainer"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="999"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,20,20,0">
|
||||
<Windows:PPTTimeCapsule x:Name="PPTTimeCapsule"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
|
||||
+324
-47
@@ -28,15 +28,14 @@ using Application = System.Windows.Application;
|
||||
using Brushes = System.Windows.Media.Brushes;
|
||||
using Button = System.Windows.Controls.Button;
|
||||
using Cursor = System.Windows.Input.Cursor;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using VerticalAlignment = System.Windows.VerticalAlignment;
|
||||
using Cursors = System.Windows.Input.Cursors;
|
||||
using DpiChangedEventArgs = System.Windows.DpiChangedEventArgs;
|
||||
using File = System.IO.File;
|
||||
using GroupBox = System.Windows.Controls.GroupBox;
|
||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using Point = System.Windows.Point;
|
||||
using VerticalAlignment = System.Windows.VerticalAlignment;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -63,11 +62,16 @@ namespace Ink_Canvas
|
||||
// 悬浮窗拦截管理器
|
||||
private FloatingWindowInterceptorManager _floatingWindowInterceptorManager;
|
||||
|
||||
// 窗口概览模型
|
||||
private WindowOverviewModel _windowOverviewModel;
|
||||
|
||||
// 设置面板相关状态
|
||||
private bool userChangedNoFocusModeInSettings;
|
||||
private bool isTemporarilyDisablingNoFocusMode = false;
|
||||
|
||||
// 全屏处理状态标志
|
||||
public bool isFullScreenApplied = false;
|
||||
|
||||
|
||||
|
||||
#region Window Initialization
|
||||
@@ -263,12 +267,9 @@ namespace Ink_Canvas
|
||||
Activated += Window_Activated;
|
||||
Deactivated += Window_Deactivated;
|
||||
|
||||
// 为浮动栏按钮添加触摸事件支持
|
||||
AddTouchSupportToFloatingBarButtons();
|
||||
|
||||
// 为滑块控件添加触摸事件支持
|
||||
AddTouchSupportToSliders();
|
||||
|
||||
|
||||
// 初始化计时器控件事件
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
@@ -277,24 +278,24 @@ namespace Ink_Canvas
|
||||
TimerControl.ShowMinimizedRequested += TimerControl_ShowMinimizedRequested;
|
||||
TimerControl.HideMinimizedRequested += TimerControl_HideMinimizedRequested;
|
||||
}
|
||||
|
||||
|
||||
if (MinimizedTimerControl != null && TimerControl != null)
|
||||
{
|
||||
MinimizedTimerControl.SetParentControl(TimerControl);
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
|
||||
private void TimerControl_ShowMinimizedRequested(object sender, EventArgs e)
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
double x = 0, y = 0;
|
||||
|
||||
if (timerContainer.HorizontalAlignment == HorizontalAlignment.Center &&
|
||||
|
||||
if (timerContainer.HorizontalAlignment == HorizontalAlignment.Center &&
|
||||
timerContainer.VerticalAlignment == VerticalAlignment.Center)
|
||||
{
|
||||
var timerPoint = timerContainer.TransformToAncestor(this).Transform(new Point(0, 0));
|
||||
@@ -307,30 +308,30 @@ namespace Ink_Canvas
|
||||
x = double.IsNaN(timerMargin.Left) ? 0 : timerMargin.Left;
|
||||
y = double.IsNaN(timerMargin.Top) ? 0 : timerMargin.Top;
|
||||
}
|
||||
|
||||
|
||||
minimizedContainer.Margin = new Thickness(x, y, 0, 0);
|
||||
minimizedContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
minimizedContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
|
||||
timerContainer.Margin = new Thickness(x, y, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
|
||||
timerContainer.Visibility = Visibility.Collapsed;
|
||||
minimizedContainer.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void TimerControl_HideMinimizedRequested(object sender, EventArgs e)
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
minimizedContainer.Visibility = Visibility.Collapsed;
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
|
||||
|
||||
if (TimerControl != null)
|
||||
{
|
||||
TimerControl.UpdateActivityTime();
|
||||
@@ -338,6 +339,66 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据DPI缩放因子调整TimerContainer的尺寸
|
||||
/// </summary>
|
||||
private void AdjustTimerContainerSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer == null) return;
|
||||
|
||||
var source = System.Windows.PresentationSource.FromVisual(this);
|
||||
if (source != null)
|
||||
{
|
||||
var dpiScaleX = source.CompositionTarget.TransformToDevice.M11;
|
||||
var dpiScaleY = source.CompositionTarget.TransformToDevice.M22;
|
||||
|
||||
// 如果DPI缩放因子大于1.25,则适当缩小容器尺寸
|
||||
// 这样可以确保在高DPI屏幕上,计时器窗口的物理像素大小不会过大
|
||||
if (dpiScaleX > 1.25 || dpiScaleY > 1.25)
|
||||
{
|
||||
// 使用较小的缩放因子来限制最大尺寸
|
||||
double scaleFactor = Math.Min(dpiScaleX, dpiScaleY);
|
||||
|
||||
// 计算目标物理像素大小(约1350x750物理像素)
|
||||
// 然后转换为逻辑像素
|
||||
double targetPhysicalWidth = 1350;
|
||||
double targetPhysicalHeight = 750;
|
||||
|
||||
// 转换为逻辑像素
|
||||
double maxWidth = targetPhysicalWidth / scaleFactor;
|
||||
double maxHeight = targetPhysicalHeight / scaleFactor;
|
||||
|
||||
// 确保不会小于原始尺寸的70%
|
||||
maxWidth = Math.Max(maxWidth, 900 * 0.7);
|
||||
maxHeight = Math.Max(maxHeight, 500 * 0.7);
|
||||
|
||||
// 应用调整后的尺寸
|
||||
timerContainer.Width = maxWidth;
|
||||
timerContainer.Height = maxHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 标准DPI,使用原始尺寸
|
||||
timerContainer.Width = 900;
|
||||
timerContainer.Height = 500;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无法获取DPI信息,使用原始尺寸
|
||||
timerContainer.Width = 900;
|
||||
timerContainer.Height = 500;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调整TimerContainer尺寸失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -514,6 +575,9 @@ namespace Ink_Canvas
|
||||
// 加载自定义背景颜色
|
||||
LoadCustomBackgroundColor();
|
||||
|
||||
// 设置窗口模式
|
||||
SetWindowMode();
|
||||
|
||||
// 注册设置面板滚动事件
|
||||
if (SettingsPanelScrollViewer != null)
|
||||
{
|
||||
@@ -529,6 +593,17 @@ namespace Ink_Canvas
|
||||
StartPPTMonitoring();
|
||||
}
|
||||
|
||||
// 初始化窗口概览模型
|
||||
try
|
||||
{
|
||||
_windowOverviewModel = new WindowOverviewModel();
|
||||
LogHelper.WriteLogToFile("窗口概览模型已初始化", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化窗口概览模型失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 如果启用PowerPoint联动增强功能,启动进程守护
|
||||
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
||||
{
|
||||
@@ -637,7 +712,7 @@ namespace Ink_Canvas
|
||||
// 初始化UIA置顶开关
|
||||
ToggleSwitchUIAccessTopMost.IsOn = Settings.Advanced.EnableUIAccessTopMost;
|
||||
UpdateUIAccessTopMostVisibility();
|
||||
|
||||
|
||||
App.IsUIAccessTopMostEnabled = Settings.Advanced.EnableUIAccessTopMost;
|
||||
|
||||
// 初始化剪贴板监控
|
||||
@@ -686,7 +761,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
|
||||
// 初始化计时器控件关联
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
@@ -694,16 +769,32 @@ namespace Ink_Canvas
|
||||
{
|
||||
MinimizedTimerControl.SetParentControl(TimerControl);
|
||||
|
||||
// 设置PPT时间胶囊的父控件
|
||||
if (PPTTimeCapsule != null)
|
||||
{
|
||||
PPTTimeCapsule.SetParentControl(TimerControl);
|
||||
}
|
||||
|
||||
TimerControl.ShowMinimizedRequested += (s, args) =>
|
||||
{
|
||||
if (TimerContainer != null && MinimizedTimerContainer != null && MinimizedTimerControl != null)
|
||||
{
|
||||
TimerContainer.Visibility = Visibility.Collapsed;
|
||||
MinimizedTimerContainer.Visibility = Visibility.Visible;
|
||||
MinimizedTimerControl.Visibility = Visibility.Visible;
|
||||
|
||||
if (Settings.PowerPointSettings.EnablePPTTimeCapsule &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
PPTTimeCapsule != null)
|
||||
{
|
||||
MinimizedTimerContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
MinimizedTimerContainer.Visibility = Visibility.Visible;
|
||||
MinimizedTimerControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TimerControl.HideMinimizedRequested += (s, args) =>
|
||||
{
|
||||
if (MinimizedTimerContainer != null && MinimizedTimerControl != null)
|
||||
@@ -711,6 +802,24 @@ namespace Ink_Canvas
|
||||
MinimizedTimerContainer.Visibility = Visibility.Collapsed;
|
||||
MinimizedTimerControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 如果启用了PPT时间胶囊,停止倒计时显示
|
||||
if (Settings.PowerPointSettings.EnablePPTTimeCapsule && PPTTimeCapsule != null)
|
||||
{
|
||||
PPTTimeCapsule.StopCountdown();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听计时器完成事件
|
||||
TimerControl.TimerCompleted += (s, args) =>
|
||||
{
|
||||
// 如果启用了PPT时间胶囊且在PPT模式下,触发完成动画
|
||||
if (Settings.PowerPointSettings.EnablePPTTimeCapsule &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
PPTTimeCapsule != null)
|
||||
{
|
||||
PPTTimeCapsule.OnTimerCompleted();
|
||||
}
|
||||
};
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
@@ -748,6 +857,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
ShowNotification($"系统DPI发生变化,从 {e.OldDpi.DpiScaleX}x{e.OldDpi.DpiScaleY} 变化为 {e.NewDpi.DpiScaleX}x{e.NewDpi.DpiScaleY}");
|
||||
|
||||
// 如果TimerContainer可见,调整其尺寸
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var timerContainer = FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null && timerContainer.Visibility == Visibility.Visible)
|
||||
{
|
||||
AdjustTimerContainerSize();
|
||||
}
|
||||
});
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
var isFloatingBarOutsideScreen = false;
|
||||
@@ -769,6 +888,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void SetWindowMode()
|
||||
{
|
||||
if (Settings.Advanced.WindowMode)
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
Left = 0.0;
|
||||
Top = 0.0;
|
||||
Height = SystemParameters.PrimaryScreenHeight;
|
||||
Width = SystemParameters.PrimaryScreenWidth;
|
||||
}
|
||||
else // 全屏
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event);
|
||||
@@ -780,7 +915,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭快抽悬浮按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
|
||||
if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp)
|
||||
{
|
||||
// 第一个确认对话框
|
||||
@@ -873,6 +1008,13 @@ namespace Ink_Canvas
|
||||
_floatingWindowInterceptorManager = null;
|
||||
}
|
||||
|
||||
// 清理窗口概览模型
|
||||
if (_windowOverviewModel != null)
|
||||
{
|
||||
_windowOverviewModel.Dispose();
|
||||
_windowOverviewModel = null;
|
||||
}
|
||||
|
||||
// 停止置顶维护定时器
|
||||
StopTopmostMaintenance();
|
||||
|
||||
@@ -949,6 +1091,24 @@ namespace Ink_Canvas
|
||||
|
||||
private async void AutoUpdate()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Settings.Startup.AutoUpdatePauseUntilDate))
|
||||
{
|
||||
if (DateTime.TryParse(Settings.Startup.AutoUpdatePauseUntilDate, out DateTime pauseUntilDate))
|
||||
{
|
||||
if (DateTime.Now < pauseUntilDate)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 自动更新已暂停,直到 {pauseUntilDate:yyyy-MM-dd}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 暂停期已过,恢复自动更新检查");
|
||||
Settings.Startup.AutoUpdatePauseUntilDate = "";
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清除之前的更新状态,确保使用新通道重新检查
|
||||
AvailableLatestVersion = null;
|
||||
AvailableLatestLineGroup = null;
|
||||
@@ -972,6 +1132,9 @@ namespace Ink_Canvas
|
||||
// 检测到新版本
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | New version available: {AvailableLatestVersion}");
|
||||
|
||||
// 通过 Windows 系统通知提示有新版本
|
||||
WindowsNotificationHelper.ShowNewVersionToast(AvailableLatestVersion);
|
||||
|
||||
// 检查是否是用户选择跳过的版本
|
||||
if (!string.IsNullOrEmpty(Settings.Startup.SkippedVersion) &&
|
||||
Settings.Startup.SkippedVersion == AvailableLatestVersion)
|
||||
@@ -1934,7 +2097,7 @@ namespace Ink_Canvas
|
||||
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentProcessId();
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
@@ -1989,16 +2152,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (nCode >= 0)
|
||||
{
|
||||
if (Settings.Advanced.IsNoFocusMode &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
if (Settings.Advanced.IsNoFocusMode &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
currentMode == 0)
|
||||
{
|
||||
KBDLLHOOKSTRUCT hookStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
|
||||
uint vkCode = hookStruct.vkCode;
|
||||
|
||||
|
||||
if (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
|
||||
{
|
||||
if (vkCode == 0x22 || vkCode == 0x28 || vkCode == 0x27 ||
|
||||
if (vkCode == 0x22 || vkCode == 0x28 || vkCode == 0x27 ||
|
||||
vkCode == 0x4E || vkCode == 0x20)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
@@ -2007,7 +2170,7 @@ namespace Ink_Canvas
|
||||
}), DispatcherPriority.Normal);
|
||||
return (IntPtr)1;
|
||||
}
|
||||
else if (vkCode == 0x21 || vkCode == 0x26 || vkCode == 0x25 ||
|
||||
else if (vkCode == 0x21 || vkCode == 0x26 || vkCode == 0x25 ||
|
||||
vkCode == 0x50)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
@@ -2050,10 +2213,10 @@ namespace Ink_Canvas
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
|
||||
bool shouldBeNoFocus = isTemporarilyDisablingNoFocusMode ?
|
||||
|
||||
bool shouldBeNoFocus = isTemporarilyDisablingNoFocusMode ?
|
||||
false : Settings.Advanced.IsNoFocusMode;
|
||||
|
||||
|
||||
if (shouldBeNoFocus)
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE);
|
||||
@@ -2163,8 +2326,8 @@ namespace Ink_Canvas
|
||||
|
||||
public void ResumeTopmostMaintenance()
|
||||
{
|
||||
if (Settings.Advanced.IsAlwaysOnTop &&
|
||||
Settings.Advanced.IsNoFocusMode &&
|
||||
if (Settings.Advanced.IsAlwaysOnTop &&
|
||||
Settings.Advanced.IsNoFocusMode &&
|
||||
!Settings.Advanced.EnableUIAccessTopMost)
|
||||
{
|
||||
if (topmostMaintenanceTimer != null && !isTopmostMaintenanceEnabled)
|
||||
@@ -2264,12 +2427,12 @@ namespace Ink_Canvas
|
||||
var toggle = sender as ToggleSwitch;
|
||||
Settings.Advanced.IsNoFocusMode = toggle != null && toggle.IsOn;
|
||||
SaveSettingsToFile();
|
||||
|
||||
|
||||
if (isTemporarilyDisablingNoFocusMode)
|
||||
{
|
||||
isTemporarilyDisablingNoFocusMode = false;
|
||||
}
|
||||
|
||||
|
||||
ApplyNoFocusMode();
|
||||
|
||||
// 如果启用了窗口置顶,需要重新应用置顶设置以处理无焦点模式的变化
|
||||
@@ -2300,13 +2463,13 @@ namespace Ink_Canvas
|
||||
if (!isLoaded) return;
|
||||
var toggle = sender as ToggleSwitch;
|
||||
bool newValue = toggle != null && toggle.IsOn;
|
||||
|
||||
|
||||
Settings.Advanced.EnableUIAccessTopMost = newValue;
|
||||
SaveSettingsToFile();
|
||||
ApplyUIAccessTopMost();
|
||||
|
||||
|
||||
App.IsUIAccessTopMostEnabled = newValue;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void Window_Activated(object sender, EventArgs e)
|
||||
@@ -2538,6 +2701,54 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnablePPTTimeCapsule_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
var toggle = sender as ToggleSwitch;
|
||||
Settings.PowerPointSettings.EnablePPTTimeCapsule = toggle != null && toggle.IsOn;
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 如果当前在PPT放映模式,需要立即更新时间胶囊的显示状态
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdatePPTTimeCapsuleVisibility();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"PPT时间显示胶囊已{(Settings.PowerPointSettings.EnablePPTTimeCapsule ? "启用" : "禁用")}", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换PPT时间显示胶囊时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ComboBoxPPTTimeCapsulePosition_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
if (ComboBoxPPTTimeCapsulePosition != null)
|
||||
{
|
||||
Settings.PowerPointSettings.PPTTimeCapsulePosition = ComboBoxPPTTimeCapsulePosition.SelectedIndex;
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 如果当前在PPT放映模式,需要立即更新时间胶囊的位置
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdatePPTTimeCapsulePosition();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"PPT时间胶囊位置已更改为: {ComboBoxPPTTimeCapsulePosition.SelectedIndex}", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更改PPT时间胶囊位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新PPT模式下手势按钮的显示状态
|
||||
/// </summary>
|
||||
@@ -2561,6 +2772,69 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"更新PPT模式下手势按钮显示状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新PPT时间胶囊的显示状态
|
||||
/// </summary>
|
||||
public void UpdatePPTTimeCapsuleVisibility()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PPTTimeCapsuleContainer == null || PPTTimeCapsule == null) return;
|
||||
|
||||
if (Settings.PowerPointSettings.EnablePPTTimeCapsule &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
PPTTimeCapsuleContainer.Visibility = Visibility.Visible;
|
||||
UpdatePPTTimeCapsulePosition();
|
||||
}
|
||||
else
|
||||
{
|
||||
PPTTimeCapsuleContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新PPT时间胶囊显示状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新PPT时间胶囊的位置
|
||||
/// </summary>
|
||||
private void UpdatePPTTimeCapsulePosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (PPTTimeCapsuleContainer == null) return;
|
||||
|
||||
int position = Settings.PowerPointSettings.PPTTimeCapsulePosition;
|
||||
// 0-左上角, 1-右上角, 2-顶部居中
|
||||
switch (position)
|
||||
{
|
||||
case 0: // 左上角
|
||||
PPTTimeCapsuleContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
PPTTimeCapsuleContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
PPTTimeCapsuleContainer.Margin = new Thickness(20, 20, 0, 0);
|
||||
break;
|
||||
case 1: // 右上角
|
||||
PPTTimeCapsuleContainer.HorizontalAlignment = HorizontalAlignment.Right;
|
||||
PPTTimeCapsuleContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
PPTTimeCapsuleContainer.Margin = new Thickness(0, 20, 20, 0);
|
||||
break;
|
||||
case 2: // 顶部居中
|
||||
PPTTimeCapsuleContainer.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
PPTTimeCapsuleContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
PPTTimeCapsuleContainer.Margin = new Thickness(0, 20, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新PPT时间胶囊位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -2689,6 +2963,10 @@ namespace Ink_Canvas
|
||||
PPTButtonRightPositionValueSlider,
|
||||
PPTButtonLBPositionValueSlider,
|
||||
PPTButtonRBPositionValueSlider,
|
||||
PPTLSButtonOpacityValueSlider,
|
||||
PPTRSButtonOpacityValueSlider,
|
||||
PPTLBButtonOpacityValueSlider,
|
||||
PPTRBButtonOpacityValueSlider,
|
||||
TouchMultiplierSlider,
|
||||
NibModeBoundsWidthSlider,
|
||||
FingerModeBoundsWidthSlider,
|
||||
@@ -3003,7 +3281,6 @@ namespace Ink_Canvas
|
||||
else if (!isInSlideShow && IsVisible)
|
||||
{
|
||||
Hide();
|
||||
LogHelper.WriteLogToFile("PPT放映结束,隐藏主窗口(仅PPT模式)", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3172,12 +3449,12 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
var visibility = Settings.Advanced.IsAlwaysOnTop ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
|
||||
if (UIAccessTopMostPanel != null)
|
||||
{
|
||||
UIAccessTopMostPanel.Visibility = visibility;
|
||||
}
|
||||
|
||||
|
||||
if (UIAccessTopMostDescription != null)
|
||||
{
|
||||
UIAccessTopMostDescription.Visibility = visibility;
|
||||
@@ -3201,7 +3478,7 @@ namespace Ink_Canvas
|
||||
// 检查是否以管理员权限运行
|
||||
var identity = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
|
||||
|
||||
if (principal.IsInRole(WindowsBuiltInRole.Administrator))
|
||||
{
|
||||
try
|
||||
@@ -3212,8 +3489,8 @@ namespace Ink_Canvas
|
||||
App.watchdogProcess.Kill();
|
||||
App.watchdogProcess = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 调用UIAccess DLL
|
||||
if (Environment.Is64BitProcess)
|
||||
{
|
||||
@@ -3223,7 +3500,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
PrepareUIAccessX86();
|
||||
}
|
||||
|
||||
|
||||
App.StartWatchdogIfNeeded();
|
||||
timerKillProcess.Start();
|
||||
}
|
||||
|
||||
@@ -536,13 +536,13 @@ namespace Ink_Canvas
|
||||
operatingGuideWindow.RefreshTheme();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 刷新计时器控件
|
||||
if (TimerControl != null)
|
||||
{
|
||||
TimerControl.RefreshTheme();
|
||||
}
|
||||
|
||||
|
||||
if (MinimizedTimerControl != null)
|
||||
{
|
||||
MinimizedTimerControl.RefreshTheme();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -206,9 +205,6 @@ namespace Ink_Canvas
|
||||
// 检查是否保存了多指书写模式状态
|
||||
if (savedMultiTouchModeStates[pageIndex])
|
||||
{
|
||||
// 恢复多指书写模式
|
||||
EnterMultiTouchModeIfNeeded();
|
||||
|
||||
// 更新UI状态
|
||||
if (ToggleSwitchEnableMultiTouchMode != null)
|
||||
{
|
||||
@@ -219,9 +215,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
// 确保多指书写模式关闭
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
|
||||
// 更新UI状态
|
||||
if (ToggleSwitchEnableMultiTouchMode != null)
|
||||
{
|
||||
|
||||
@@ -75,48 +75,54 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private void CheckEnableTwoFingerGestureBtnColorPrompt()
|
||||
{
|
||||
if (ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
// 根据主题选择手势图标和颜色
|
||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
||||
bool isLightTheme = !isDarkTheme;
|
||||
string gestureIconPath = isLightTheme ? "/Resources/new-icons/gesture.png" : "/Resources/new-icons/gesture_white.png";
|
||||
|
||||
// 根据主题设置白板模式下的颜色
|
||||
Color boardBgColor, boardIconColor, boardTextColor, boardBorderColor;
|
||||
if (isLightTheme)
|
||||
{
|
||||
// 多指书写模式启用时,手势功能被禁用
|
||||
TwoFingerGestureSimpleStackPanel.Opacity = 0.5;
|
||||
TwoFingerGestureSimpleStackPanel.IsHitTestVisible = false;
|
||||
EnableTwoFingerGestureBtn.Source = (BitmapImage)Application.Current.FindResource("GestureIcon");
|
||||
|
||||
// 根据主题设置颜色
|
||||
if (Settings.Appearance.Theme == 1) // 深色主题
|
||||
{
|
||||
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
|
||||
}
|
||||
else // 浅色主题或跟随系统
|
||||
{
|
||||
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
|
||||
}
|
||||
BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon);
|
||||
BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z");
|
||||
|
||||
// 强制禁用所有双指手势功能
|
||||
ForceDisableTwoFingerGestures();
|
||||
// 浅色主题
|
||||
boardBgColor = Color.FromRgb(244, 244, 245);
|
||||
boardIconColor = Color.FromRgb(24, 24, 27);
|
||||
boardTextColor = Color.FromRgb(24, 24, 27);
|
||||
boardBorderColor = Color.FromRgb(161, 161, 170);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 深色主题
|
||||
boardBgColor = Color.FromRgb(39, 39, 42);
|
||||
boardIconColor = Color.FromRgb(244, 244, 245);
|
||||
boardTextColor = Color.FromRgb(244, 244, 245);
|
||||
boardBorderColor = Color.FromRgb(113, 113, 122);
|
||||
}
|
||||
|
||||
if (ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
TwoFingerGestureSimpleStackPanel.Opacity = 0.5;
|
||||
TwoFingerGestureSimpleStackPanel.IsHitTestVisible = false;
|
||||
EnableTwoFingerGestureBtn.Source =
|
||||
new BitmapImage(new Uri(gestureIconPath, UriKind.Relative));
|
||||
|
||||
BoardGesture.Background = new SolidColorBrush(boardBgColor);
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(boardIconColor);
|
||||
BoardGestureGeometry2.Brush = new SolidColorBrush(boardIconColor);
|
||||
BoardGestureLabel.Foreground = new SolidColorBrush(boardTextColor);
|
||||
BoardGesture.BorderBrush = new SolidColorBrush(boardBorderColor);
|
||||
BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon);
|
||||
BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 多指书写模式禁用时,根据实际手势功能状态显示
|
||||
TwoFingerGestureSimpleStackPanel.Opacity = 1;
|
||||
TwoFingerGestureSimpleStackPanel.IsHitTestVisible = true;
|
||||
|
||||
// 检查是否有任何手势功能启用
|
||||
bool hasGestureEnabled = Settings.Gesture.IsEnableTwoFingerGesture;
|
||||
|
||||
if (hasGestureEnabled)
|
||||
if (Settings.Gesture.IsEnableTwoFingerGesture)
|
||||
{
|
||||
EnableTwoFingerGestureBtn.Source = (BitmapImage)Application.Current.FindResource("GestureIconEnabled");
|
||||
EnableTwoFingerGestureBtn.Source =
|
||||
new BitmapImage(new Uri("/Resources/new-icons/gesture-enabled.png", UriKind.Relative));
|
||||
|
||||
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(Colors.GhostWhite);
|
||||
@@ -128,25 +134,14 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
EnableTwoFingerGestureBtn.Source = (BitmapImage)Application.Current.FindResource("GestureIcon");
|
||||
EnableTwoFingerGestureBtn.Source =
|
||||
new BitmapImage(new Uri(gestureIconPath, UriKind.Relative));
|
||||
|
||||
// 根据主题设置颜色
|
||||
if (Settings.Appearance.Theme == 1) // 深色主题
|
||||
{
|
||||
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(42, 42, 42));
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255));
|
||||
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(85, 85, 85));
|
||||
}
|
||||
else // 浅色主题或跟随系统
|
||||
{
|
||||
BoardGesture.Background = new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
BoardGestureGeometry2.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
BoardGestureLabel.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(161, 161, 170));
|
||||
}
|
||||
BoardGesture.Background = new SolidColorBrush(boardBgColor);
|
||||
BoardGestureGeometry.Brush = new SolidColorBrush(boardIconColor);
|
||||
BoardGestureGeometry2.Brush = new SolidColorBrush(boardIconColor);
|
||||
BoardGestureLabel.Foreground = new SolidColorBrush(boardTextColor);
|
||||
BoardGesture.BorderBrush = new SolidColorBrush(boardBorderColor);
|
||||
BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon);
|
||||
BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z");
|
||||
}
|
||||
@@ -368,8 +363,7 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
|
||||
// 隐藏背景设置面板
|
||||
var bgPalette = LogicalTreeHelper.FindLogicalNode(this, "BackgroundPalette") as Border;
|
||||
if (bgPalette != null)
|
||||
if (LogicalTreeHelper.FindLogicalNode(this, "BackgroundPalette") is Border bgPalette)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(bgPalette);
|
||||
}
|
||||
@@ -386,9 +380,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
From = 0, // 滑动距离
|
||||
To = BorderSettings.RenderTransform.Value.OffsetX - 490,
|
||||
Duration = TimeSpan.FromSeconds(0.6)
|
||||
Duration = TimeSpan.FromSeconds(0.6),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut };
|
||||
Storyboard.SetTargetProperty(slideAnimation,
|
||||
new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
|
||||
|
||||
@@ -605,8 +599,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
//if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == SymbolIconUndo && lastBorderMouseDownObject != SymbolIconUndo) return;
|
||||
|
||||
if (!BtnUndo.IsEnabled) return;
|
||||
@@ -618,8 +612,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
//if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == SymbolIconRedo && lastBorderMouseDownObject != SymbolIconRedo) return;
|
||||
|
||||
if (!BtnRedo.IsEnabled) return;
|
||||
@@ -637,8 +631,8 @@ namespace Ink_Canvas
|
||||
internal void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == WhiteboardFloatingBarBtn && lastBorderMouseDownObject != WhiteboardFloatingBarBtn) return;
|
||||
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
@@ -715,21 +709,7 @@ namespace Ink_Canvas
|
||||
BlackBoardWaterMark.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (Settings.Appearance.ChickenSoupSource == 0)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.OSUPlayerYuLu.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.OSUPlayerYuLu[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 1)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.MingYanJingJu.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.MingYanJingJu[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 2)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.GaoKaoPhrases.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.GaoKaoPhrases[randChickenSoupIndex];
|
||||
}
|
||||
_ = UpdateChickenSoupTextAsync();
|
||||
|
||||
if (Settings.Canvas.UsingWhiteboard)
|
||||
{
|
||||
@@ -772,10 +752,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 使用PPT UI管理器来正确更新翻页按钮显示状态,确保遵循用户设置
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.UpdateNavigationPanelsVisibility();
|
||||
}
|
||||
_pptUIManager?.UpdateNavigationPanelsVisibility();
|
||||
|
||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true);
|
||||
@@ -881,8 +858,8 @@ namespace Ink_Canvas
|
||||
internal void SymbolIconDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == SymbolIconDelete && lastBorderMouseDownObject != SymbolIconDelete) return;
|
||||
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
@@ -932,8 +909,8 @@ namespace Ink_Canvas
|
||||
internal void SymbolIconSelect_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == SymbolIconSelect && lastBorderMouseDownObject != SymbolIconSelect) return;
|
||||
|
||||
BtnSelect_Click(null, null);
|
||||
@@ -962,8 +939,7 @@ namespace Ink_Canvas
|
||||
if (border.Name?.StartsWith("QuickColor") == true)
|
||||
{
|
||||
// 保存原始颜色并添加透明度
|
||||
var originalColor = border.Background as SolidColorBrush;
|
||||
if (originalColor != null)
|
||||
if (border.Background is SolidColorBrush originalColor)
|
||||
{
|
||||
border.Background = new SolidColorBrush(Color.FromArgb(180, originalColor.Color.R, originalColor.Color.G, originalColor.Color.B));
|
||||
}
|
||||
@@ -1055,6 +1031,12 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (TimerContainer != null && TimerControl != null)
|
||||
{
|
||||
// 每次打开计时器窗口时重置计时器
|
||||
TimerControl.ResetTimerState();
|
||||
|
||||
// 根据DPI缩放因子调整TimerContainer的尺寸
|
||||
AdjustTimerContainerSize();
|
||||
|
||||
TimerContainer.Visibility = Visibility.Visible;
|
||||
if (MinimizedTimerContainer != null)
|
||||
{
|
||||
@@ -1368,8 +1350,10 @@ namespace Ink_Canvas
|
||||
catch { }
|
||||
|
||||
stylusPoints.Add(stylusPoint);
|
||||
s = new Stroke(stylusPoints.Clone());
|
||||
s.DrawingAttributes = stroke.DrawingAttributes;
|
||||
s = new Stroke(stylusPoints.Clone())
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes
|
||||
};
|
||||
InkCanvasForInkReplay.Strokes.Add(s);
|
||||
});
|
||||
}
|
||||
@@ -1403,8 +1387,10 @@ namespace Ink_Canvas
|
||||
catch { }
|
||||
|
||||
stylusPoints.Add(stylusPoint);
|
||||
s = new Stroke(stylusPoints.Clone());
|
||||
s.DrawingAttributes = stroke.DrawingAttributes;
|
||||
s = new Stroke(stylusPoints.Clone())
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes
|
||||
};
|
||||
InkCanvasForInkReplay.Strokes.Add(s);
|
||||
});
|
||||
}
|
||||
@@ -1532,8 +1518,8 @@ namespace Ink_Canvas
|
||||
private void SymbolIconTools_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == ToolsFloatingBarBtn && lastBorderMouseDownObject != ToolsFloatingBarBtn) return;
|
||||
|
||||
if (BorderTools.Visibility == Visibility.Visible)
|
||||
@@ -1568,6 +1554,11 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderTools);
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderTools);
|
||||
}
|
||||
|
||||
if (sender == ToolsFloatingBarBtn)
|
||||
{
|
||||
lastBorderMouseDownObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isViewboxFloatingBarMarginAnimationRunning;
|
||||
@@ -1704,9 +1695,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.35),
|
||||
From = ViewboxFloatingBar.Margin,
|
||||
To = new Thickness(pos.X, pos.Y, 0, -20)
|
||||
To = new Thickness(pos.X, pos.Y, 0, -20),
|
||||
EasingFunction = new CircleEase()
|
||||
};
|
||||
marginAnimation.EasingFunction = new CircleEase();
|
||||
ViewboxFloatingBar.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
@@ -1809,9 +1800,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.35),
|
||||
From = ViewboxFloatingBar.Margin,
|
||||
To = new Thickness(pos.X, pos.Y, 0, -20)
|
||||
To = new Thickness(pos.X, pos.Y, 0, -20),
|
||||
EasingFunction = new CircleEase()
|
||||
};
|
||||
marginAnimation.EasingFunction = new CircleEase();
|
||||
ViewboxFloatingBar.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
@@ -1823,7 +1814,7 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
public async void PureViewboxFloatingBarMarginAnimationInPPTMode()
|
||||
public async void PureViewboxFloatingBarMarginAnimationInPPTMode(bool isRetry = false)
|
||||
{
|
||||
// 新增:在白板模式下不执行浮动栏动画
|
||||
if (currentMode == 1)
|
||||
@@ -1907,9 +1898,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.35),
|
||||
From = ViewboxFloatingBar.Margin,
|
||||
To = new Thickness(pos.X, pos.Y, 0, -20)
|
||||
To = new Thickness(pos.X, pos.Y, 0, -20),
|
||||
EasingFunction = new CircleEase()
|
||||
};
|
||||
marginAnimation.EasingFunction = new CircleEase();
|
||||
ViewboxFloatingBar.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
@@ -1919,12 +1910,31 @@ namespace Ink_Canvas
|
||||
{
|
||||
ViewboxFloatingBar.Margin = new Thickness(pos.X, pos.Y, -2000, -200);
|
||||
});
|
||||
|
||||
if (Settings.ModeSettings.IsPPTOnlyMode && !isRetry)
|
||||
{
|
||||
await Task.Delay(2000); // 等待动画完成后再检查
|
||||
|
||||
bool isFloatingBarVisible = false;
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 检查浮动栏是否真的显示了
|
||||
isFloatingBarVisible = ViewboxFloatingBar.Visibility == Visibility.Visible &&
|
||||
ViewboxFloatingBar.Margin.Left >= 0 &&
|
||||
ViewboxFloatingBar.Margin.Top >= 0;
|
||||
});
|
||||
|
||||
if (!isFloatingBarVisible)
|
||||
{
|
||||
PureViewboxFloatingBarMarginAnimationInPPTMode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async void CursorIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == Cursor_Icon && lastBorderMouseDownObject != Cursor_Icon) return;
|
||||
|
||||
// 禁用高级橡皮擦系统
|
||||
@@ -1985,6 +1995,10 @@ namespace Ink_Canvas
|
||||
GridTransparencyFakeBackground.Background = Brushes.Transparent;
|
||||
|
||||
GridBackgroundCoverHolder.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 点击鼠标按钮退出批注模式时的全屏还原
|
||||
RestoreFullScreenOnExitAnnotationMode();
|
||||
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
|
||||
@@ -2031,8 +2045,8 @@ namespace Ink_Canvas
|
||||
internal void PenIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == Pen_Icon && lastBorderMouseDownObject != Pen_Icon) return;
|
||||
|
||||
// 如果当前有选中的图片元素,先取消选中
|
||||
@@ -2062,7 +2076,6 @@ namespace Ink_Canvas
|
||||
{
|
||||
drawingShapeMode = 0;
|
||||
isLongPressSelected = false;
|
||||
ResetAllShapeButtonsOpacity();
|
||||
}
|
||||
|
||||
// 使用集中化的工具模式切换方法
|
||||
@@ -2099,6 +2112,21 @@ namespace Ink_Canvas
|
||||
|
||||
BtnHideInkCanvas.Content = "隐藏\n画板";
|
||||
|
||||
// 进入批注模式时的全屏处理(仅当未应用过全屏处理时)
|
||||
if (Settings.Advanced.IsEnableAvoidFullScreenHelper && !isFullScreenApplied)
|
||||
{
|
||||
// 设置为画板模式,允许全屏操作
|
||||
AvoidFullScreenHelper.SetBoardMode(true);
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(this).Handle, 0, 0,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
isFullScreenApplied = true; // 标记已应用全屏处理
|
||||
}
|
||||
|
||||
StackPanelCanvasControls.Visibility = Visibility.Visible;
|
||||
//AnimationsHelper.ShowWithSlideFromLeftAndFade(StackPanelCanvasControls);
|
||||
CheckEnableTwoFingerGestureBtnVisibility(true);
|
||||
@@ -2270,7 +2298,6 @@ namespace Ink_Canvas
|
||||
|
||||
internal void EraserIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EnterMultiTouchModeIfNeeded();
|
||||
bool isAlreadyEraser = inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint;
|
||||
forceEraser = false;
|
||||
forcePointEraser = true;
|
||||
@@ -2322,7 +2349,6 @@ namespace Ink_Canvas
|
||||
|
||||
private void BoardEraserIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EnterMultiTouchModeIfNeeded();
|
||||
bool isAlreadyEraser = inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint;
|
||||
forceEraser = false;
|
||||
forcePointEraser = true;
|
||||
@@ -2361,10 +2387,9 @@ namespace Ink_Canvas
|
||||
|
||||
private void EraserIconByStrokes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EnterMultiTouchModeIfNeeded();
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == EraserByStrokes_Icon && lastBorderMouseDownObject != EraserByStrokes_Icon) return;
|
||||
|
||||
// 禁用高级橡皮擦系统
|
||||
@@ -2395,8 +2420,8 @@ namespace Ink_Canvas
|
||||
private void CursorWithDelIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (lastBorderMouseDownObject is Panel panel)
|
||||
panel.Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == CursorWithDelFloatingBarBtn && lastBorderMouseDownObject != CursorWithDelFloatingBarBtn) return;
|
||||
|
||||
SymbolIconDelete_MouseUp(sender, null);
|
||||
@@ -2834,9 +2859,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
From = BorderSettings.RenderTransform.Value.OffsetX - 490, // 滑动距离
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(0.6)
|
||||
Duration = TimeSpan.FromSeconds(0.6),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut };
|
||||
Storyboard.SetTargetProperty(slideAnimation,
|
||||
new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
|
||||
|
||||
@@ -2914,12 +2939,6 @@ namespace Ink_Canvas
|
||||
// 清空触摸点计数器
|
||||
dec.Clear();
|
||||
|
||||
// 重置手掌擦状态
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
isPalmEraserActive = false;
|
||||
}
|
||||
|
||||
// 确保触摸事件能正常响应
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
@@ -2949,6 +2968,30 @@ namespace Ink_Canvas
|
||||
|
||||
private int currentMode;
|
||||
|
||||
// 退出批注模式时的全屏还原处理
|
||||
private void RestoreFullScreenOnExitAnnotationMode()
|
||||
{
|
||||
if (Settings.Advanced.IsEnableAvoidFullScreenHelper &&
|
||||
isFullScreenApplied &&
|
||||
currentMode == 0 && // 不在白板模式
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible) // 不在PPT放映模式
|
||||
{
|
||||
// 恢复为非画板模式,重新启用全屏限制
|
||||
AvoidFullScreenHelper.SetBoardMode(false);
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
// 退出批注模式,恢复到工作区域大小
|
||||
var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(this).Handle,
|
||||
workingArea.Left, workingArea.Top,
|
||||
workingArea.Width, workingArea.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
isFullScreenApplied = false; // 标记全屏处理已还原
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnSwitch_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (GridTransparencyFakeBackground.Background == Brushes.Transparent)
|
||||
@@ -3017,10 +3060,29 @@ namespace Ink_Canvas
|
||||
ClearStrokes(true);
|
||||
RestoreStrokes(true);
|
||||
|
||||
// 新增:在屏幕模式下恢复基础浮动栏的显示
|
||||
// 退出白板模式时取消全屏(仅在非PPT模式下)
|
||||
if (Settings.Advanced.IsEnableAvoidFullScreenHelper &&
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible) // 不在PPT放映模式
|
||||
{
|
||||
// 恢复为非画板模式,重新启用全屏限制
|
||||
AvoidFullScreenHelper.SetBoardMode(false);
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
// 退出白板模式,恢复到工作区域大小
|
||||
var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(this).Handle,
|
||||
workingArea.Left, workingArea.Top,
|
||||
workingArea.Width, workingArea.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
isFullScreenApplied = false; // 标记全屏处理已还原
|
||||
}
|
||||
|
||||
// 在屏幕模式下恢复基础浮动栏的显示
|
||||
ViewboxFloatingBar.Visibility = Visibility.Visible;
|
||||
|
||||
// 新增:退出白板时自动收纳功能 - 等待浮动栏完全展开后再收纳
|
||||
// 退出白板时自动收纳功能 - 等待浮动栏完全展开后再收纳
|
||||
if (Settings.Automation.IsAutoFoldWhenExitWhiteboard && !isFloatingBarFolded)
|
||||
{
|
||||
// 使用异步延迟,等待浮动栏展开动画完成后再收纳
|
||||
@@ -3070,6 +3132,22 @@ namespace Ink_Canvas
|
||||
|
||||
RestoreStrokes();
|
||||
|
||||
// 进入白板模式时全屏(仅在非PPT模式下)
|
||||
if (Settings.Advanced.IsEnableAvoidFullScreenHelper &&
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible) // 不在PPT放映模式
|
||||
{
|
||||
// 设置为画板模式,允许全屏操作
|
||||
AvoidFullScreenHelper.SetBoardMode(true);
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(this).Handle, 0, 0,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
isFullScreenApplied = true; // 标记已应用全屏处理
|
||||
}
|
||||
|
||||
ViewboxFloatingBar.Visibility = Visibility.Collapsed;
|
||||
|
||||
BtnSwitch.Content = "屏幕";
|
||||
@@ -3105,7 +3183,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
StackPanelPPTButtons.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
||||
if (Settings.Advanced.EnableUIAccessTopMost)
|
||||
{
|
||||
Topmost = true;
|
||||
@@ -3125,6 +3203,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (GridTransparencyFakeBackground.Background == Brushes.Transparent)
|
||||
{
|
||||
// 进入批注模式
|
||||
GridTransparencyFakeBackground.Opacity = 1;
|
||||
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
@@ -3149,6 +3228,21 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
BtnHideInkCanvas.Content = "隐藏\n画板";
|
||||
|
||||
// 进入批注模式时的全屏处理(仅当未应用过全屏处理时)
|
||||
if (Settings.Advanced.IsEnableAvoidFullScreenHelper && !isFullScreenApplied)
|
||||
{
|
||||
// 设置为画板模式,允许全屏操作
|
||||
AvoidFullScreenHelper.SetBoardMode(true);
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(this).Handle, 0, 0,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
isFullScreenApplied = true; // 标记已应用全屏处理
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3199,6 +3293,9 @@ namespace Ink_Canvas
|
||||
|
||||
GridBackgroundCoverHolder.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 退出批注模式时的全屏还原
|
||||
RestoreFullScreenOnExitAnnotationMode();
|
||||
|
||||
if (currentMode != 0)
|
||||
{
|
||||
SaveStrokes();
|
||||
@@ -3831,154 +3928,6 @@ namespace Ink_Canvas
|
||||
|
||||
#endregion
|
||||
|
||||
#region 触摸事件支持
|
||||
|
||||
/// <summary>
|
||||
/// 为浮动栏按钮添加触摸和手写笔事件支持,让触摸和手写笔点击直接调用对应的鼠标点击方法
|
||||
/// </summary>
|
||||
private void AddTouchSupportToFloatingBarButtons()
|
||||
{
|
||||
// 为主要的浮动栏按钮添加触摸和手写笔事件支持
|
||||
if (SymbolIconSelect != null)
|
||||
{
|
||||
SymbolIconSelect.TouchDown += (s, e) => SymbolIconSelect_MouseUp(s, null);
|
||||
SymbolIconSelect.StylusDown += (s, e) => SymbolIconSelect_MouseUp(s, null);
|
||||
}
|
||||
|
||||
if (SymbolIconUndo != null)
|
||||
{
|
||||
SymbolIconUndo.TouchDown += (s, e) => SymbolIconUndo_MouseUp(s, null);
|
||||
SymbolIconUndo.StylusDown += (s, e) => SymbolIconUndo_MouseUp(s, null);
|
||||
}
|
||||
|
||||
if (SymbolIconRedo != null)
|
||||
{
|
||||
SymbolIconRedo.TouchDown += (s, e) => SymbolIconRedo_MouseUp(s, null);
|
||||
SymbolIconRedo.StylusDown += (s, e) => SymbolIconRedo_MouseUp(s, null);
|
||||
}
|
||||
|
||||
if (SymbolIconDelete != null)
|
||||
{
|
||||
SymbolIconDelete.TouchDown += (s, e) => SymbolIconDelete_MouseUp(s, null);
|
||||
SymbolIconDelete.StylusDown += (s, e) => SymbolIconDelete_MouseUp(s, null);
|
||||
}
|
||||
|
||||
if (ToolsFloatingBarBtn != null)
|
||||
{
|
||||
ToolsFloatingBarBtn.TouchDown += (s, e) => SymbolIconTools_MouseUp(s, null);
|
||||
ToolsFloatingBarBtn.StylusDown += (s, e) => SymbolIconTools_MouseUp(s, null);
|
||||
}
|
||||
|
||||
if (RandomDrawPanel != null)
|
||||
{
|
||||
RandomDrawPanel.TouchDown += (s, e) => SymbolIconRand_MouseUp(s, null);
|
||||
RandomDrawPanel.StylusDown += (s, e) => SymbolIconRand_MouseUp(s, null);
|
||||
}
|
||||
|
||||
if (SingleDrawPanel != null)
|
||||
{
|
||||
SingleDrawPanel.TouchDown += (s, e) => SymbolIconRandOne_MouseUp(s, null);
|
||||
SingleDrawPanel.StylusDown += (s, e) => SymbolIconRandOne_MouseUp(s, null);
|
||||
}
|
||||
|
||||
// 注意:Screenshot和Settings按钮在XAML中没有直接的Name属性,需要通过其他方式绑定
|
||||
// 这些按钮的事件处理已经在XAML中通过MouseUp绑定
|
||||
|
||||
if (BorderFloatingBarMoveControls != null)
|
||||
{
|
||||
BorderFloatingBarMoveControls.TouchDown += (s, e) => SymbolIconEmoji_MouseUp(s, null);
|
||||
BorderFloatingBarMoveControls.StylusDown += (s, e) => SymbolIconEmoji_MouseUp(s, null);
|
||||
}
|
||||
|
||||
// 白板模式下的按钮不添加触摸事件支持,保持原有的鼠标事件处理
|
||||
|
||||
// 为快捷调色盘按钮添加触摸和手写笔事件支持
|
||||
if (QuickColorWhite != null)
|
||||
{
|
||||
QuickColorWhite.TouchDown += (s, e) => QuickColorWhite_Click(s, null);
|
||||
QuickColorWhite.StylusDown += (s, e) => QuickColorWhite_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorOrange != null)
|
||||
{
|
||||
QuickColorOrange.TouchDown += (s, e) => QuickColorOrange_Click(s, null);
|
||||
QuickColorOrange.StylusDown += (s, e) => QuickColorOrange_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorYellow != null)
|
||||
{
|
||||
QuickColorYellow.TouchDown += (s, e) => QuickColorYellow_Click(s, null);
|
||||
QuickColorYellow.StylusDown += (s, e) => QuickColorYellow_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorBlack != null)
|
||||
{
|
||||
QuickColorBlack.TouchDown += (s, e) => QuickColorBlack_Click(s, null);
|
||||
QuickColorBlack.StylusDown += (s, e) => QuickColorBlack_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorBlue != null)
|
||||
{
|
||||
QuickColorBlue.TouchDown += (s, e) => QuickColorBlue_Click(s, null);
|
||||
QuickColorBlue.StylusDown += (s, e) => QuickColorBlue_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorRed != null)
|
||||
{
|
||||
QuickColorRed.TouchDown += (s, e) => QuickColorRed_Click(s, null);
|
||||
QuickColorRed.StylusDown += (s, e) => QuickColorRed_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorGreen != null)
|
||||
{
|
||||
QuickColorGreen.TouchDown += (s, e) => QuickColorGreen_Click(s, null);
|
||||
QuickColorGreen.StylusDown += (s, e) => QuickColorGreen_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorPurple != null)
|
||||
{
|
||||
QuickColorPurple.TouchDown += (s, e) => QuickColorPurple_Click(s, null);
|
||||
QuickColorPurple.StylusDown += (s, e) => QuickColorPurple_Click(s, null);
|
||||
}
|
||||
|
||||
// 单行快捷调色盘
|
||||
if (QuickColorWhiteSingle != null)
|
||||
{
|
||||
QuickColorWhiteSingle.TouchDown += (s, e) => QuickColorWhite_Click(s, null);
|
||||
QuickColorWhiteSingle.StylusDown += (s, e) => QuickColorWhite_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorOrangeSingle != null)
|
||||
{
|
||||
QuickColorOrangeSingle.TouchDown += (s, e) => QuickColorOrange_Click(s, null);
|
||||
QuickColorOrangeSingle.StylusDown += (s, e) => QuickColorOrange_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorYellowSingle != null)
|
||||
{
|
||||
QuickColorYellowSingle.TouchDown += (s, e) => QuickColorYellow_Click(s, null);
|
||||
QuickColorYellowSingle.StylusDown += (s, e) => QuickColorYellow_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorBlackSingle != null)
|
||||
{
|
||||
QuickColorBlackSingle.TouchDown += (s, e) => QuickColorBlack_Click(s, null);
|
||||
QuickColorBlackSingle.StylusDown += (s, e) => QuickColorBlack_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorRedSingle != null)
|
||||
{
|
||||
QuickColorRedSingle.TouchDown += (s, e) => QuickColorRed_Click(s, null);
|
||||
QuickColorRedSingle.StylusDown += (s, e) => QuickColorRed_Click(s, null);
|
||||
}
|
||||
|
||||
if (QuickColorGreenSingle != null)
|
||||
{
|
||||
QuickColorGreenSingle.TouchDown += (s, e) => QuickColorGreen_Click(s, null);
|
||||
QuickColorGreenSingle.StylusDown += (s, e) => QuickColorGreen_Click(s, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制禁用所有双指手势功能(当多指书写模式启用时)
|
||||
/// </summary>
|
||||
@@ -4006,7 +3955,5 @@ namespace Ink_Canvas
|
||||
BoardToggleSwitchEnableTwoFingerRotation.IsOn = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+277
-194
@@ -77,8 +77,7 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
#region PPT State Management
|
||||
private bool wasFloatingBarFoldedWhenEnterSlideShow;
|
||||
private bool isEnteredSlideShowEndEvent;
|
||||
private bool isEnteredSlideShowEndEvent;
|
||||
private bool isPresentationHaveBlackSpace;
|
||||
|
||||
// 长按翻页相关字段
|
||||
@@ -95,11 +94,14 @@ namespace Ink_Canvas
|
||||
private int _lastPlaybackPage = 0;
|
||||
private bool _shouldNavigateToLastPage = false;
|
||||
|
||||
// 当前播放页码跟踪
|
||||
private int _currentSlideShowPosition = 0;
|
||||
|
||||
// 页面切换防抖机制
|
||||
private DateTime _lastSlideSwitchTime = DateTime.MinValue;
|
||||
private int _pendingSlideIndex = -1;
|
||||
private System.Timers.Timer _slideSwitchDebounceTimer;
|
||||
private const int SlideSwitchDebounceMs = 150; // 防抖延迟150毫秒
|
||||
private const int SlideSwitchDebounceMs = 150;
|
||||
private bool _isInkClearedByButton = false;
|
||||
#endregion
|
||||
|
||||
#region PPT Managers
|
||||
@@ -150,6 +152,10 @@ namespace Ink_Canvas
|
||||
_pptUIManager.PPTRBButtonPosition = Settings.PowerPointSettings.PPTRBButtonPosition;
|
||||
_pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable;
|
||||
_pptUIManager.EnablePPTButtonLongPressPageTurn = Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn;
|
||||
_pptUIManager.PPTLSButtonOpacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
_pptUIManager.PPTRSButtonOpacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
_pptUIManager.PPTLBButtonOpacity = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
_pptUIManager.PPTRBButtonOpacity = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
|
||||
LogHelper.WriteLogToFile("PPT管理器初始化完成", LogHelper.LogType.Event);
|
||||
}
|
||||
@@ -522,6 +528,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
bool isInSlideShowWhenOpened = _pptManager?.IsInSlideShow == true;
|
||||
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 在初始化墨迹管理器之前,先清理画布上的所有墨迹
|
||||
@@ -544,8 +552,7 @@ namespace Ink_Canvas
|
||||
CheckAndNotifyHiddenSlides(pres);
|
||||
}
|
||||
|
||||
// 检查自动播放设置
|
||||
if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation)
|
||||
if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation && !isInSlideShowWhenOpened)
|
||||
{
|
||||
CheckAndNotifyAutoPlaySettings(pres);
|
||||
}
|
||||
@@ -612,9 +619,6 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 始终记录进入放映时浮动栏收纳状态,用于退出时恢复
|
||||
wasFloatingBarFoldedWhenEnterSlideShow = isFloatingBarFolded;
|
||||
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded)
|
||||
@@ -641,12 +645,16 @@ namespace Ink_Canvas
|
||||
activePresentation = wn.Presentation;
|
||||
currentSlide = wn.View.CurrentShowPosition;
|
||||
totalSlides = activePresentation.Slides.Count;
|
||||
// 初始化当前播放页码跟踪
|
||||
_currentSlideShowPosition = currentSlide;
|
||||
}
|
||||
else
|
||||
{
|
||||
activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
totalSlides = _pptManager?.SlidesCount ?? 0;
|
||||
// 初始化当前播放页码跟踪
|
||||
_currentSlideShowPosition = currentSlide;
|
||||
}
|
||||
|
||||
if (activePresentation != null)
|
||||
@@ -722,7 +730,7 @@ namespace Ink_Canvas
|
||||
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
|
||||
!Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(600);
|
||||
// 先进入批注模式,这会显示调色盘
|
||||
PenIcon_Click(null, null);
|
||||
// 然后设置颜色
|
||||
@@ -763,6 +771,7 @@ namespace Ink_Canvas
|
||||
LoadCurrentSlideInk(currentSlide);
|
||||
});
|
||||
|
||||
|
||||
if (!isFloatingBarFolded)
|
||||
{
|
||||
new Thread(() =>
|
||||
@@ -775,9 +784,8 @@ namespace Ink_Canvas
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,7 +793,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (wn?.View == null || wn.Presentation == null)
|
||||
{
|
||||
@@ -796,8 +804,58 @@ namespace Ink_Canvas
|
||||
var activePresentation = wn.Presentation;
|
||||
var totalSlides = activePresentation.Slides.Count;
|
||||
|
||||
// 使用防抖机制处理页面切换
|
||||
HandleSlideSwitchWithDebounce(currentSlide, totalSlides);
|
||||
// 获取之前的页码(用于保存墨迹)
|
||||
var previousSlide = _currentSlideShowPosition > 0 ? _currentSlideShowPosition :
|
||||
(_pptManager?.GetCurrentSlideNumber() ?? 0);
|
||||
|
||||
if (_isInkClearedByButton)
|
||||
{
|
||||
_isInkClearedByButton = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
StrokeCollection strokesToSave = null;
|
||||
if (previousSlide > 0 && previousSlide != currentSlide && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
strokesToSave = inkCanvas.Strokes.Clone();
|
||||
}
|
||||
|
||||
// 清除墨迹
|
||||
if (inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
}
|
||||
|
||||
// 异步保存之前页面的墨迹
|
||||
if (strokesToSave != null && previousSlide > 0 && previousSlide != currentSlide)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
bool canWrite = _singlePPTInkManager?.CanWriteInk(previousSlide) == true;
|
||||
if (canWrite)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(previousSlide, strokesToSave);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"异步保存PPT页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前播放页码
|
||||
_currentSlideShowPosition = currentSlide;
|
||||
|
||||
LoadCurrentSlideInk(currentSlide, skipClear: true);
|
||||
_pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
|
||||
|
||||
});
|
||||
}
|
||||
@@ -811,39 +869,39 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
// PPT退出时自动收纳浮动栏
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
|
||||
if (isEnteredSlideShowEndEvent) return;
|
||||
isEnteredSlideShowEndEvent = true;
|
||||
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
// 获取当前播放页码,优先使用跟踪的页码,否则尝试从PPT管理器获取
|
||||
int currentPage = _currentSlideShowPosition;
|
||||
if (currentPage <= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentPage = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果无法获取,尝试从演示文稿的SlideShowWindow获取
|
||||
try
|
||||
{
|
||||
if (pres.SlideShowWindow != null && pres.SlideShowWindow.View != null)
|
||||
{
|
||||
currentPage = pres.SlideShowWindow.View.CurrentShowPosition;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// 保存墨迹和位置信息
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres, currentPage);
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
@@ -903,7 +961,7 @@ namespace Ink_Canvas
|
||||
if (GridTransparencyFakeBackground.Background != Brushes.Transparent)
|
||||
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
||||
SetCurrentToolMode(InkCanvasEditingMode.None);
|
||||
|
||||
|
||||
UpdateCurrentToolMode("cursor");
|
||||
SetFloatingBarHighlightPosition("cursor");
|
||||
}
|
||||
@@ -916,8 +974,15 @@ namespace Ink_Canvas
|
||||
await Task.Delay(100);
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
PureViewboxFloatingBarMarginAnimationInDesktopMode();
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
PureViewboxFloatingBarMarginAnimationInDesktopMode();
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
PureViewboxFloatingBarMarginAnimationInDesktopMode();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -938,7 +1003,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
else if (Settings.PowerPointSettings.IsNotifyPreviousPage)
|
||||
{
|
||||
ShowPreviousPageNotification(pres);
|
||||
if (_pptManager?.IsInSlideShow != true)
|
||||
{
|
||||
ShowPreviousPageNotification(pres);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1050,7 +1118,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) return;
|
||||
if (_pptManager?.IsInSlideShow == true) return;
|
||||
|
||||
bool hasSlideTimings = false;
|
||||
if (pres?.Slides != null)
|
||||
@@ -1098,13 +1166,16 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadCurrentSlideInk(int slideIndex)
|
||||
private void LoadCurrentSlideInk(int slideIndex, bool skipClear = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
// 如果未跳过清除,则清除当前墨迹
|
||||
if (!skipClear)
|
||||
{
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
}
|
||||
StrokeCollection strokes = _singlePPTInkManager?.LoadSlideStrokes(slideIndex);
|
||||
|
||||
if (strokes != null && strokes.Count > 0)
|
||||
@@ -1140,9 +1211,6 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 重置进入PPT时的浮动栏收纳状态记录
|
||||
wasFloatingBarFoldedWhenEnterSlideShow = false;
|
||||
|
||||
// 重置PPT放映结束事件标志
|
||||
isEnteredSlideShowEndEvent = false;
|
||||
|
||||
@@ -1153,6 +1221,9 @@ namespace Ink_Canvas
|
||||
_lastPlaybackPage = 0;
|
||||
_shouldNavigateToLastPage = false;
|
||||
|
||||
// 重置当前播放页码跟踪
|
||||
_currentSlideShowPosition = 0;
|
||||
|
||||
// 重置页面切换防抖机制
|
||||
_lastSlideSwitchTime = DateTime.MinValue;
|
||||
_pendingSlideIndex = -1;
|
||||
@@ -1170,51 +1241,14 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private void HandleSlideSwitchWithDebounce(int currentSlide, int totalSlides)
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 如果距离上次切换时间太短,使用防抖机制
|
||||
if (now - _lastSlideSwitchTime < TimeSpan.FromMilliseconds(SlideSwitchDebounceMs))
|
||||
{
|
||||
_pendingSlideIndex = currentSlide;
|
||||
|
||||
// 停止之前的定时器
|
||||
_slideSwitchDebounceTimer?.Stop();
|
||||
|
||||
// 创建新的定时器
|
||||
_slideSwitchDebounceTimer = new System.Timers.Timer(SlideSwitchDebounceMs);
|
||||
_slideSwitchDebounceTimer.Elapsed += (sender, e) =>
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (_pendingSlideIndex > 0)
|
||||
{
|
||||
SwitchSlideInk(_pendingSlideIndex);
|
||||
_pptUIManager?.UpdateCurrentSlideNumber(_pendingSlideIndex, totalSlides);
|
||||
_pendingSlideIndex = -1;
|
||||
}
|
||||
});
|
||||
_slideSwitchDebounceTimer?.Stop();
|
||||
};
|
||||
_slideSwitchDebounceTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接处理页面切换
|
||||
SwitchSlideInk(currentSlide);
|
||||
_pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
|
||||
}
|
||||
|
||||
_lastSlideSwitchTime = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理页面切换防抖失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchSlideInk(int newSlideIndex)
|
||||
/// <summary>
|
||||
/// 切换页面墨迹
|
||||
/// </summary>
|
||||
/// <param name="newSlideIndex">新页面索引</param>
|
||||
/// <param name="skipClear">是否跳过清除操作(如果已在翻页时立即清除,则设为true)</param>
|
||||
private void SwitchSlideInk(int newSlideIndex, bool skipClear = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1235,20 +1269,25 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 如果有当前墨迹且不是第一次切换,先保存到当前页面
|
||||
if (inkCanvas.Strokes.Count > 0 && currentSlideIndex > 0 && currentSlideIndex != newSlideIndex)
|
||||
if (currentSlideIndex > 0 && currentSlideIndex != newSlideIndex)
|
||||
{
|
||||
bool canWrite = _singlePPTInkManager?.CanWriteInk(currentSlideIndex) == true;
|
||||
|
||||
if (canWrite)
|
||||
if (canWrite && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
|
||||
}
|
||||
}
|
||||
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
if (!skipClear)
|
||||
{
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
}
|
||||
|
||||
// 加载新页面的墨迹
|
||||
StrokeCollection newStrokes = _singlePPTInkManager?.SwitchToSlide(newSlideIndex, null);
|
||||
|
||||
|
||||
if (newStrokes != null && newStrokes.Count > 0)
|
||||
{
|
||||
inkCanvas.Strokes.Add(newStrokes);
|
||||
@@ -1383,25 +1422,72 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存当前页墨迹
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
if (currentSlide > 0)
|
||||
var previousSlideBeforeNavigate = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
|
||||
StrokeCollection strokesToSave = null;
|
||||
if (previousSlideBeforeNavigate > 0 && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
strokesToSave = inkCanvas.Strokes.Clone();
|
||||
}
|
||||
|
||||
// 保存截图(如果启用)
|
||||
if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
|
||||
Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
|
||||
{
|
||||
var presentationName = _pptManager?.GetPresentationName() ?? "";
|
||||
SaveScreenShot(true, $"{presentationName}/{currentSlide}");
|
||||
}
|
||||
|
||||
// 执行翻页
|
||||
if (_pptManager?.TryNavigatePrevious() == true)
|
||||
{
|
||||
// 翻页成功,等待事件处理墨迹切换
|
||||
var currentSlideAfterNavigate = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
|
||||
if (previousSlideBeforeNavigate == currentSlideAfterNavigate && previousSlideBeforeNavigate > 0)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
currentSlideAfterNavigate = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
}
|
||||
|
||||
if (previousSlideBeforeNavigate != currentSlideAfterNavigate && previousSlideBeforeNavigate > 0)
|
||||
{
|
||||
if (inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
_isInkClearedByButton = true;
|
||||
}
|
||||
|
||||
if (strokesToSave != null && previousSlideBeforeNavigate > 0)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(previousSlideBeforeNavigate, strokesToSave);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"异步保存PPT上一页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 异步保存截图(如果启用)
|
||||
if (strokesToSave.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
|
||||
Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var presentationName = _pptManager?.GetPresentationName() ?? "";
|
||||
SaveScreenShot(true, $"{presentationName}/{previousSlideBeforeNavigate}");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"异步保存PPT上一页截图失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1423,25 +1509,74 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存当前页墨迹
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
if (currentSlide > 0)
|
||||
var previousSlideBeforeNavigate = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
|
||||
StrokeCollection strokesToSave = null;
|
||||
if (previousSlideBeforeNavigate > 0 && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
strokesToSave = inkCanvas.Strokes.Clone();
|
||||
}
|
||||
|
||||
// 保存截图(如果启用)
|
||||
if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
|
||||
Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
|
||||
{
|
||||
var presentationName = _pptManager?.GetPresentationName() ?? "";
|
||||
SaveScreenShot(true, $"{presentationName}/{currentSlide}");
|
||||
}
|
||||
var skipAnimations = Settings.PowerPointSettings.SkipAnimationsWhenGoNext;
|
||||
|
||||
// 执行翻页
|
||||
if (_pptManager?.TryNavigateNext() == true)
|
||||
if (_pptManager?.TryNavigateNext(skipAnimations: skipAnimations) == true)
|
||||
{
|
||||
// 翻页成功,等待事件处理墨迹切换
|
||||
var currentSlideAfterNavigate = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
|
||||
if (previousSlideBeforeNavigate == currentSlideAfterNavigate && previousSlideBeforeNavigate > 0)
|
||||
{
|
||||
Thread.Sleep(50);
|
||||
currentSlideAfterNavigate = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
}
|
||||
|
||||
if (previousSlideBeforeNavigate != currentSlideAfterNavigate && previousSlideBeforeNavigate > 0)
|
||||
{
|
||||
if (inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
_isInkClearedByButton = true;
|
||||
}
|
||||
|
||||
if (strokesToSave != null && previousSlideBeforeNavigate > 0)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(previousSlideBeforeNavigate, strokesToSave);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"异步保存PPT下一页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 异步保存截图(如果启用)
|
||||
if (strokesToSave.Count > Settings.Automation.MinimumAutomationStrokeNumber &&
|
||||
Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var presentationName = _pptManager?.GetPresentationName() ?? "";
|
||||
SaveScreenShot(true, $"{presentationName}/{previousSlideBeforeNavigate}");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"异步保存PPT下一页截图失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1609,7 +1744,7 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
|
||||
});
|
||||
|
||||
// 手动处理收纳状态恢复,因为OnPPTSlideShowEnd事件可能未触发
|
||||
// 手动处理自动收纳,因为OnPPTSlideShowEnd事件可能未触发
|
||||
await HandleManualSlideShowEnd();
|
||||
}
|
||||
|
||||
@@ -1617,27 +1752,14 @@ namespace Ink_Canvas
|
||||
SetCurrentToolMode(InkCanvasEditingMode.None);
|
||||
|
||||
await Task.Delay(150);
|
||||
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1651,76 +1773,37 @@ namespace Ink_Canvas
|
||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||
});
|
||||
|
||||
// 异常情况下也手动处理收纳状态恢复
|
||||
// 异常情况下也手动处理自动收纳
|
||||
await HandleManualSlideShowEnd();
|
||||
|
||||
// 异常情况下也要根据设置决定浮动栏边距
|
||||
await Task.Delay(150);
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动处理PPT放映结束时的收纳状态恢复
|
||||
/// 手动处理PPT放映结束时的自动收纳
|
||||
/// </summary>
|
||||
private async Task HandleManualSlideShowEnd()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
// PPT退出时自动收纳浮动栏
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && !isFloatingBarFolded)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果两个功能都关闭,确保浮动栏展开
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"手动处理PPT放映结束收纳状态恢复失败: {ex}", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"手动处理PPT放映结束自动收纳失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -15,6 +16,8 @@ using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Color = System.Drawing.Color;
|
||||
using File = System.IO.File;
|
||||
using Image = System.Windows.Controls.Image;
|
||||
@@ -127,12 +130,86 @@ namespace Ink_Canvas
|
||||
SaveSinglePageStrokesAsImage(savePathWithName, newNotice);
|
||||
}
|
||||
}
|
||||
else if (Settings.Automation.IsSaveStrokesAsXML)
|
||||
{
|
||||
// XML保存模式 - 检查是否存在多页面墨迹
|
||||
bool hasMultiplePages = false;
|
||||
List<StrokeCollection> allPageStrokes = new List<StrokeCollection>();
|
||||
|
||||
// 检查PPT放映模式下的多页面墨迹
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _pptManager?.IsConnected == true)
|
||||
{
|
||||
hasMultiplePages = true;
|
||||
var totalSlides = _pptManager.SlidesCount;
|
||||
var currentSlide = _pptManager.GetCurrentSlideNumber();
|
||||
|
||||
for (int i = 1; i <= totalSlides; i++)
|
||||
{
|
||||
var slideStrokes = _singlePPTInkManager?.LoadSlideStrokes(i);
|
||||
if (slideStrokes != null && slideStrokes.Count > 0)
|
||||
{
|
||||
allPageStrokes.Add(slideStrokes);
|
||||
}
|
||||
else if (i == currentSlide && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
allPageStrokes.Add(inkCanvas.Strokes.Clone());
|
||||
}
|
||||
else
|
||||
{
|
||||
allPageStrokes.Add(new StrokeCollection());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检查白板模式下的多页面墨迹
|
||||
else if (currentMode != 0 && WhiteboardTotalCount > 1)
|
||||
{
|
||||
hasMultiplePages = true;
|
||||
for (int i = 1; i <= WhiteboardTotalCount; i++)
|
||||
{
|
||||
if (TimeMachineHistories[i] != null)
|
||||
{
|
||||
var strokes = ApplyHistoriesToNewStrokeCollection(TimeMachineHistories[i]);
|
||||
allPageStrokes.Add(strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
allPageStrokes.Add(new StrokeCollection());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMultiplePages && allPageStrokes.Count > 0)
|
||||
{
|
||||
// 多页面XML保存为压缩包
|
||||
string zipFileName = Path.ChangeExtension(savePathWithName, "zip");
|
||||
SaveMultiPageStrokesAsXMLZip(allPageStrokes, zipFileName, newNotice);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 单页面XML保存
|
||||
string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
|
||||
SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
|
||||
if (newNotice) ShowNotification("墨迹成功保存为XML格式至 " + xmlPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 常规保存模式 - 仅保存墨迹对象
|
||||
var fs = new FileStream(savePathWithName, FileMode.Create);
|
||||
inkCanvas.Strokes.Save(fs);
|
||||
fs.Close();
|
||||
if (Settings.Automation.IsSaveStrokesAsXML)
|
||||
{
|
||||
// 保存为XML格式
|
||||
string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
|
||||
SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
|
||||
if (newNotice) ShowNotification("墨迹成功保存为XML格式至 " + xmlPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 保存为二进制格式
|
||||
var fs = new FileStream(savePathWithName, FileMode.Create);
|
||||
inkCanvas.Strokes.Save(fs);
|
||||
fs.Close();
|
||||
if (newNotice) ShowNotification("墨迹成功保存至 " + savePathWithName);
|
||||
}
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
@@ -149,7 +226,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 保存元素信息
|
||||
var elementInfos = new List<CanvasElementInfo>();
|
||||
foreach (var child in inkCanvas.Children)
|
||||
@@ -168,8 +245,7 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
}
|
||||
File.WriteAllText(Path.ChangeExtension(savePathWithName, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Formatting.Indented));
|
||||
if (newNotice) ShowNotification("墨迹成功保存至 " + savePathWithName);
|
||||
File.WriteAllText(Path.ChangeExtension(savePathWithName, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -179,6 +255,201 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将StrokeCollection保存为XML格式
|
||||
/// </summary>
|
||||
private void SaveStrokesAsXML(StrokeCollection strokes, string xmlPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用XDocument创建XML文档
|
||||
XDocument doc = new XDocument(
|
||||
new XDeclaration("1.0", "utf-8", "yes"),
|
||||
new XElement("InkCanvasStrokes",
|
||||
new XAttribute("Version", "1.0"),
|
||||
new XAttribute("StrokeCount", strokes.Count),
|
||||
new XAttribute("SaveTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
|
||||
from stroke in strokes
|
||||
select new XElement("Stroke",
|
||||
new XAttribute("DrawingAttributes", SerializeDrawingAttributes(stroke.DrawingAttributes)),
|
||||
new XElement("StylusPoints",
|
||||
from point in stroke.StylusPoints
|
||||
select new XElement("StylusPoint",
|
||||
new XAttribute("X", point.X),
|
||||
new XAttribute("Y", point.Y),
|
||||
new XAttribute("PressureFactor", point.PressureFactor)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// 保存XML文件
|
||||
using (var writer = new XmlTextWriter(xmlPath, Encoding.UTF8))
|
||||
{
|
||||
writer.Formatting = System.Xml.Formatting.Indented;
|
||||
doc.Save(writer);
|
||||
}
|
||||
|
||||
// 同时保存元素信息
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
||||
File.WriteAllText(Path.ChangeExtension(xmlPath, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
|
||||
// 异步上传到Dlass
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(xmlPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存XML格式墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 序列化DrawingAttributes为字符串
|
||||
/// </summary>
|
||||
private string SerializeDrawingAttributes(DrawingAttributes da)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"Color={da.Color};");
|
||||
sb.Append($"Width={da.Width};");
|
||||
sb.Append($"Height={da.Height};");
|
||||
sb.Append($"FitToCurve={da.FitToCurve};");
|
||||
sb.Append($"IsHighlighter={da.IsHighlighter};");
|
||||
sb.Append($"IgnorePressure={da.IgnorePressure};");
|
||||
sb.Append($"StylusTip={da.StylusTip};");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将多页面墨迹保存为XML格式压缩包
|
||||
/// </summary>
|
||||
private void SaveMultiPageStrokesAsXMLZip(List<StrokeCollection> allPageStrokes, string zipFileName, bool newNotice)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建临时目录来存放文件
|
||||
string tempDir = Path.Combine(Path.GetTempPath(), $"InkCanvas_MultiPage_XML_{DateTime.Now:yyyyMMdd_HHmmss}");
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
try
|
||||
{
|
||||
// 保存所有页面的XML文件到临时目录
|
||||
for (int i = 0; i < allPageStrokes.Count; i++)
|
||||
{
|
||||
var strokes = allPageStrokes[i];
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
// 保存XML文件
|
||||
string xmlFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.xml");
|
||||
SaveStrokesAsXML(strokes, xmlFileName);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存元数据信息
|
||||
string metadataFile = Path.Combine(tempDir, "metadata.txt");
|
||||
using (var writer = new StreamWriter(metadataFile, false, Encoding.UTF8))
|
||||
{
|
||||
writer.WriteLine($"保存时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||||
writer.WriteLine($"总页数: {allPageStrokes.Count}");
|
||||
writer.WriteLine($"模式: {(currentMode == 0 ? "PPT放映" : "白板")}");
|
||||
writer.WriteLine($"格式: XML");
|
||||
if (currentMode != 0)
|
||||
{
|
||||
writer.WriteLine($"当前页面: {CurrentWhiteboardIndex}");
|
||||
writer.WriteLine($"总页面数: {WhiteboardTotalCount}");
|
||||
}
|
||||
else if (pptApplication != null)
|
||||
{
|
||||
writer.WriteLine($"PPT名称: {pptApplication.SlideShowWindows[1].Presentation.Name}");
|
||||
writer.WriteLine($"PPT总页数: {pptApplication.SlideShowWindows[1].Presentation.Slides.Count}");
|
||||
writer.WriteLine($"PPT文件路径: {pptApplication.SlideShowWindows[1].Presentation.FullName}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < allPageStrokes.Count; i++)
|
||||
{
|
||||
writer.WriteLine($"页面 {i + 1}: {allPageStrokes[i].Count} 条墨迹");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建ZIP文件
|
||||
if (File.Exists(zipFileName))
|
||||
File.Delete(zipFileName);
|
||||
|
||||
ZipFile.CreateFromDirectory(tempDir, zipFileName);
|
||||
|
||||
// 异步上传ZIP文件到Dlass
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(zipFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
if (newNotice) ShowNotification($"多页面XML墨迹成功保存至压缩包 {zipFileName}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 清理临时目录
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
Directory.Delete(tempDir, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理临时目录失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存多页面XML墨迹压缩包失败: {ex}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将多页面墨迹保存为压缩包
|
||||
/// </summary>
|
||||
@@ -411,7 +682,7 @@ namespace Ink_Canvas
|
||||
var openFileDialog = new OpenFileDialog();
|
||||
openFileDialog.InitialDirectory = Settings.Automation.AutoSavedStrokesLocation;
|
||||
openFileDialog.Title = "打开墨迹文件";
|
||||
openFileDialog.Filter = "Ink Canvas Strokes File (*.icstk)|*.icstk|ICC压缩包 (*.zip)|*.zip";
|
||||
openFileDialog.Filter = "Ink Canvas Strokes File (*.icstk)|*.icstk|XML墨迹文件 (*.xml)|*.xml|ICC压缩包 (*.zip)|*.zip|所有支持的文件 (*.icstk;*.xml;*.zip)|*.icstk;*.xml;*.zip";
|
||||
if (openFileDialog.ShowDialog() != true) return;
|
||||
LogHelper.WriteLogToFile($"Strokes Insert: Name: {openFileDialog.FileName}",
|
||||
LogHelper.LogType.Event);
|
||||
@@ -422,12 +693,17 @@ namespace Ink_Canvas
|
||||
|
||||
if (fileExtension == ".zip")
|
||||
{
|
||||
// 处理ICC压缩包
|
||||
// 处理ICC压缩包(可能包含XML格式)
|
||||
OpenICCZipFile(openFileDialog.FileName);
|
||||
}
|
||||
else if (fileExtension == ".xml")
|
||||
{
|
||||
// 处理XML格式墨迹文件
|
||||
OpenXMLStrokeFile(openFileDialog.FileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理单个墨迹文件
|
||||
// 处理单个墨迹文件(二进制格式)
|
||||
OpenSingleStrokeFile(openFileDialog.FileName);
|
||||
}
|
||||
|
||||
@@ -583,21 +859,39 @@ namespace Ink_Canvas
|
||||
// 重置PPT墨迹存储
|
||||
_singlePPTInkManager?.ClearAllStrokes();
|
||||
|
||||
// 读取所有页面的墨迹文件
|
||||
var files = Directory.GetFiles(tempDir, "page_*.icstk");
|
||||
foreach (var file in files)
|
||||
// 读取所有页面的墨迹文件(支持.icstk和.xml格式)
|
||||
var icstkFiles = Directory.GetFiles(tempDir, "page_*.icstk");
|
||||
var xmlFiles = Directory.GetFiles(tempDir, "page_*.xml");
|
||||
var allFiles = new List<string>();
|
||||
allFiles.AddRange(icstkFiles);
|
||||
allFiles.AddRange(xmlFiles);
|
||||
|
||||
foreach (var file in allFiles)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(file);
|
||||
if (fileName.StartsWith("page_") && int.TryParse(fileName.Substring(5), out int pageNumber))
|
||||
{
|
||||
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
|
||||
StrokeCollection strokes = null;
|
||||
string extension = Path.GetExtension(file).ToLower();
|
||||
|
||||
if (extension == ".xml")
|
||||
{
|
||||
var strokes = new StrokeCollection(fs);
|
||||
if (strokes.Count > 0)
|
||||
// 从XML文件加载
|
||||
strokes = LoadStrokesFromXML(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从二进制文件加载
|
||||
using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
_singlePPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes);
|
||||
strokes = new StrokeCollection(fs);
|
||||
}
|
||||
}
|
||||
|
||||
if (strokes != null && strokes.Count > 0)
|
||||
{
|
||||
_singlePPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +906,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功恢复PPT墨迹,共{files.Length}页");
|
||||
LogHelper.WriteLogToFile($"成功恢复PPT墨迹,共{allFiles.Count}页");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -693,6 +987,173 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开XML格式的墨迹文件
|
||||
/// </summary>
|
||||
public void OpenXMLStrokeFile(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument doc = XDocument.Load(filePath);
|
||||
var root = doc.Root;
|
||||
if (root == null || root.Name != "InkCanvasStrokes")
|
||||
{
|
||||
throw new Exception("无效的XML墨迹文件格式");
|
||||
}
|
||||
|
||||
var strokes = new StrokeCollection();
|
||||
foreach (var strokeElement in root.Elements("Stroke"))
|
||||
{
|
||||
var drawingAttributesStr = strokeElement.Attribute("DrawingAttributes")?.Value ?? "";
|
||||
var da = ParseDrawingAttributes(drawingAttributesStr);
|
||||
|
||||
var stylusPoints = new StylusPointCollection();
|
||||
var stylusPointsElement = strokeElement.Element("StylusPoints");
|
||||
if (stylusPointsElement != null)
|
||||
{
|
||||
foreach (var pointElement in stylusPointsElement.Elements("StylusPoint"))
|
||||
{
|
||||
double x = double.Parse(pointElement.Attribute("X")?.Value ?? "0");
|
||||
double y = double.Parse(pointElement.Attribute("Y")?.Value ?? "0");
|
||||
float pressure = float.Parse(pointElement.Attribute("PressureFactor")?.Value ?? "0.5");
|
||||
stylusPoints.Add(new StylusPoint(x, y, pressure));
|
||||
}
|
||||
}
|
||||
|
||||
if (stylusPoints.Count > 0)
|
||||
{
|
||||
var stroke = new Stroke(stylusPoints) { DrawingAttributes = da };
|
||||
strokes.Add(stroke);
|
||||
}
|
||||
}
|
||||
|
||||
ClearStrokes(true);
|
||||
timeMachine.ClearStrokeHistory();
|
||||
inkCanvas.Strokes.Add(strokes);
|
||||
LogHelper.NewLog($"XML Strokes Insert: Strokes Count: {inkCanvas.Strokes.Count}");
|
||||
|
||||
// 恢复元素信息
|
||||
var elementsFile = Path.ChangeExtension(filePath, ".elements.json");
|
||||
if (File.Exists(elementsFile))
|
||||
{
|
||||
var elementInfos = JsonConvert.DeserializeObject<List<CanvasElementInfo>>(File.ReadAllText(elementsFile));
|
||||
foreach (var info in elementInfos)
|
||||
{
|
||||
if (info.Type == "Image" && File.Exists(info.SourcePath))
|
||||
{
|
||||
var img = new Image
|
||||
{
|
||||
Source = new BitmapImage(new Uri(info.SourcePath)),
|
||||
Width = info.Width,
|
||||
Height = info.Height,
|
||||
Stretch = Enum.TryParse<Stretch>(info.Stretch, out var stretch) ? stretch : Stretch.Fill
|
||||
};
|
||||
InkCanvas.SetLeft(img, info.Left);
|
||||
InkCanvas.SetTop(img, info.Top);
|
||||
inkCanvas.Children.Add(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开XML墨迹文件失败: {ex}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从XML文件加载StrokeCollection(辅助方法,用于ZIP文件恢复)
|
||||
/// </summary>
|
||||
private StrokeCollection LoadStrokesFromXML(string xmlPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument doc = XDocument.Load(xmlPath);
|
||||
var root = doc.Root;
|
||||
if (root == null || root.Name != "InkCanvasStrokes")
|
||||
{
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
var strokes = new StrokeCollection();
|
||||
foreach (var strokeElement in root.Elements("Stroke"))
|
||||
{
|
||||
var drawingAttributesStr = strokeElement.Attribute("DrawingAttributes")?.Value ?? "";
|
||||
var da = ParseDrawingAttributes(drawingAttributesStr);
|
||||
|
||||
var stylusPoints = new StylusPointCollection();
|
||||
var stylusPointsElement = strokeElement.Element("StylusPoints");
|
||||
if (stylusPointsElement != null)
|
||||
{
|
||||
foreach (var pointElement in stylusPointsElement.Elements("StylusPoint"))
|
||||
{
|
||||
double x = double.Parse(pointElement.Attribute("X")?.Value ?? "0");
|
||||
double y = double.Parse(pointElement.Attribute("Y")?.Value ?? "0");
|
||||
float pressure = float.Parse(pointElement.Attribute("PressureFactor")?.Value ?? "0.5");
|
||||
stylusPoints.Add(new StylusPoint(x, y, pressure));
|
||||
}
|
||||
}
|
||||
|
||||
if (stylusPoints.Count > 0)
|
||||
{
|
||||
var stroke = new Stroke(stylusPoints) { DrawingAttributes = da };
|
||||
strokes.Add(stroke);
|
||||
}
|
||||
}
|
||||
|
||||
return strokes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从XML加载墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
return new StrokeCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字符串解析DrawingAttributes
|
||||
/// </summary>
|
||||
private DrawingAttributes ParseDrawingAttributes(string attributesStr)
|
||||
{
|
||||
var da = new DrawingAttributes();
|
||||
var parts = attributesStr.Split(';');
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var kv = part.Split('=');
|
||||
if (kv.Length == 2)
|
||||
{
|
||||
var key = kv[0].Trim();
|
||||
var value = kv[1].Trim();
|
||||
switch (key)
|
||||
{
|
||||
case "Color":
|
||||
da.Color = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(value);
|
||||
break;
|
||||
case "Width":
|
||||
da.Width = double.Parse(value);
|
||||
break;
|
||||
case "Height":
|
||||
da.Height = double.Parse(value);
|
||||
break;
|
||||
case "FitToCurve":
|
||||
da.FitToCurve = bool.Parse(value);
|
||||
break;
|
||||
case "IsHighlighter":
|
||||
da.IsHighlighter = bool.Parse(value);
|
||||
break;
|
||||
case "IgnorePressure":
|
||||
da.IgnorePressure = bool.Parse(value);
|
||||
break;
|
||||
case "StylusTip":
|
||||
da.StylusTip = Enum.TryParse<StylusTip>(value, out var tip) ? tip : StylusTip.Ellipse;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return da;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 打开单个墨迹文件
|
||||
/// </summary>
|
||||
@@ -748,3 +1209,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -354,7 +354,6 @@ namespace Ink_Canvas
|
||||
|
||||
private void BtnSelect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
forceEraser = true;
|
||||
drawingShapeMode = 0;
|
||||
inkCanvas.IsManipulationEnabled = false;
|
||||
@@ -628,36 +627,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(null);
|
||||
centerPoint = touchPoint.Position;
|
||||
lastTouchPointOnGridInkCanvasCover = e.GetTouchPoint(inkCanvas).Position;
|
||||
|
||||
// 检查是否有选中的墨迹
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
// 获取触摸点位置
|
||||
var touchPosition = e.GetTouchPoint(inkCanvas).Position;
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
|
||||
// 检查触摸位置是否在选择框边界内
|
||||
if (touchPosition.X >= selectionBounds.Left &&
|
||||
touchPosition.X <= selectionBounds.Right &&
|
||||
touchPosition.Y >= selectionBounds.Top &&
|
||||
touchPosition.Y <= selectionBounds.Bottom)
|
||||
{
|
||||
// 只有在选择框边界内才允许拖动
|
||||
// 触摸拖动状态已通过TouchMove事件处理
|
||||
}
|
||||
else
|
||||
{
|
||||
// 触摸在选择框外,取消选择
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
lastTouchPointOnGridInkCanvasCover = touchPoint.Position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,33 +635,25 @@ namespace Ink_Canvas
|
||||
{
|
||||
dec.Remove(e.TouchDevice.Id);
|
||||
if (dec.Count >= 1) return;
|
||||
|
||||
// 重置触摸状态
|
||||
lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
|
||||
isProgramChangeStrokeSelection = false;
|
||||
|
||||
// 检查是否有点击(没有移动)
|
||||
var currentTouchPoint = e.GetTouchPoint(null).Position;
|
||||
if (Math.Abs(currentTouchPoint.X - centerPoint.X) < 5 && Math.Abs(currentTouchPoint.Y - centerPoint.Y) < 5)
|
||||
|
||||
var touchUpPoint = e.GetTouchPoint(null).Position;
|
||||
if (lastTouchPointOnGridInkCanvasCover == touchUpPoint)
|
||||
{
|
||||
// 点击在选择框内,保持选择状态
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
var touchPointInCanvas = e.GetTouchPoint(inkCanvas).Position;
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
|
||||
if (!(touchPointInCanvas.X < selectionBounds.Left) &&
|
||||
!(touchPointInCanvas.Y < selectionBounds.Top) &&
|
||||
!(touchPointInCanvas.X > selectionBounds.Right) &&
|
||||
!(touchPointInCanvas.Y > selectionBounds.Bottom))
|
||||
{
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
if (currentTouchPoint.X >= selectionBounds.Left &&
|
||||
currentTouchPoint.X <= selectionBounds.Right &&
|
||||
currentTouchPoint.Y >= selectionBounds.Top &&
|
||||
currentTouchPoint.Y <= selectionBounds.Bottom)
|
||||
{
|
||||
// 点击在选择框内,保持选择
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
StrokesSelectionClone = new StrokeCollection();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 点击在选择框外,取消选择
|
||||
isProgramChangeStrokeSelection = true;
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
isProgramChangeStrokeSelection = false;
|
||||
StrokesSelectionClone = new StrokeCollection();
|
||||
}
|
||||
else if (inkCanvas.GetSelectedStrokes().Count == 0)
|
||||
@@ -704,12 +666,10 @@ namespace Ink_Canvas
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
StrokesSelectionClone = new StrokeCollection();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void LassoSelect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
forceEraser = false;
|
||||
forcePointEraser = false;
|
||||
drawingShapeMode = 0;
|
||||
@@ -720,7 +680,6 @@ namespace Ink_Canvas
|
||||
|
||||
private void BtnLassoSelect_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
forceEraser = false;
|
||||
forcePointEraser = false;
|
||||
drawingShapeMode = 0;
|
||||
|
||||
@@ -5,6 +5,7 @@ using OSVersionExtension;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -297,26 +298,107 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void ComboBoxChickenSoupSource_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
private static readonly Lazy<object> HitokotoHttpClient = new Lazy<object>(CreateHitokotoClient, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
private static object CreateHitokotoClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(5)
|
||||
};
|
||||
try
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("InkCanvas-Hitokoto/1.0");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return client;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"无法创建 HttpClient (System.Net.Http 可能缺失): {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateChickenSoupTextAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Settings.Appearance.EnableChickenSoupInWhiteboardMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.Appearance.ChickenSoupSource == 0)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.OSUPlayerYuLu.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.OSUPlayerYuLu[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 1)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.MingYanJingJu.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.MingYanJingJu[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 2)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.GaoKaoPhrases.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.GaoKaoPhrases[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 3)
|
||||
{
|
||||
BlackBoardWaterMark.Text = "正在获取一言...";
|
||||
|
||||
try
|
||||
{
|
||||
var clientObj = HitokotoHttpClient.Value;
|
||||
if (clientObj == null || !(clientObj is HttpClient client))
|
||||
{
|
||||
BlackBoardWaterMark.Text = "一言功能不可用(缺少 System.Net.Http)";
|
||||
return;
|
||||
}
|
||||
|
||||
var response = await client.GetAsync("https://v1.hitokoto.cn/?encode=text");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var text = await response.Content.ReadAsStringAsync();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
BlackBoardWaterMark.Text = text.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
BlackBoardWaterMark.Text = "一言暂时没有返回内容";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Hitokoto API 请求失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
BlackBoardWaterMark.Text = "一言获取失败,请稍后重试";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新白板名言时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ComboBoxChickenSoupSource_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Appearance.ChickenSoupSource = ComboBoxChickenSoupSource.SelectedIndex;
|
||||
SaveSettingsToFile();
|
||||
if (Settings.Appearance.ChickenSoupSource == 0)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.OSUPlayerYuLu.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.OSUPlayerYuLu[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 1)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.MingYanJingJu.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.MingYanJingJu[randChickenSoupIndex];
|
||||
}
|
||||
else if (Settings.Appearance.ChickenSoupSource == 2)
|
||||
{
|
||||
int randChickenSoupIndex = new Random().Next(ChickenSoup.GaoKaoPhrases.Length);
|
||||
BlackBoardWaterMark.Text = ChickenSoup.GaoKaoPhrases[randChickenSoupIndex];
|
||||
}
|
||||
await UpdateChickenSoupTextAsync();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableViewboxBlackBoardScaleTransform_Toggled(object sender, RoutedEventArgs e)
|
||||
@@ -578,6 +660,305 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchSkipAnimationsWhenGoNext_OnToggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.PowerPointSettings.SkipAnimationsWhenGoNext = ToggleSwitchSkipAnimationsWhenGoNext.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void PPTLSButtonOpacityValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
double roundedValue = Math.Round(PPTLSButtonOpacityValueSlider.Value, 1);
|
||||
PPTLSButtonOpacityValueSlider.Value = roundedValue;
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = roundedValue;
|
||||
SaveSettingsToFile();
|
||||
// 更新PPT UI管理器设置
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLSButtonOpacity = roundedValue;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRSButtonOpacityValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
double roundedValue = Math.Round(PPTRSButtonOpacityValueSlider.Value, 1);
|
||||
PPTRSButtonOpacityValueSlider.Value = roundedValue;
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = roundedValue;
|
||||
SaveSettingsToFile();
|
||||
// 更新PPT UI管理器设置
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRSButtonOpacity = roundedValue;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLBButtonOpacityValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
double roundedValue = Math.Round(PPTLBButtonOpacityValueSlider.Value, 1);
|
||||
PPTLBButtonOpacityValueSlider.Value = roundedValue;
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = roundedValue;
|
||||
SaveSettingsToFile();
|
||||
// 更新PPT UI管理器设置
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLBButtonOpacity = roundedValue;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRBButtonOpacityValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
double roundedValue = Math.Round(PPTRBButtonOpacityValueSlider.Value, 1);
|
||||
PPTRBButtonOpacityValueSlider.Value = roundedValue;
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = roundedValue;
|
||||
SaveSettingsToFile();
|
||||
// 更新PPT UI管理器设置
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRBButtonOpacity = roundedValue;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
// 左侧透明度按钮
|
||||
private void PPTLSOpacityPlusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLSButtonOpacityValueSlider.Value = Math.Min(1.0, PPTLSButtonOpacityValueSlider.Value + 0.1);
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = PPTLSButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLSButtonOpacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLSOpacityMinusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLSButtonOpacityValueSlider.Value = Math.Max(0.1, PPTLSButtonOpacityValueSlider.Value - 0.1);
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = PPTLSButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLSButtonOpacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLSOpacitySyncBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRSButtonOpacityValueSlider.Value = PPTLSButtonOpacityValueSlider.Value;
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = PPTLSButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRSButtonOpacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
// 右侧透明度按钮
|
||||
private void PPTRSOpacityPlusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRSButtonOpacityValueSlider.Value = Math.Min(1.0, PPTRSButtonOpacityValueSlider.Value + 0.1);
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = PPTRSButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRSButtonOpacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRSOpacityMinusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRSButtonOpacityValueSlider.Value = Math.Max(0.1, PPTRSButtonOpacityValueSlider.Value - 0.1);
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = PPTRSButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRSButtonOpacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRSOpacitySyncBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLSButtonOpacityValueSlider.Value = PPTRSButtonOpacityValueSlider.Value;
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = PPTRSButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLSButtonOpacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
// 左下透明度按钮
|
||||
private void PPTLBOpacityPlusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLBButtonOpacityValueSlider.Value = Math.Min(1.0, PPTLBButtonOpacityValueSlider.Value + 0.1);
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = PPTLBButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLBButtonOpacity = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLBOpacityMinusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLBButtonOpacityValueSlider.Value = Math.Max(0.1, PPTLBButtonOpacityValueSlider.Value - 0.1);
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = PPTLBButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLBButtonOpacity = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLBOpacitySyncBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRBButtonOpacityValueSlider.Value = PPTLBButtonOpacityValueSlider.Value;
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = PPTLBButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRBButtonOpacity = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
// 右下透明度按钮
|
||||
private void PPTRBOpacityPlusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRBButtonOpacityValueSlider.Value = Math.Min(1.0, PPTRBButtonOpacityValueSlider.Value + 0.1);
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = PPTRBButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRBButtonOpacity = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRBOpacityMinusBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRBButtonOpacityValueSlider.Value = Math.Max(0.1, PPTRBButtonOpacityValueSlider.Value - 0.1);
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = PPTRBButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRBButtonOpacity = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRBOpacitySyncBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLBButtonOpacityValueSlider.Value = PPTRBButtonOpacityValueSlider.Value;
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = PPTRBButtonOpacityValueSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLBButtonOpacity = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLSOpacityResetBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLSButtonOpacityValueSlider.Value = 0.5;
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = 0.5;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLSButtonOpacity = 0.5;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRSOpacityResetBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRSButtonOpacityValueSlider.Value = 0.5;
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = 0.5;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRSButtonOpacity = 0.5;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTLBOpacityResetBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTLBButtonOpacityValueSlider.Value = 0.5;
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = 0.5;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTLBButtonOpacity = 0.5;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void PPTRBOpacityResetBtn_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
PPTRBButtonOpacityValueSlider.Value = 0.5;
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = 0.5;
|
||||
SaveSettingsToFile();
|
||||
if (_pptUIManager != null)
|
||||
{
|
||||
_pptUIManager.PPTRBButtonOpacity = 0.5;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
}
|
||||
|
||||
private void CheckboxEnableLBPPTButton_IsCheckChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -668,13 +1049,37 @@ namespace Ink_Canvas
|
||||
if (!isLoaded) return;
|
||||
var str = Settings.PowerPointSettings.PPTSButtonsOption.ToString();
|
||||
char[] c = str.ToCharArray();
|
||||
c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
|
||||
bool isHalfOpacity = (bool)((CheckBox)sender).IsChecked;
|
||||
c[1] = isHalfOpacity ? '2' : '1';
|
||||
Settings.PowerPointSettings.PPTSButtonsOption = int.Parse(new string(c));
|
||||
|
||||
// 如果开启半透明选项,设置默认透明度为0.5;否则为1.0
|
||||
if (isHalfOpacity)
|
||||
{
|
||||
if (Settings.PowerPointSettings.PPTLSButtonOpacity == 1.0)
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = 0.5;
|
||||
if (Settings.PowerPointSettings.PPTRSButtonOpacity == 1.0)
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = 0.5;
|
||||
PPTLSButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
PPTRSButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.PowerPointSettings.PPTLSButtonOpacity == 0.5)
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = 1.0;
|
||||
if (Settings.PowerPointSettings.PPTRSButtonOpacity == 0.5)
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = 1.0;
|
||||
PPTLSButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
PPTRSButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
}
|
||||
|
||||
SaveSettingsToFile();
|
||||
// 更新PPT UI管理器设置
|
||||
if (_pptUIManager != null && BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
_pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption;
|
||||
_pptUIManager.PPTLSButtonOpacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
_pptUIManager.PPTRSButtonOpacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
UpdatePPTBtnPreview();
|
||||
@@ -719,8 +1124,30 @@ namespace Ink_Canvas
|
||||
if (!isLoaded) return;
|
||||
var str = Settings.PowerPointSettings.PPTBButtonsOption.ToString();
|
||||
char[] c = str.ToCharArray();
|
||||
c[1] = (bool)((CheckBox)sender).IsChecked ? '2' : '1';
|
||||
bool isHalfOpacity = (bool)((CheckBox)sender).IsChecked;
|
||||
c[1] = isHalfOpacity ? '2' : '1';
|
||||
Settings.PowerPointSettings.PPTBButtonsOption = int.Parse(new string(c));
|
||||
|
||||
// 如果开启半透明选项,设置默认透明度为0.5;否则为1.0
|
||||
if (isHalfOpacity)
|
||||
{
|
||||
if (Settings.PowerPointSettings.PPTLBButtonOpacity == 1.0)
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = 0.5;
|
||||
if (Settings.PowerPointSettings.PPTRBButtonOpacity == 1.0)
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = 0.5;
|
||||
PPTLBButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
PPTRBButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.PowerPointSettings.PPTLBButtonOpacity == 0.5)
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = 1.0;
|
||||
if (Settings.PowerPointSettings.PPTRBButtonOpacity == 0.5)
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = 1.0;
|
||||
PPTLBButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
PPTRBButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
}
|
||||
|
||||
SaveSettingsToFile();
|
||||
UpdatePPTUIManagerSettings();
|
||||
UpdatePPTBtnPreview();
|
||||
@@ -952,6 +1379,10 @@ namespace Ink_Canvas
|
||||
_pptUIManager.PPTRBButtonPosition = Settings.PowerPointSettings.PPTRBButtonPosition;
|
||||
_pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable;
|
||||
_pptUIManager.EnablePPTButtonLongPressPageTurn = Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn;
|
||||
_pptUIManager.PPTLSButtonOpacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
_pptUIManager.PPTRSButtonOpacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
_pptUIManager.PPTLBButtonOpacity = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
_pptUIManager.PPTRBButtonOpacity = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
_pptUIManager.UpdateNavigationPanelsVisibility();
|
||||
_pptUIManager.UpdateNavigationButtonStyles();
|
||||
}
|
||||
@@ -962,16 +1393,9 @@ namespace Ink_Canvas
|
||||
//new BitmapImage(new Uri("pack://application:,,,/Resources/new-icons/unfold-chevron.png"));
|
||||
var bopt = Settings.PowerPointSettings.PPTBButtonsOption.ToString();
|
||||
char[] boptc = bopt.ToCharArray();
|
||||
if (boptc[1] == '2')
|
||||
{
|
||||
PPTBtnPreviewLB.Opacity = 0.5;
|
||||
PPTBtnPreviewRB.Opacity = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
PPTBtnPreviewLB.Opacity = 1;
|
||||
PPTBtnPreviewRB.Opacity = 1;
|
||||
}
|
||||
// 使用实际的透明度设置值
|
||||
PPTBtnPreviewLB.Opacity = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
PPTBtnPreviewRB.Opacity = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
|
||||
if (boptc[2] == '2')
|
||||
{
|
||||
@@ -992,16 +1416,8 @@ namespace Ink_Canvas
|
||||
|
||||
var sopt = Settings.PowerPointSettings.PPTSButtonsOption.ToString();
|
||||
char[] soptc = sopt.ToCharArray();
|
||||
if (soptc[1] == '2')
|
||||
{
|
||||
PPTBtnPreviewLS.Opacity = 0.5;
|
||||
PPTBtnPreviewRS.Opacity = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
PPTBtnPreviewLS.Opacity = 1;
|
||||
PPTBtnPreviewRS.Opacity = 1;
|
||||
}
|
||||
PPTBtnPreviewLS.Opacity = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
PPTBtnPreviewRS.Opacity = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
|
||||
if (soptc[2] == '2')
|
||||
{
|
||||
@@ -1855,7 +2271,7 @@ namespace Ink_Canvas
|
||||
// 检查是否是第一次打开(检查用户是否已设置Token)
|
||||
bool hasToken = !string.IsNullOrEmpty(Settings?.Dlass?.UserToken?.Trim());
|
||||
bool isFirstTime = !hasToken;
|
||||
|
||||
|
||||
if (isFirstTime)
|
||||
{
|
||||
// 第一次打开,询问用户是否已注册
|
||||
@@ -1866,7 +2282,7 @@ namespace Ink_Canvas
|
||||
"Dlass账号注册",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
|
||||
if (result == MessageBoxResult.No)
|
||||
{
|
||||
// 用户未注册,打开浏览器
|
||||
@@ -1882,14 +2298,14 @@ namespace Ink_Canvas
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开浏览器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"无法打开浏览器。请手动访问: https://dlass.tech/dashboard",
|
||||
MessageBox.Show($"无法打开浏览器。请手动访问: https://dlass.tech/dashboard",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
return; // 不打开设置窗口
|
||||
}
|
||||
// 如果用户选择"是",继续打开设置窗口
|
||||
}
|
||||
|
||||
|
||||
// 打开设置管理窗口
|
||||
var dlassSettingsWindow = new Windows.DlassSettingsWindow();
|
||||
dlassSettingsWindow.Owner = this;
|
||||
@@ -1933,6 +2349,13 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchSaveStrokesAsXML_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Automation.IsSaveStrokesAsXML = ToggleSwitchSaveStrokesAsXML.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableAutoSaveStrokes_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1945,7 +2368,7 @@ namespace Ink_Canvas
|
||||
private void ComboBoxAutoSaveStrokesInterval_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded || ComboBoxAutoSaveStrokesInterval.SelectedItem == null) return;
|
||||
|
||||
|
||||
var selectedItem = ComboBoxAutoSaveStrokesInterval.SelectedItem as System.Windows.Controls.ComboBoxItem;
|
||||
if (selectedItem?.Tag != null && int.TryParse(selectedItem.Tag.ToString(), out int intervalMinutes))
|
||||
{
|
||||
@@ -2532,6 +2955,14 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchWindowMode_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Advanced.WindowMode = ToggleSwitchWindowMode.IsOn;
|
||||
SaveSettingsToFile();
|
||||
SetWindowMode();
|
||||
}
|
||||
|
||||
private void ToggleSwitchIsAutoBackupBeforeUpdate_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2712,13 +3143,13 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.EnableOvertimeCountUp = ToggleSwitchEnableOvertimeCountUp.IsOn;
|
||||
|
||||
|
||||
if (!ToggleSwitchEnableOvertimeCountUp.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = false;
|
||||
Settings.RandSettings.EnableOvertimeRedText = false;
|
||||
}
|
||||
|
||||
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
@@ -2731,7 +3162,7 @@ namespace Ink_Canvas
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = true;
|
||||
Settings.RandSettings.EnableOvertimeCountUp = true;
|
||||
}
|
||||
|
||||
|
||||
Settings.RandSettings.EnableOvertimeRedText = ToggleSwitchEnableOvertimeRedText.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
@@ -2857,7 +3288,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
|
||||
|
||||
// 根据设置状态显示或隐藏快抽悬浮按钮
|
||||
ShowQuickDrawFloatingButton();
|
||||
}
|
||||
@@ -2886,23 +3317,67 @@ namespace Ink_Canvas
|
||||
|
||||
public void UpdateFloatingBarIcons()
|
||||
{
|
||||
string currentMode = GetCurrentSelectedMode();
|
||||
|
||||
bool isCursorSolid = currentMode == "cursor";
|
||||
bool isPenSolid = currentMode == "pen" || currentMode == "color";
|
||||
bool isCircleEraserSolid = currentMode == "eraser";
|
||||
bool isStrokeEraserSolid = currentMode == "eraserByStrokes";
|
||||
bool isLassoSolid = currentMode == "select";
|
||||
|
||||
if (Settings.Appearance.UseLegacyFloatingBarUI)
|
||||
{
|
||||
// 使用老版图标
|
||||
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedCursorIcon);
|
||||
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedPenIcon);
|
||||
StrokeEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon);
|
||||
CircleEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon);
|
||||
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon);
|
||||
CursorIconGeometry.Geometry = Geometry.Parse(
|
||||
isCursorSolid
|
||||
? XamlGraphicsIconGeometries.LegacySolidCursorIcon
|
||||
: XamlGraphicsIconGeometries.LegacyLinedCursorIcon);
|
||||
|
||||
PenIconGeometry.Geometry = Geometry.Parse(
|
||||
isPenSolid
|
||||
? XamlGraphicsIconGeometries.LegacySolidPenIcon
|
||||
: XamlGraphicsIconGeometries.LegacyLinedPenIcon);
|
||||
|
||||
StrokeEraserIconGeometry.Geometry = Geometry.Parse(
|
||||
isStrokeEraserSolid
|
||||
? XamlGraphicsIconGeometries.LegacySolidEraserStrokeIcon
|
||||
: XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon);
|
||||
|
||||
CircleEraserIconGeometry.Geometry = Geometry.Parse(
|
||||
isCircleEraserSolid
|
||||
? XamlGraphicsIconGeometries.LegacySolidEraserCircleIcon
|
||||
: XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon);
|
||||
|
||||
LassoSelectIconGeometry.Geometry = Geometry.Parse(
|
||||
isLassoSolid
|
||||
? XamlGraphicsIconGeometries.LegacySolidLassoSelectIcon
|
||||
: XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用新版图标
|
||||
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedCursorIcon);
|
||||
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedPenIcon);
|
||||
StrokeEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserStrokeIcon);
|
||||
CircleEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserCircleIcon);
|
||||
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedLassoSelectIcon);
|
||||
CursorIconGeometry.Geometry = Geometry.Parse(
|
||||
isCursorSolid
|
||||
? XamlGraphicsIconGeometries.SolidCursorIcon
|
||||
: XamlGraphicsIconGeometries.LinedCursorIcon);
|
||||
|
||||
PenIconGeometry.Geometry = Geometry.Parse(
|
||||
isPenSolid
|
||||
? XamlGraphicsIconGeometries.SolidPenIcon
|
||||
: XamlGraphicsIconGeometries.LinedPenIcon);
|
||||
|
||||
StrokeEraserIconGeometry.Geometry = Geometry.Parse(
|
||||
isStrokeEraserSolid
|
||||
? XamlGraphicsIconGeometries.SolidEraserStrokeIcon
|
||||
: XamlGraphicsIconGeometries.LinedEraserStrokeIcon);
|
||||
|
||||
CircleEraserIconGeometry.Geometry = Geometry.Parse(
|
||||
isCircleEraserSolid
|
||||
? XamlGraphicsIconGeometries.SolidEraserCircleIcon
|
||||
: XamlGraphicsIconGeometries.LinedEraserCircleIcon);
|
||||
|
||||
LassoSelectIconGeometry.Geometry = Geometry.Parse(
|
||||
isLassoSolid
|
||||
? XamlGraphicsIconGeometries.SolidLassoSelectIcon
|
||||
: XamlGraphicsIconGeometries.LinedLassoSelectIcon);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OSVersionExtension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -31,6 +33,11 @@ namespace Ink_Canvas
|
||||
string text = File.ReadAllText(App.RootPath + settingsFileName);
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
|
||||
if (Settings != null)
|
||||
{
|
||||
CleanupObsoleteSettings(text);
|
||||
}
|
||||
|
||||
// 验证设置是否成功加载
|
||||
if (Settings == null)
|
||||
{
|
||||
@@ -42,6 +49,8 @@ namespace Ink_Canvas
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
if (Settings != null)
|
||||
{
|
||||
// 清理过期配置项
|
||||
CleanupObsoleteSettings(text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +76,8 @@ namespace Ink_Canvas
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
if (Settings != null)
|
||||
{
|
||||
// 清理过期配置项
|
||||
CleanupObsoleteSettings(text);
|
||||
}
|
||||
}
|
||||
catch (Exception restoreEx)
|
||||
@@ -95,6 +106,8 @@ namespace Ink_Canvas
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
if (Settings != null)
|
||||
{
|
||||
// 清理过期配置项
|
||||
CleanupObsoleteSettings(text);
|
||||
}
|
||||
}
|
||||
catch (Exception restoreEx)
|
||||
@@ -195,7 +208,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleSwitchIsAutoUpdateWithSilence.Visibility = Settings.Startup.IsAutoUpdate ? Visibility.Visible : Visibility.Collapsed;
|
||||
ToggleSwitchIsAutoUpdateWithSilence.Visibility = Settings.Startup.IsAutoUpdate ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (Settings.Startup.IsAutoUpdateWithSilence)
|
||||
{
|
||||
ToggleSwitchIsAutoUpdateWithSilence.IsOn = true;
|
||||
@@ -467,6 +480,21 @@ namespace Ink_Canvas
|
||||
|
||||
ToggleSwitchNotifyPreviousPage.IsOn = Settings.PowerPointSettings.IsNotifyPreviousPage;
|
||||
|
||||
// PPT时间显示胶囊设置
|
||||
if (ToggleSwitchEnablePPTTimeCapsule != null)
|
||||
{
|
||||
ToggleSwitchEnablePPTTimeCapsule.IsOn = Settings.PowerPointSettings.EnablePPTTimeCapsule;
|
||||
}
|
||||
if (ComboBoxPPTTimeCapsulePosition != null)
|
||||
{
|
||||
int position = Settings.PowerPointSettings.PPTTimeCapsulePosition;
|
||||
if (position < 0 || position > 2)
|
||||
{
|
||||
position = 1; // 默认右上角
|
||||
}
|
||||
ComboBoxPPTTimeCapsulePosition.SelectedIndex = position;
|
||||
}
|
||||
|
||||
// -- new --
|
||||
ToggleSwitchShowPPTButton.IsOn = Settings.PowerPointSettings.ShowPPTButton;
|
||||
|
||||
@@ -540,6 +568,38 @@ namespace Ink_Canvas
|
||||
|
||||
PPTButtonRBPositionValueSlider.Value = Settings.PowerPointSettings.PPTRBButtonPosition;
|
||||
|
||||
// 初始化PPT翻页按钮透明度滑块值,根据半透明选项设置默认值
|
||||
// 重用之前定义的sopsc和bopsc变量
|
||||
bool isSideHalfOpacity = sopsc.Length >= 2 && sopsc[1] == '2';
|
||||
// 如果透明度为0或未设置,根据半透明选项设置默认值
|
||||
if (Settings.PowerPointSettings.PPTLSButtonOpacity == 0.0 ||
|
||||
(Settings.PowerPointSettings.PPTLSButtonOpacity == 1.0 && isSideHalfOpacity))
|
||||
{
|
||||
Settings.PowerPointSettings.PPTLSButtonOpacity = isSideHalfOpacity ? 0.5 : 1.0;
|
||||
}
|
||||
if (Settings.PowerPointSettings.PPTRSButtonOpacity == 0.0 ||
|
||||
(Settings.PowerPointSettings.PPTRSButtonOpacity == 1.0 && isSideHalfOpacity))
|
||||
{
|
||||
Settings.PowerPointSettings.PPTRSButtonOpacity = isSideHalfOpacity ? 0.5 : 1.0;
|
||||
}
|
||||
PPTLSButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTLSButtonOpacity;
|
||||
PPTRSButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTRSButtonOpacity;
|
||||
|
||||
bool isBottomHalfOpacity = bopsc.Length >= 2 && bopsc[1] == '2';
|
||||
// 如果透明度为0或未设置,根据半透明选项设置默认值
|
||||
if (Settings.PowerPointSettings.PPTLBButtonOpacity == 0.0 ||
|
||||
(Settings.PowerPointSettings.PPTLBButtonOpacity == 1.0 && isBottomHalfOpacity))
|
||||
{
|
||||
Settings.PowerPointSettings.PPTLBButtonOpacity = isBottomHalfOpacity ? 0.5 : 1.0;
|
||||
}
|
||||
if (Settings.PowerPointSettings.PPTRBButtonOpacity == 0.0 ||
|
||||
(Settings.PowerPointSettings.PPTRBButtonOpacity == 1.0 && isBottomHalfOpacity))
|
||||
{
|
||||
Settings.PowerPointSettings.PPTRBButtonOpacity = isBottomHalfOpacity ? 0.5 : 1.0;
|
||||
}
|
||||
PPTLBButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTLBButtonOpacity;
|
||||
PPTRBButtonOpacityValueSlider.Value = Settings.PowerPointSettings.PPTRBButtonOpacity;
|
||||
|
||||
UpdatePPTBtnSlidersStatus();
|
||||
|
||||
UpdatePPTBtnPreview();
|
||||
@@ -795,6 +855,7 @@ namespace Ink_Canvas
|
||||
ToggleSwitchIsLogEnabled.IsOn = Settings.Advanced.IsLogEnabled;
|
||||
ToggleSwitchIsSaveLogByDate.IsOn = Settings.Advanced.IsSaveLogByDate;
|
||||
ToggleSwitchIsSecondConfimeWhenShutdownApp.IsOn = Settings.Advanced.IsSecondConfirmWhenShutdownApp;
|
||||
ToggleSwitchWindowMode.IsOn = Settings.Advanced.WindowMode;
|
||||
ToggleSwitchIsSpecialScreen.IsOn = Settings.Advanced.IsSpecialScreen;
|
||||
ToggleSwitchIsQuadIR.IsOn = Settings.Advanced.IsQuadIR;
|
||||
ToggleSwitchEraserBindTouchMultiplier.IsOn = Settings.Advanced.EraserBindTouchMultiplier;
|
||||
@@ -886,20 +947,20 @@ namespace Ink_Canvas
|
||||
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
|
||||
ToggleSwitchUseNewStyleUI.IsOn = Settings.RandSettings.UseNewStyleUI;
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = Settings.RandSettings.EnableOvertimeCountUp;
|
||||
|
||||
|
||||
// 新点名UI设置
|
||||
ToggleSwitchUseNewRollCallUI.IsOn = Settings.RandSettings.UseNewRollCallUI;
|
||||
ToggleSwitchEnableMLAvoidance.IsOn = Settings.RandSettings.EnableMLAvoidance;
|
||||
MLAvoidanceHistorySlider.Value = Settings.RandSettings.MLAvoidanceHistoryCount;
|
||||
MLAvoidanceWeightSlider.Value = Settings.RandSettings.MLAvoidanceWeight;
|
||||
|
||||
|
||||
bool canEnableRedText = Settings.RandSettings.EnableOvertimeCountUp && Settings.RandSettings.EnableOvertimeRedText;
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = canEnableRedText;
|
||||
if (!canEnableRedText)
|
||||
{
|
||||
Settings.RandSettings.EnableOvertimeRedText = false;
|
||||
}
|
||||
|
||||
|
||||
TimerVolumeSlider.Value = Settings.RandSettings.TimerVolume;
|
||||
|
||||
// 渐进提醒设置
|
||||
@@ -928,16 +989,16 @@ namespace Ink_Canvas
|
||||
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
|
||||
ToggleSwitchUseNewStyleUI.IsOn = Settings.RandSettings.UseNewStyleUI;
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = Settings.RandSettings.EnableOvertimeCountUp;
|
||||
|
||||
|
||||
bool canEnableRedText = Settings.RandSettings.EnableOvertimeCountUp && Settings.RandSettings.EnableOvertimeRedText;
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = canEnableRedText;
|
||||
if (!canEnableRedText)
|
||||
{
|
||||
Settings.RandSettings.EnableOvertimeRedText = false;
|
||||
}
|
||||
|
||||
|
||||
TimerVolumeSlider.Value = Settings.RandSettings.TimerVolume;
|
||||
|
||||
|
||||
// 渐进提醒设置
|
||||
ToggleSwitchEnableProgressiveReminder.IsOn = Settings.RandSettings.EnableProgressiveReminder;
|
||||
ProgressiveReminderVolumeSlider.Value = Settings.RandSettings.ProgressiveReminderVolume;
|
||||
@@ -1053,6 +1114,8 @@ namespace Ink_Canvas
|
||||
|
||||
ToggleSwitchSaveFullPageStrokes.IsOn = Settings.Automation.IsSaveFullPageStrokes;
|
||||
|
||||
ToggleSwitchSaveStrokesAsXML.IsOn = Settings.Automation.IsSaveStrokesAsXML;
|
||||
|
||||
// 加载定时保存墨迹设置
|
||||
ToggleSwitchEnableAutoSaveStrokes.IsOn = Settings.Automation.IsEnableAutoSaveStrokes;
|
||||
// 初始化保存间隔下拉框
|
||||
@@ -1149,5 +1212,102 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"加载墨迹渐隐设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="userConfigJson">用户配置的JSON字符串</param>
|
||||
private void CleanupObsoleteSettings(string userConfigJson)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建默认配置对象
|
||||
Settings defaultSettings = new Settings();
|
||||
|
||||
// 将默认配置和用户配置都序列化为JObject
|
||||
JObject defaultConfigObj = JObject.FromObject(defaultSettings);
|
||||
JObject userConfigObj = JObject.Parse(userConfigJson);
|
||||
|
||||
// 记录是否有清理操作
|
||||
bool hasChanges = false;
|
||||
|
||||
// 递归比较并删除用户配置中多余的键
|
||||
RemoveObsoleteProperties(userConfigObj, defaultConfigObj, ref hasChanges);
|
||||
|
||||
// 如果有清理操作,重新反序列化并保存
|
||||
if (hasChanges)
|
||||
{
|
||||
string cleanedJson = userConfigObj.ToString(Formatting.Indented);
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(cleanedJson);
|
||||
SaveSettingsToFile();
|
||||
LogHelper.WriteLogToFile("已清理过期配置项", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理过期配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="userObj">用户配置的JObject</param>
|
||||
/// <param name="defaultObj">默认配置的JObject</param>
|
||||
/// <param name="hasChanges">是否有变更的引用标志</param>
|
||||
private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges)
|
||||
{
|
||||
if (userObj == null || defaultObj == null)
|
||||
return;
|
||||
|
||||
// 获取需要删除的键列表(避免在遍历时修改集合)
|
||||
List<string> keysToRemove = new List<string>();
|
||||
|
||||
foreach (var property in userObj.Properties())
|
||||
{
|
||||
string propertyName = property.Name;
|
||||
|
||||
// 如果默认配置中不存在该属性,标记为删除
|
||||
if (!defaultObj.ContainsKey(propertyName))
|
||||
{
|
||||
keysToRemove.Add(propertyName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果两个属性都是对象类型,递归比较
|
||||
JToken userValue = property.Value;
|
||||
JToken defaultValue = defaultObj[propertyName];
|
||||
|
||||
if (userValue != null && defaultValue != null)
|
||||
{
|
||||
if (userValue.Type == JTokenType.Object && defaultValue.Type == JTokenType.Object)
|
||||
{
|
||||
RemoveObsoleteProperties(userValue as JObject, defaultValue as JObject, ref hasChanges);
|
||||
}
|
||||
// 处理数组中的对象(如自定义图标列表等)
|
||||
else if (userValue.Type == JTokenType.Array && defaultValue.Type == JTokenType.Array)
|
||||
{
|
||||
JArray userArray = userValue as JArray;
|
||||
JArray defaultArray = defaultValue as JArray;
|
||||
|
||||
if (userArray != null && defaultArray != null && userArray.Count > 0 && defaultArray.Count > 0)
|
||||
{
|
||||
// 如果数组元素是对象,比较第一个元素的属性结构
|
||||
if (userArray[0].Type == JTokenType.Object && defaultArray[0].Type == JTokenType.Object)
|
||||
{
|
||||
for (int i = 0; i < userArray.Count; i++)
|
||||
{
|
||||
if (userArray[i] is JObject userItemObj && defaultArray[0] is JObject defaultItemObj)
|
||||
{
|
||||
RemoveObsoleteProperties(userItemObj, defaultItemObj, ref hasChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除标记的键
|
||||
foreach (string key in keysToRemove)
|
||||
{
|
||||
userObj.Remove(key);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,21 +18,19 @@ namespace Ink_Canvas
|
||||
{
|
||||
private StrokeCollection newStrokes = new StrokeCollection();
|
||||
private List<Circle> circles = new List<Circle>();
|
||||
private const double LINE_STRAIGHTEN_THRESHOLD = 0.20; // 默认灵敏度阈值,与UI默认值对应
|
||||
private const double LINE_STRAIGHTEN_THRESHOLD = 0.20;
|
||||
|
||||
// 矩形参考线系统
|
||||
private List<RectangleGuideLine> rectangleGuideLines = new List<RectangleGuideLine>();
|
||||
private const double RECTANGLE_ENDPOINT_THRESHOLD = 30.0; // 端点相交判断阈值
|
||||
private const double RECTANGLE_ANGLE_THRESHOLD = 15.0; // 角度判断阈值(度)
|
||||
private const double RECTANGLE_ENDPOINT_THRESHOLD = 30.0;
|
||||
private const double RECTANGLE_ANGLE_THRESHOLD = 15.0;
|
||||
|
||||
// 矩形参考线数据结构
|
||||
private class RectangleGuideLine
|
||||
{
|
||||
public Stroke OriginalStroke { get; set; }
|
||||
public Point StartPoint { get; set; }
|
||||
public Point EndPoint { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public double Angle { get; set; } // 直线角度(弧度)
|
||||
public double Angle { get; set; }
|
||||
public bool IsHorizontal { get; set; }
|
||||
public bool IsVertical { get; set; }
|
||||
|
||||
@@ -64,7 +62,6 @@ namespace Ink_Canvas
|
||||
var startPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[0].ToPoint() : new Point();
|
||||
var endPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint() : new Point();
|
||||
|
||||
// 确保InkCanvas保持Ink编辑模式,防止自动切换到鼠标模式
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
@@ -80,19 +77,15 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile("StrokeCollected: 墨迹渐隐管理器为空,无法添加墨迹", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 延迟移除墨迹,避免立即移除导致模式切换
|
||||
// 使用Dispatcher.BeginInvoke确保在UI线程上异步执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次确保InkCanvas保持Ink编辑模式
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
|
||||
if (inkCanvas.Strokes.Contains(e.Stroke))
|
||||
{
|
||||
inkCanvas.Strokes.Remove(e.Stroke);
|
||||
@@ -104,21 +97,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
}), DispatcherPriority.Background);
|
||||
|
||||
// 墨迹渐隐模式下不参与墨迹纠正和其他处理,直接返回
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记是否进行了直线拉直
|
||||
bool wasStraightened = false;
|
||||
|
||||
// 禁用原有的FitToCurve,使用新的高级贝塞尔曲线平滑
|
||||
if (Settings.Canvas.FitToCurve) drawingAttributes.FitToCurve = false;
|
||||
|
||||
try
|
||||
{
|
||||
inkCanvas.Opacity = 1;
|
||||
|
||||
// 应用屏蔽压感功能 - 如果启用,所有笔画都使用统一粗细
|
||||
if (Settings.Canvas.DisablePressure)
|
||||
{
|
||||
var uniformPoints = new StylusPointCollection();
|
||||
@@ -129,13 +119,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
e.Stroke.StylusPoints = uniformPoints;
|
||||
}
|
||||
// 应用压感触屏模式 - 如果启用并且检测到触屏输入
|
||||
else if (Settings.Canvas.EnablePressureTouchMode)
|
||||
{
|
||||
bool isTouchInput = true;
|
||||
foreach (StylusPoint point in e.Stroke.StylusPoints)
|
||||
{
|
||||
// 检测是否为压感笔输入(压感笔的PressureFactor不等于0.5或0)
|
||||
if ((point.PressureFactor > 0.501 || point.PressureFactor < 0.5) && point.PressureFactor != 0)
|
||||
{
|
||||
isTouchInput = false;
|
||||
@@ -143,7 +131,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是触屏输入,则应用模拟压感
|
||||
if (isTouchInput)
|
||||
{
|
||||
switch (Settings.Canvas.InkStyle)
|
||||
@@ -859,30 +846,61 @@ namespace Ink_Canvas
|
||||
// 快速检查:计算几个关键点与直线的距离
|
||||
if (stroke.StylusPoints.Count >= 10)
|
||||
{
|
||||
// 取中点和1/4、3/4位置的点,快速检查偏差
|
||||
int quarterIdx = stroke.StylusPoints.Count / 4;
|
||||
int midIdx = stroke.StylusPoints.Count / 2;
|
||||
int threeQuarterIdx = quarterIdx * 3;
|
||||
List<Point> checkPoints;
|
||||
|
||||
Point quarterPoint = stroke.StylusPoints[quarterIdx].ToPoint();
|
||||
Point midPoint = stroke.StylusPoints[midIdx].ToPoint();
|
||||
Point threeQuarterPoint = stroke.StylusPoints[threeQuarterIdx].ToPoint();
|
||||
|
||||
double quarterDeviation = DistanceFromLineToPoint(start, end, quarterPoint);
|
||||
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
|
||||
double threeQuarterDeviation = DistanceFromLineToPoint(start, end, threeQuarterPoint);
|
||||
|
||||
// 使用相对偏差:偏差与线长的比例,并使用灵敏度进行调整
|
||||
double quickRelativeThreshold = lineLength * quickThreshold;
|
||||
|
||||
// 记录检测到的偏差
|
||||
Debug.WriteLine($"Deviations: q={quarterDeviation}, m={midDeviation}, tq={threeQuarterDeviation}, threshold={quickRelativeThreshold}");
|
||||
|
||||
if (quarterDeviation > quickRelativeThreshold ||
|
||||
midDeviation > quickRelativeThreshold ||
|
||||
threeQuarterDeviation > quickRelativeThreshold)
|
||||
// 使用采样点进行更准确的判断
|
||||
if (Settings.Canvas.HighPrecisionLineStraighten)
|
||||
{
|
||||
return false;
|
||||
var allPoints = stroke.StylusPoints.Select(p => p.ToPoint()).ToList();
|
||||
checkPoints = SamplePointsByDistance(allPoints, 10.0);
|
||||
Debug.WriteLine($"高精度模式快速检查:原始点数={allPoints.Count}, 采样点数={checkPoints.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 取中点和1/4、3/4位置的点
|
||||
int quarterIdx = stroke.StylusPoints.Count / 4;
|
||||
int midIdx = stroke.StylusPoints.Count / 2;
|
||||
int threeQuarterIdx = quarterIdx * 3;
|
||||
|
||||
checkPoints = new List<Point>
|
||||
{
|
||||
stroke.StylusPoints[quarterIdx].ToPoint(),
|
||||
stroke.StylusPoints[midIdx].ToPoint(),
|
||||
stroke.StylusPoints[threeQuarterIdx].ToPoint()
|
||||
};
|
||||
}
|
||||
|
||||
// 计算所有检查点与直线的平均偏差
|
||||
double totalDeviation = 0;
|
||||
double maxDeviation = 0;
|
||||
int validPointCount = 0;
|
||||
|
||||
foreach (Point checkPoint in checkPoints)
|
||||
{
|
||||
double deviation = DistanceFromLineToPoint(start, end, checkPoint);
|
||||
totalDeviation += deviation;
|
||||
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||
validPointCount++;
|
||||
}
|
||||
|
||||
if (validPointCount > 0)
|
||||
{
|
||||
double avgDeviation = totalDeviation / validPointCount;
|
||||
// 使用相对偏差:偏差与线长的比例,并使用灵敏度进行调整
|
||||
double quickRelativeThreshold = lineLength * quickThreshold;
|
||||
|
||||
// 使用平均偏差和最大偏差的综合判断
|
||||
double deviationThreshold = Settings.Canvas.HighPrecisionLineStraighten
|
||||
? Math.Max(avgDeviation, maxDeviation * 0.7) // 高精度模式更严格
|
||||
: maxDeviation;
|
||||
|
||||
// 记录检测到的偏差
|
||||
Debug.WriteLine($"Deviations: avg={avgDeviation:F2}, max={maxDeviation:F2}, threshold={quickRelativeThreshold:F2}, highPrecision={Settings.Canvas.HighPrecisionLineStraighten}");
|
||||
|
||||
if (deviationThreshold > quickRelativeThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,13 +944,6 @@ namespace Ink_Canvas
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否有明显的回环或重叠
|
||||
if (HasSignificantLoops(stroke))
|
||||
{
|
||||
Debug.WriteLine("检测到复杂形状:存在明显回环");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1013,55 +1024,6 @@ namespace Ink_Canvas
|
||||
return changes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有明显的回环
|
||||
/// </summary>
|
||||
private bool HasSignificantLoops(Stroke stroke)
|
||||
{
|
||||
if (stroke.StylusPoints.Count < 20) return false;
|
||||
|
||||
// 检查起点和终点是否接近(可能是闭合图形)
|
||||
Point start = stroke.StylusPoints.First().ToPoint();
|
||||
Point end = stroke.StylusPoints.Last().ToPoint();
|
||||
double startEndDistance = GetDistance(start, end);
|
||||
|
||||
// 计算平均点间距
|
||||
double totalDistance = 0;
|
||||
for (int i = 1; i < stroke.StylusPoints.Count; i++)
|
||||
{
|
||||
Point p1 = stroke.StylusPoints[i - 1].ToPoint();
|
||||
Point p2 = stroke.StylusPoints[i].ToPoint();
|
||||
totalDistance += GetDistance(p1, p2);
|
||||
}
|
||||
double avgPointDistance = totalDistance / (stroke.StylusPoints.Count - 1);
|
||||
|
||||
// 如果起点和终点很接近,可能是闭合图形
|
||||
if (startEndDistance < avgPointDistance * 5)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否有点重复经过相似区域
|
||||
int overlapCount = 0;
|
||||
double overlapThreshold = avgPointDistance * 3;
|
||||
|
||||
for (int i = 0; i < stroke.StylusPoints.Count - 10; i += 5)
|
||||
{
|
||||
Point p1 = stroke.StylusPoints[i].ToPoint();
|
||||
for (int j = i + 10; j < stroke.StylusPoints.Count; j += 5)
|
||||
{
|
||||
Point p2 = stroke.StylusPoints[j].ToPoint();
|
||||
if (GetDistance(p1, p2) < overlapThreshold)
|
||||
{
|
||||
overlapCount++;
|
||||
if (overlapCount > 3) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查曲率是否一致(用于识别圆弧等规则曲线)
|
||||
/// </summary>
|
||||
@@ -1177,14 +1139,29 @@ namespace Ink_Canvas
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Point> workingPoints = points;
|
||||
if (Settings.Canvas.HighPrecisionLineStraighten)
|
||||
{
|
||||
workingPoints = SamplePointsByDistance(points, 10.0);
|
||||
Debug.WriteLine($"高精度模式:原始点数={points.Count}, 采样后点数={workingPoints.Count}");
|
||||
}
|
||||
|
||||
// 使用总最小二乘法(TLS/PCA)进行直线拟合
|
||||
int n = points.Count - 8;
|
||||
int n = workingPoints.Count - 8;
|
||||
if (n < 1)
|
||||
{
|
||||
// 如果采样后点数太少,回退到原始方法
|
||||
n = points.Count - 8;
|
||||
workingPoints = points;
|
||||
}
|
||||
|
||||
List<Point> filteredPoints = new List<Point>();
|
||||
|
||||
// 收集过滤后的点(跳过前 4 个和后 4 个点,用于计算直线方向)
|
||||
for (int i = 4; i < n + 4; i++)
|
||||
int skipCount = Math.Min(4, n / 2); // 确保跳过数量不超过一半
|
||||
for (int i = skipCount; i < n + skipCount && i < workingPoints.Count; i++)
|
||||
{
|
||||
filteredPoints.Add(points[i]);
|
||||
filteredPoints.Add(workingPoints[i]);
|
||||
}
|
||||
|
||||
// 计算中心点(使用过滤后的点)
|
||||
@@ -1244,7 +1221,8 @@ namespace Ink_Canvas
|
||||
double maxProjection = double.MinValue;
|
||||
|
||||
// 计算所有点在直线方向上的投影
|
||||
foreach (Point p in points)
|
||||
List<Point> pointsForProjection = Settings.Canvas.HighPrecisionLineStraighten ? workingPoints : points;
|
||||
foreach (Point p in pointsForProjection)
|
||||
{
|
||||
// 相对于过滤点中心的投影
|
||||
double projection = (p.X - centerX) * directionX + (p.Y - centerY) * directionY;
|
||||
@@ -1276,7 +1254,7 @@ namespace Ink_Canvas
|
||||
Point end = stroke.StylusPoints.Last().ToPoint();
|
||||
double lineLength = GetDistance(start, end);
|
||||
double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale();
|
||||
|
||||
|
||||
// 如果线条太短,不进行拉直处理
|
||||
if (lineLength < adaptiveThreshold)
|
||||
{
|
||||
@@ -1293,7 +1271,7 @@ namespace Ink_Canvas
|
||||
|
||||
Point endpoint1, endpoint2;
|
||||
bool shouldStraighten = TryGetStraightLineEndpoints(stroke, out endpoint1, out endpoint2);
|
||||
|
||||
|
||||
if (shouldStraighten)
|
||||
{
|
||||
Debug.WriteLine($"接受拉直:判断为直线,解释方差比例满足阈值");
|
||||
@@ -1418,41 +1396,69 @@ namespace Ink_Canvas
|
||||
if (!Settings.Canvas.EnablePressureTouchMode || Settings.Canvas.DisablePressure ||
|
||||
Settings.InkToShape.IsInkToShapeNoFakePressureRectangle || penType == 1)
|
||||
{
|
||||
// 使用均匀粗细(所有点压感值都是0.5f)
|
||||
points.Add(new StylusPoint(start.X, start.Y, 0.5f));
|
||||
|
||||
// 可以添加一些额外的中间点使线条更平滑(均匀粗细)
|
||||
double distance = GetDistance(start, end);
|
||||
if (distance > 100)
|
||||
var linePoints = GeneratePointsBetween(start, end, 0.5f, 0.5f, 8.0);
|
||||
foreach (var pt in linePoints)
|
||||
{
|
||||
// 对于较长的线条,添加几个中间点
|
||||
for (int i = 1; i < 3; i++)
|
||||
{
|
||||
double ratio = i / 3.0;
|
||||
Point midPoint = new Point(
|
||||
start.X + (end.X - start.X) * ratio,
|
||||
start.Y + (end.Y - start.Y) * ratio);
|
||||
points.Add(new StylusPoint(midPoint.X, midPoint.Y, 0.5f));
|
||||
}
|
||||
points.Add(pt);
|
||||
}
|
||||
|
||||
points.Add(new StylusPoint(end.X, end.Y, 0.5f));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 启用了压感触屏模式,使用变化的粗细(原有行为)
|
||||
points.Add(new StylusPoint(start.X, start.Y, 0.4f));
|
||||
|
||||
// 添加中点,压感值较高,使线条中间较粗
|
||||
Point midPoint = new Point((start.X + end.X) / 2, (start.Y + end.Y) / 2);
|
||||
points.Add(new StylusPoint(midPoint.X, midPoint.Y, 0.8f));
|
||||
|
||||
points.Add(new StylusPoint(end.X, end.Y, 0.4f));
|
||||
|
||||
var startToMid = GeneratePointsBetween(start, midPoint, 0.4f, 0.8f, 8.0);
|
||||
foreach (var pt in startToMid)
|
||||
{
|
||||
points.Add(pt);
|
||||
}
|
||||
|
||||
var midToEnd = GeneratePointsBetween(midPoint, end, 0.8f, 0.4f, 8.0);
|
||||
for (int i = 1; i < midToEnd.Count; i++)
|
||||
{
|
||||
points.Add(midToEnd[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 高精度模式
|
||||
/// </summary>
|
||||
private List<Point> SamplePointsByDistance(List<Point> points, double sampleInterval = 10.0)
|
||||
{
|
||||
if (points == null || points.Count < 2)
|
||||
return points;
|
||||
|
||||
List<Point> sampledPoints = new List<Point>();
|
||||
sampledPoints.Add(points[0]); // 总是包含起点
|
||||
|
||||
double accumulatedDistance = 0;
|
||||
Point lastSampledPoint = points[0];
|
||||
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
double segmentDistance = GetDistance(lastSampledPoint, points[i]);
|
||||
accumulatedDistance += segmentDistance;
|
||||
|
||||
// 当累积距离达到采样间隔时,添加当前点
|
||||
if (accumulatedDistance >= sampleInterval)
|
||||
{
|
||||
sampledPoints.Add(points[i]);
|
||||
lastSampledPoint = points[i];
|
||||
accumulatedDistance = 0; // 重置累积距离
|
||||
}
|
||||
}
|
||||
|
||||
// 总是包含终点(如果还没有包含)
|
||||
if (sampledPoints.Count == 0 || GetDistance(sampledPoints.Last(), points.Last()) > 1.0)
|
||||
{
|
||||
sampledPoints.Add(points.Last());
|
||||
}
|
||||
|
||||
return sampledPoints;
|
||||
}
|
||||
|
||||
// New method: Gets distance from point to a line defined by two points
|
||||
private double DistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point)
|
||||
{
|
||||
@@ -1467,11 +1473,66 @@ namespace Ink_Canvas
|
||||
return distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断一个 stroke 是否是直线(排除虚线和点线)
|
||||
/// </summary>
|
||||
/// <param name="stroke">要检查的 stroke</param>
|
||||
/// <returns>如果是直线返回 true,否则返回 false</returns>
|
||||
private bool IsStraightLine(Stroke stroke)
|
||||
{
|
||||
if (stroke == null || stroke.StylusPoints.Count == 0)
|
||||
return false;
|
||||
|
||||
int pointCount = stroke.StylusPoints.Count;
|
||||
|
||||
if (pointCount == 1)
|
||||
return false;
|
||||
|
||||
// 最简单的直线:只有2个点
|
||||
if (pointCount == 2)
|
||||
{
|
||||
Point p1 = stroke.StylusPoints[0].ToPoint();
|
||||
Point p2 = stroke.StylusPoints[1].ToPoint();
|
||||
double lineLength = GetDistance(p1, p2);
|
||||
|
||||
if (lineLength < 10)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pointCount > 3)
|
||||
return false;
|
||||
|
||||
// 对于3个点的情况,检查它们是否基本在一条直线上
|
||||
if (pointCount == 3)
|
||||
{
|
||||
Point p1 = stroke.StylusPoints[0].ToPoint();
|
||||
Point p2 = stroke.StylusPoints[1].ToPoint();
|
||||
Point p3 = stroke.StylusPoints[2].ToPoint();
|
||||
|
||||
double totalLength = GetDistance(p1, p3);
|
||||
if (totalLength < 10)
|
||||
return false;
|
||||
|
||||
// 计算点到直线的距离
|
||||
// 使用 p1 和 p3 作为直线端点,检查 p2 是否在这条直线上
|
||||
double distance = DistanceFromLineToPoint(p1, p3, p2);
|
||||
|
||||
// 如果点到直线的距离相对于线段长度很小,认为是直线
|
||||
// 使用相对误差阈值(比如 1%)
|
||||
if (totalLength > 0 && distance / totalLength < 0.01)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// New method: Attempts to snap endpoints to existing stroke endpoints
|
||||
private Point[] GetSnappedEndpoints(Point start, Point end)
|
||||
{
|
||||
// 如果端点吸附功能关闭,直接返回null
|
||||
// 这里不再返回原始点,因为调用此方法的地方会判断返回值是否为null
|
||||
if (!Settings.Canvas.LineEndpointSnapping)
|
||||
return null;
|
||||
|
||||
@@ -1488,6 +1549,10 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (stroke.StylusPoints.Count == 0) continue;
|
||||
|
||||
// 只对直线进行端点吸附,跳过虚线和点线
|
||||
if (!IsStraightLine(stroke))
|
||||
continue;
|
||||
|
||||
// Get stroke endpoints
|
||||
Point strokeStart = stroke.StylusPoints.First().ToPoint();
|
||||
Point strokeEnd = stroke.StylusPoints.Last().ToPoint();
|
||||
@@ -1597,66 +1662,199 @@ namespace Ink_Canvas
|
||||
|
||||
public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points)
|
||||
{
|
||||
var newPoint = new StylusPointCollection();
|
||||
|
||||
if (Settings.InkToShape.IsInkToShapeNoFakePressureTriangle || penType == 1)
|
||||
{
|
||||
var newPoint = new StylusPointCollection();
|
||||
newPoint.Add(new StylusPoint(points[0].X, points[0].Y));
|
||||
var cPoint = GetCenterPoint(points[0], points[1]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y));
|
||||
newPoint.Add(new StylusPoint(points[1].X, points[1].Y));
|
||||
newPoint.Add(new StylusPoint(points[1].X, points[1].Y));
|
||||
cPoint = GetCenterPoint(points[1], points[2]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y));
|
||||
newPoint.Add(new StylusPoint(points[2].X, points[2].Y));
|
||||
newPoint.Add(new StylusPoint(points[2].X, points[2].Y));
|
||||
cPoint = GetCenterPoint(points[2], points[0]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y));
|
||||
newPoint.Add(new StylusPoint(points[0].X, points[0].Y));
|
||||
if (points.Count >= 3)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Point start = points[i].ToPoint();
|
||||
Point end = points[(i + 1) % 3].ToPoint();
|
||||
var edgePoints = GeneratePointsBetween(start, end, 0.5f, 0.5f, 8.0);
|
||||
if (i == 0)
|
||||
{
|
||||
foreach (var pt in edgePoints)
|
||||
{
|
||||
newPoint.Add(pt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 1; j < edgePoints.Count; j++)
|
||||
{
|
||||
newPoint.Add(edgePoints[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Point lastPoint = points[0].ToPoint();
|
||||
Point firstPoint = newPoint[0].ToPoint();
|
||||
if (GetDistance(lastPoint, firstPoint) > 1.0)
|
||||
{
|
||||
newPoint.Add(new StylusPoint(lastPoint.X, lastPoint.Y, 0.5f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return points;
|
||||
}
|
||||
return newPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newPoint = new StylusPointCollection();
|
||||
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
|
||||
var cPoint = GetCenterPoint(points[0], points[1]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
|
||||
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
|
||||
cPoint = GetCenterPoint(points[1], points[2]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
|
||||
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
|
||||
cPoint = GetCenterPoint(points[2], points[0]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
|
||||
if (points.Count >= 3)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Point start = points[i].ToPoint();
|
||||
Point end = points[(i + 1) % 3].ToPoint();
|
||||
|
||||
Point midPoint = GetCenterPoint(start, end);
|
||||
|
||||
var startToMid = GeneratePointsBetween(start, midPoint, 0.4f, 0.8f, 8.0);
|
||||
if (i == 0)
|
||||
{
|
||||
foreach (var pt in startToMid)
|
||||
{
|
||||
newPoint.Add(pt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 1; j < startToMid.Count; j++)
|
||||
{
|
||||
newPoint.Add(startToMid[j]);
|
||||
}
|
||||
}
|
||||
|
||||
var midToEnd = GeneratePointsBetween(midPoint, end, 0.8f, 0.4f, 8.0);
|
||||
for (int j = 1; j < midToEnd.Count; j++)
|
||||
{
|
||||
newPoint.Add(midToEnd[j]);
|
||||
}
|
||||
}
|
||||
Point lastPoint = points[0].ToPoint();
|
||||
Point firstPoint = newPoint[0].ToPoint();
|
||||
if (GetDistance(lastPoint, firstPoint) > 1.0)
|
||||
{
|
||||
newPoint.Add(new StylusPoint(lastPoint.X, lastPoint.Y, 0.4f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return points;
|
||||
}
|
||||
return newPoint;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在两点之间生成多个点,用于增加图形边缘的点密度
|
||||
/// </summary>
|
||||
private StylusPointCollection GeneratePointsBetween(Point start, Point end, float startPressure, float endPressure, double minPointInterval = 8.0)
|
||||
{
|
||||
var result = new StylusPointCollection();
|
||||
double distance = GetDistance(start, end);
|
||||
|
||||
if (distance < minPointInterval)
|
||||
{
|
||||
result.Add(new StylusPoint(start.X, start.Y, startPressure));
|
||||
result.Add(new StylusPoint(end.X, end.Y, endPressure));
|
||||
return result;
|
||||
}
|
||||
|
||||
int pointCount = Math.Max(2, (int)(distance / minPointInterval) + 1);
|
||||
|
||||
result.Add(new StylusPoint(start.X, start.Y, startPressure));
|
||||
|
||||
for (int i = 1; i < pointCount - 1; i++)
|
||||
{
|
||||
double ratio = (double)i / (pointCount - 1);
|
||||
double pressure = startPressure + (endPressure - startPressure) * ratio;
|
||||
double x = start.X + (end.X - start.X) * ratio;
|
||||
double y = start.Y + (end.Y - start.Y) * ratio;
|
||||
result.Add(new StylusPoint(x, y, (float)pressure));
|
||||
}
|
||||
|
||||
result.Add(new StylusPoint(end.X, end.Y, endPressure));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public StylusPointCollection GenerateFakePressureRectangle(StylusPointCollection points)
|
||||
{
|
||||
var newPoint = new StylusPointCollection();
|
||||
|
||||
if (Settings.InkToShape.IsInkToShapeNoFakePressureRectangle || penType == 1)
|
||||
{
|
||||
if (points.Count >= 4)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Point start = points[i].ToPoint();
|
||||
Point end = points[(i + 1) % 4].ToPoint();
|
||||
var edgePoints = GeneratePointsBetween(start, end, 0.5f, 0.5f, 8.0);
|
||||
if (i == 0)
|
||||
{
|
||||
foreach (var pt in edgePoints)
|
||||
{
|
||||
newPoint.Add(pt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 1; j < edgePoints.Count; j++)
|
||||
{
|
||||
newPoint.Add(edgePoints[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return points;
|
||||
}
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
if (points.Count >= 4)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Point start = points[i].ToPoint();
|
||||
Point end = points[(i + 1) % 4].ToPoint();
|
||||
|
||||
Point midPoint = GetCenterPoint(start, end);
|
||||
|
||||
var startToMid = GeneratePointsBetween(start, midPoint, 0.4f, 0.8f, 8.0);
|
||||
if (i == 0)
|
||||
{
|
||||
foreach (var pt in startToMid)
|
||||
{
|
||||
newPoint.Add(pt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 1; j < startToMid.Count; j++)
|
||||
{
|
||||
newPoint.Add(startToMid[j]);
|
||||
}
|
||||
}
|
||||
|
||||
var midToEnd = GeneratePointsBetween(midPoint, end, 0.8f, 0.4f, 8.0);
|
||||
for (int j = 1; j < midToEnd.Count; j++)
|
||||
{
|
||||
newPoint.Add(midToEnd[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
var newPoint = new StylusPointCollection();
|
||||
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
|
||||
var cPoint = GetCenterPoint(points[0], points[1]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
|
||||
newPoint.Add(new StylusPoint(points[1].X, points[1].Y, (float)0.4));
|
||||
cPoint = GetCenterPoint(points[1], points[2]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
|
||||
newPoint.Add(new StylusPoint(points[2].X, points[2].Y, (float)0.4));
|
||||
cPoint = GetCenterPoint(points[2], points[3]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[3].X, points[3].Y, (float)0.4));
|
||||
newPoint.Add(new StylusPoint(points[3].X, points[3].Y, (float)0.4));
|
||||
cPoint = GetCenterPoint(points[3], points[0]);
|
||||
newPoint.Add(new StylusPoint(cPoint.X, cPoint.Y, (float)0.8));
|
||||
newPoint.Add(new StylusPoint(points[0].X, points[0].Y, (float)0.4));
|
||||
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -13,6 +14,7 @@ using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using WinForms = System.Windows.Forms;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -57,18 +59,18 @@ namespace Ink_Canvas
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Timer timerCheckPPT = new Timer();
|
||||
private Timer timerKillProcess = new Timer();
|
||||
private Timer timerCheckAutoFold = new Timer();
|
||||
private System.Timers.Timer timerCheckPPT = new System.Timers.Timer();
|
||||
private System.Timers.Timer timerKillProcess = new System.Timers.Timer();
|
||||
private System.Timers.Timer timerCheckAutoFold = new System.Timers.Timer();
|
||||
private string AvailableLatestVersion;
|
||||
private Timer timerCheckAutoUpdateWithSilence = new Timer();
|
||||
private Timer timerCheckAutoUpdateRetry = new Timer();
|
||||
private System.Timers.Timer timerCheckAutoUpdateWithSilence = new System.Timers.Timer();
|
||||
private System.Timers.Timer timerCheckAutoUpdateRetry = new System.Timers.Timer();
|
||||
private bool isHidingSubPanelsWhenInking; // 避免书写时触发二次关闭二级菜单导致动画不连续
|
||||
private int updateCheckRetryCount = 0;
|
||||
private const int MAX_UPDATE_CHECK_RETRIES = 6;
|
||||
private Timer timerDisplayTime = new Timer();
|
||||
private Timer timerDisplayDate = new Timer();
|
||||
private Timer timerNtpSync = new Timer();
|
||||
private System.Timers.Timer timerDisplayTime = new System.Timers.Timer();
|
||||
private System.Timers.Timer timerDisplayDate = new System.Timers.Timer();
|
||||
private System.Timers.Timer timerNtpSync = new System.Timers.Timer();
|
||||
|
||||
private TimeViewModel nowTimeVM = new TimeViewModel();
|
||||
private DateTime cachedNetworkTime = DateTime.Now;
|
||||
@@ -512,6 +514,14 @@ namespace Ink_Canvas
|
||||
if (isFloatingBarChangingHideMode) return;
|
||||
try
|
||||
{
|
||||
// 优先使用窗口概览模型进行检测
|
||||
if (_windowOverviewModel != null)
|
||||
{
|
||||
CheckAutoFoldWithWindowOverviewModel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果窗口概览模型未初始化,回退到传统的进程检测方式
|
||||
var windowProcessName = ForegroundWindowInfo.ProcessName();
|
||||
var windowTitle = ForegroundWindowInfo.WindowTitle();
|
||||
//LogHelper.WriteLogToFile("windowTitle | " + windowTitle + " | windowProcessName | " + windowProcessName);
|
||||
@@ -732,6 +742,225 @@ namespace Ink_Canvas
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查进程是否在用户的自动收纳设置中启用
|
||||
/// </summary>
|
||||
private bool IsProcessInAutoFoldSettings(string processName, WindowInfo windowInfo = null)
|
||||
{
|
||||
// 根据进程名和窗口信息检查是否在自动收纳设置中
|
||||
switch (processName)
|
||||
{
|
||||
case "EasiNote":
|
||||
// EasiNote需要检查版本
|
||||
if (windowInfo != null && !string.IsNullOrEmpty(windowInfo.ProcessPath) && windowInfo.ProcessPath != "Unknown")
|
||||
{
|
||||
try
|
||||
{
|
||||
var versionInfo = FileVersionInfo.GetVersionInfo(windowInfo.ProcessPath);
|
||||
string version = versionInfo.FileVersion;
|
||||
string prodName = versionInfo.ProductName;
|
||||
|
||||
if (version != null && version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
|
||||
return true;
|
||||
if (version != null && version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
|
||||
return true;
|
||||
if (prodName != null && prodName.Contains("3C") && Settings.Automation.IsAutoFoldInEasiNote3C)
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return false;
|
||||
|
||||
case "EasiCamera":
|
||||
return Settings.Automation.IsAutoFoldInEasiCamera;
|
||||
|
||||
case "EasiNote5C":
|
||||
return Settings.Automation.IsAutoFoldInEasiNote5C;
|
||||
|
||||
case "BoardService":
|
||||
case "seewoPincoTeacher":
|
||||
return Settings.Automation.IsAutoFoldInSeewoPincoTeacher;
|
||||
|
||||
case "HiteCamera":
|
||||
return Settings.Automation.IsAutoFoldInHiteCamera;
|
||||
|
||||
case "HiteTouchPro":
|
||||
return Settings.Automation.IsAutoFoldInHiteTouchPro;
|
||||
|
||||
case "HiteLightBoard":
|
||||
return Settings.Automation.IsAutoFoldInHiteLightBoard;
|
||||
|
||||
case "WxBoardMain":
|
||||
return Settings.Automation.IsAutoFoldInWxBoardMain;
|
||||
|
||||
case "MicrosoftWhiteboard":
|
||||
case "msedgewebview2":
|
||||
return Settings.Automation.IsAutoFoldInMSWhiteboard;
|
||||
|
||||
case "Amdox.WhiteBoard":
|
||||
return Settings.Automation.IsAutoFoldInAdmoxWhiteboard;
|
||||
|
||||
case "Amdox.Booth":
|
||||
return Settings.Automation.IsAutoFoldInAdmoxBooth;
|
||||
|
||||
case "QPoint":
|
||||
return Settings.Automation.IsAutoFoldInQPoint;
|
||||
|
||||
case "YiYunVisualPresenter":
|
||||
return Settings.Automation.IsAutoFoldInYiYunVisualPresenter;
|
||||
|
||||
case "WhiteBoard":
|
||||
// MaxHub需要检查窗口标题
|
||||
if (windowInfo != null && !string.IsNullOrEmpty(windowInfo.Title) &&
|
||||
windowInfo.Title.Contains("白板书写") && Settings.Automation.IsAutoFoldInMaxHubWhiteboard)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(windowInfo.ProcessPath) && windowInfo.ProcessPath != "Unknown")
|
||||
{
|
||||
try
|
||||
{
|
||||
var versionInfo = FileVersionInfo.GetVersionInfo(windowInfo.ProcessPath);
|
||||
if (versionInfo.FileVersion != null && versionInfo.FileVersion.StartsWith("6.") &&
|
||||
versionInfo.ProductName == "WhiteBoard")
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用窗口概览模型检测是否需要自动收纳
|
||||
/// </summary>
|
||||
private void CheckAutoFoldWithWindowOverviewModel()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_windowOverviewModel == null) return;
|
||||
|
||||
// 获取浮动栏的位置和大小
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var floatingBarMargin = ViewboxFloatingBar.Margin;
|
||||
var floatingBarWidth = ViewboxFloatingBar.ActualWidth;
|
||||
var floatingBarHeight = ViewboxFloatingBar.ActualHeight;
|
||||
|
||||
// 如果浮动栏未显示或大小为0,跳过检测
|
||||
if (floatingBarWidth <= 0 || floatingBarHeight <= 0) return;
|
||||
|
||||
// 计算浮动栏在屏幕上的位置(考虑DPI缩放)
|
||||
var screen = WinForms.Screen.PrimaryScreen;
|
||||
var dpiScaleX = PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1.0;
|
||||
var dpiScaleY = PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M22 ?? 1.0;
|
||||
|
||||
// 将WPF坐标转换为屏幕坐标
|
||||
var point = ViewboxFloatingBar.PointToScreen(new System.Windows.Point(0, 0));
|
||||
int left = (int)(point.X);
|
||||
int top = (int)(point.Y);
|
||||
int right = left + (int)(floatingBarWidth * dpiScaleX);
|
||||
int bottom = top + (int)(floatingBarHeight * dpiScaleY);
|
||||
|
||||
// 创建检测区域(稍微扩大一点,确保检测到覆盖)
|
||||
var detectionArea = new WindowRect
|
||||
{
|
||||
Left = left - 5,
|
||||
Top = top - 5,
|
||||
Right = right + 5,
|
||||
Bottom = bottom + 5
|
||||
};
|
||||
|
||||
// 排除当前应用程序的进程
|
||||
var excludeProcesses = new List<string> { "InkCanvasForClass", "Ink Canvas" };
|
||||
|
||||
// 检查 OldZyBoard(通过窗口标题检测)
|
||||
bool isOldZyBoardWindowExisted = Settings.Automation.IsAutoFoldInOldZyBoard &&
|
||||
(WinTabWindowsChecker.IsWindowExisted("WhiteBoard - DrawingWindow") ||
|
||||
WinTabWindowsChecker.IsWindowExisted("InstantAnnotationWindow"));
|
||||
|
||||
if (isOldZyBoardWindowExisted)
|
||||
{
|
||||
if (!isFloatingBarFolded)
|
||||
{
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
}
|
||||
else if (!isOldZyBoardWindowExisted && isFloatingBarFolded && !foldFloatingBarByUser)
|
||||
{
|
||||
// OldZyBoard窗口退出时,如果未开启保持收纳模式,则展开
|
||||
if (!Settings.Automation.KeepFoldAfterSoftwareExit)
|
||||
{
|
||||
UnFoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
return; // OldZyBoard 使用特殊检测方式,处理完后直接返回
|
||||
}
|
||||
|
||||
if (isOldZyBoardWindowExisted)
|
||||
{
|
||||
return; // OldZyBoard 窗口存在时,直接返回,不继续检测其他窗口
|
||||
}
|
||||
|
||||
// 获取覆盖浮动栏的所有窗口
|
||||
var coveringWindows = _windowOverviewModel.GetCoveringWindows(detectionArea, excludeProcesses, 0.1);
|
||||
|
||||
// 检查是否有覆盖窗口在用户的自动收纳设置中
|
||||
bool shouldFold = false;
|
||||
|
||||
foreach (var window in coveringWindows)
|
||||
{
|
||||
// 检查窗口是否全屏(全屏窗口优先)
|
||||
bool isFullScreen = window.IsFullScreen;
|
||||
|
||||
// 检查窗口大小是否接近全屏(用于检测二级菜单等)
|
||||
bool isNearFullScreen = false;
|
||||
try
|
||||
{
|
||||
var screenBounds = WinForms.Screen.FromHandle(window.Handle).Bounds;
|
||||
isNearFullScreen = window.Rect.Width >= screenBounds.Width - 16 &&
|
||||
window.Rect.Height >= screenBounds.Height - 16;
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 如果窗口是全屏或接近全屏,且进程在自动收纳设置中,则应该收纳
|
||||
if ((isFullScreen || isNearFullScreen) && IsProcessInAutoFoldSettings(window.ProcessName, window))
|
||||
{
|
||||
shouldFold = true;
|
||||
break; // 找到匹配的窗口就退出
|
||||
}
|
||||
}
|
||||
|
||||
// 如果检测到应该收纳的窗口,且当前未收纳,则收纳
|
||||
if (shouldFold && !isFloatingBarFolded)
|
||||
{
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
// 如果未检测到应该收纳的窗口,且当前已收纳(且不是用户手动收纳),则展开
|
||||
else if (!shouldFold && isFloatingBarFolded && !foldFloatingBarByUser)
|
||||
{
|
||||
// 检查是否启用了软件退出后保持收纳模式
|
||||
if (!Settings.Automation.KeepFoldAfterSoftwareExit)
|
||||
{
|
||||
UnFoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口概览模型检测失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口概览模型自动收纳检测异常: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void timerCheckAutoUpdateWithSilence_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
// 停止计时器,避免重复触发
|
||||
|
||||
@@ -96,53 +96,43 @@ namespace Ink_Canvas
|
||||
}
|
||||
else if (originalElement is MediaElement originalMedia)
|
||||
{
|
||||
var clonedMedia = new MediaElement();
|
||||
|
||||
// 复制媒体属性
|
||||
clonedMedia.Source = originalMedia.Source;
|
||||
clonedMedia.Width = originalMedia.Width;
|
||||
clonedMedia.Height = originalMedia.Height;
|
||||
clonedMedia.Name = originalMedia.Name;
|
||||
clonedMedia.IsHitTestVisible = originalMedia.IsHitTestVisible;
|
||||
clonedMedia.Focusable = originalMedia.Focusable;
|
||||
var clonedMedia = new MediaElement
|
||||
{
|
||||
Source = originalMedia.Source,
|
||||
Width = originalMedia.Width,
|
||||
Height = originalMedia.Height,
|
||||
Name = originalMedia.Name,
|
||||
IsHitTestVisible = originalMedia.IsHitTestVisible,
|
||||
Focusable = originalMedia.Focusable,
|
||||
RenderTransform = originalMedia.RenderTransform?.Clone()
|
||||
};
|
||||
|
||||
// 复制位置
|
||||
InkCanvas.SetLeft(clonedMedia, InkCanvas.GetLeft(originalMedia));
|
||||
InkCanvas.SetTop(clonedMedia, InkCanvas.GetTop(originalMedia));
|
||||
|
||||
// 复制变换
|
||||
if (originalMedia.RenderTransform != null)
|
||||
{
|
||||
clonedMedia.RenderTransform = originalMedia.RenderTransform.Clone();
|
||||
}
|
||||
|
||||
return clonedMedia;
|
||||
}
|
||||
else if (originalElement is Border originalBorder)
|
||||
{
|
||||
var clonedBorder = new Border();
|
||||
|
||||
// 复制边框属性
|
||||
clonedBorder.Width = originalBorder.Width;
|
||||
clonedBorder.Height = originalBorder.Height;
|
||||
clonedBorder.Name = originalBorder.Name;
|
||||
clonedBorder.IsHitTestVisible = originalBorder.IsHitTestVisible;
|
||||
clonedBorder.Focusable = originalBorder.Focusable;
|
||||
clonedBorder.Background = originalBorder.Background;
|
||||
clonedBorder.BorderBrush = originalBorder.BorderBrush;
|
||||
clonedBorder.BorderThickness = originalBorder.BorderThickness;
|
||||
clonedBorder.CornerRadius = originalBorder.CornerRadius;
|
||||
var clonedBorder = new Border
|
||||
{
|
||||
Width = originalBorder.Width,
|
||||
Height = originalBorder.Height,
|
||||
Name = originalBorder.Name,
|
||||
IsHitTestVisible = originalBorder.IsHitTestVisible,
|
||||
Focusable = originalBorder.Focusable,
|
||||
Background = originalBorder.Background,
|
||||
BorderBrush = originalBorder.BorderBrush,
|
||||
BorderThickness = originalBorder.BorderThickness,
|
||||
CornerRadius = originalBorder.CornerRadius,
|
||||
RenderTransform = originalBorder.RenderTransform?.Clone()
|
||||
};
|
||||
|
||||
// 复制位置
|
||||
InkCanvas.SetLeft(clonedBorder, InkCanvas.GetLeft(originalBorder));
|
||||
InkCanvas.SetTop(clonedBorder, InkCanvas.GetTop(originalBorder));
|
||||
|
||||
// 复制变换
|
||||
if (originalBorder.RenderTransform != null)
|
||||
{
|
||||
clonedBorder.RenderTransform = originalBorder.RenderTransform.Clone();
|
||||
}
|
||||
|
||||
return clonedBorder;
|
||||
}
|
||||
}
|
||||
@@ -221,17 +211,6 @@ namespace Ink_Canvas
|
||||
|
||||
private void MainWindow_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 检查触摸是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
||||
var touchPoint = e.GetTouchPoint(this);
|
||||
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
||||
new Rect(0, 0, ViewboxFloatingBar.ActualWidth, ViewboxFloatingBar.ActualHeight));
|
||||
|
||||
// 如果触摸发生在浮动栏区域,不阻止事件传播,让浮动栏按钮能够接收触摸事件
|
||||
if (floatingBarBounds.Contains(touchPoint.Position))
|
||||
{
|
||||
// 不设置 ViewboxFloatingBar.IsHitTestVisible = false,让浮动栏按钮能够接收触摸事件
|
||||
return;
|
||||
}
|
||||
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint
|
||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke
|
||||
@@ -325,7 +304,7 @@ namespace Ink_Canvas
|
||||
// 根据当前编辑模式设置不同的光标
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
inkCanvas.Cursor = Cursors.Cross;
|
||||
inkCanvas.Cursor = Cursors.Arrow;
|
||||
}
|
||||
else if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
@@ -483,8 +462,8 @@ namespace Ink_Canvas
|
||||
|
||||
var strokeVisual = new StrokeVisual(inkCanvas.DefaultDrawingAttributes.Clone());
|
||||
StrokeVisualList[id] = strokeVisual;
|
||||
StrokeVisualList[id] = strokeVisual;
|
||||
var visualCanvas = new VisualCanvas(strokeVisual);
|
||||
var visualCanvas = new VisualCanvas();
|
||||
strokeVisual.SetVisualCanvas(visualCanvas);
|
||||
VisualCanvasList[id] = visualCanvas;
|
||||
inkCanvas.Children.Add(visualCanvas);
|
||||
|
||||
@@ -533,13 +512,6 @@ namespace Ink_Canvas
|
||||
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
// 橡皮状态下只return,保证橡皮状态可保持
|
||||
return;
|
||||
}
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
|
||||
{
|
||||
// 套索选状态下不直接return,允许触摸事件继续处理
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
return;
|
||||
}
|
||||
if (drawingShapeMode != 0)
|
||||
@@ -556,6 +528,10 @@ namespace Ink_Canvas
|
||||
|
||||
return;
|
||||
}
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
return;
|
||||
@@ -571,171 +547,30 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 手掌擦相关变量
|
||||
private bool isPalmEraserActive;
|
||||
private InkCanvasEditingMode palmEraserLastEditingMode = InkCanvasEditingMode.Ink;
|
||||
private bool palmEraserLastIsHighlighter;
|
||||
private bool palmEraserWasEnabledBeforeMultiTouch;
|
||||
|
||||
public double GetTouchBoundWidth(TouchEventArgs e)
|
||||
{
|
||||
var args = e.GetTouchPoint(null).Bounds;
|
||||
if (!Settings.Advanced.IsQuadIR) return args.Width;
|
||||
else return Math.Sqrt(args.Width * args.Height); // 四边红外
|
||||
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;
|
||||
}
|
||||
|
||||
private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||
private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 检查触摸是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
||||
var touchPoint = e.GetTouchPoint(this);
|
||||
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
||||
new Rect(0, 0, ViewboxFloatingBar.ActualWidth, ViewboxFloatingBar.ActualHeight));
|
||||
|
||||
// 如果触摸发生在浮动栏区域,不阻止事件传播,让浮动栏按钮能够接收触摸事件
|
||||
if (floatingBarBounds.Contains(touchPoint.Position))
|
||||
{
|
||||
// 不设置 ViewboxFloatingBar.IsHitTestVisible = false,让浮动栏按钮能够接收触摸事件
|
||||
return;
|
||||
}
|
||||
|
||||
// 橡皮状态下不做任何切换,直接return,保证橡皮可持续
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint
|
||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke)
|
||||
{
|
||||
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;
|
||||
}
|
||||
// 第二笔时不更新iniP,保持第一笔的起点
|
||||
}
|
||||
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);
|
||||
|
||||
// Palm Eraser 逻辑
|
||||
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// 记录当前编辑模式和高光状态
|
||||
palmEraserLastEditingMode = inkCanvas.EditingMode;
|
||||
palmEraserLastIsHighlighter = drawingAttributes.IsHighlighter;
|
||||
|
||||
// 动态调整橡皮大小
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设备1个的时候,记录中心点
|
||||
//设备1个的时候,记录中心点
|
||||
if (dec.Count == 1)
|
||||
{
|
||||
touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
centerPoint = touchPoint.Position;
|
||||
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 对于双曲线绘制,第一笔时记录起点,第二笔时不更新起点
|
||||
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||
{
|
||||
// 双曲线绘制:第一笔记录起点,第二笔保持第一笔的起点
|
||||
if (drawMultiStepShapeCurrentStep == 0)
|
||||
{
|
||||
iniP = touchPoint.Position;
|
||||
}
|
||||
// 第二笔时不更新iniP,保持第一笔的起点
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他图形正常记录起点
|
||||
iniP = touchPoint.Position;
|
||||
}
|
||||
}
|
||||
|
||||
// 记录第一根手指点击时的 StrokeCollection
|
||||
//记录第一根手指点击时的 StrokeCollection
|
||||
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
||||
}
|
||||
//设备两个及两个以上,将画笔功能关闭
|
||||
@@ -744,61 +579,25 @@ namespace Ink_Canvas
|
||||
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;
|
||||
System.Threading.Tasks.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;
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void inkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||
private void InkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
|
||||
// 如果手掌擦激活,更新橡皮擦反馈位置
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||
}
|
||||
}
|
||||
|
||||
private void inkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 橡皮状态下不做任何切换,直接return,保证橡皮可持续
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
inkCanvas.ReleaseAllTouchCaptures();
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// Palm Eraser 逻辑
|
||||
//手势完成后切回之前的状态
|
||||
if (dec.Count > 1)
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
dec.Remove(e.TouchDevice.Id);
|
||||
|
||||
// 重置多触控点定时器状态
|
||||
@@ -807,17 +606,31 @@ namespace Ink_Canvas
|
||||
isMultiTouchTimerActive = false;
|
||||
}
|
||||
|
||||
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 (drawingShapeMode != 0)
|
||||
{
|
||||
isTouchDown = false;
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// 对于双曲线等需要多步绘制的图形,触摸抬手时应该进入下一步
|
||||
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||
{
|
||||
// 双曲线绘制:触摸抬手时进入下一步,但不自动触发鼠标抬起事件
|
||||
// 让用户继续绘制第二笔
|
||||
if (drawMultiStepShapeCurrentStep == 0)
|
||||
{
|
||||
// 第一笔完成,进入第二笔
|
||||
@@ -836,7 +649,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他单步绘制的图形,触摸抬手时完成绘制
|
||||
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = MouseLeftButtonUpEvent,
|
||||
@@ -846,82 +658,6 @@ 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)
|
||||
{
|
||||
// 当所有触摸点都抬起时,确保正确恢复编辑模式
|
||||
// 这对于从橡皮擦切换到笔后恢复多指手势功能很重要
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None &&
|
||||
lastInkCanvasEditingMode != InkCanvasEditingMode.None &&
|
||||
lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
}
|
||||
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
LogHelper.WriteLogToFile("Palm eraser force recovery - all touch points cleared");
|
||||
|
||||
// 恢复高光状态
|
||||
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
|
||||
|
||||
// 恢复编辑模式
|
||||
try
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
switch (palmEraserLastEditingMode)
|
||||
{
|
||||
case InkCanvasEditingMode.Ink:
|
||||
PenIcon_Click(null, null);
|
||||
break;
|
||||
case InkCanvasEditingMode.Select:
|
||||
SymbolIconSelect_MouseUp(null, null);
|
||||
break;
|
||||
default:
|
||||
inkCanvas.EditingMode = palmEraserLastEditingMode;
|
||||
break;
|
||||
}
|
||||
LogHelper.WriteLogToFile($"Palm eraser force recovered to mode: {palmEraserLastEditingMode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser force recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 如果手掌擦还在激活状态但触摸点已清空,强制重置状态
|
||||
isPalmEraserActive = false;
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
DisableEraserOverlay();
|
||||
if (Settings.Canvas.IsShowCursor)
|
||||
{
|
||||
inkCanvas.ForceCursor = true;
|
||||
inkCanvas.UseCustomCursor = true;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("Palm eraser force recovery completed");
|
||||
}
|
||||
}
|
||||
}
|
||||
inkCanvas.Opacity = 1;
|
||||
|
||||
if (dec.Count == 0)
|
||||
@@ -934,70 +670,46 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void inkCanvas_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
|
||||
private void InkCanvas_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
|
||||
{
|
||||
e.Mode = ManipulationModes.All;
|
||||
}
|
||||
|
||||
private void inkCanvas_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e) { }
|
||||
private void InkCanvas_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e) { }
|
||||
|
||||
private void Main_Grid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
|
||||
{
|
||||
if (e.Manipulators.Count() != 0) return;
|
||||
if (drawingShapeMode == 0
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||
if (e.Manipulators.Count() == 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
if (dec.Count > 0)
|
||||
{
|
||||
dec.Clear();
|
||||
}
|
||||
isSingleFingerDragMode = false;
|
||||
|
||||
if (drawingShapeMode == 0
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||
{
|
||||
// 手掌擦时禁止移动/缩放
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
return;
|
||||
// 三指及以上禁止缩放
|
||||
bool disableScale = dec.Count >= 3;
|
||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
|
||||
if (isInMultiTouchMode) return;
|
||||
bool hasMultipleManipulators = e.Manipulators.Count() >= 2;
|
||||
bool shouldUseTwoFingerGesture = (dec.Count >= 2 && hasMultipleManipulators &&
|
||||
(Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode ||
|
||||
StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
StackPanelPPTButtons.Visibility == Visibility.Collapsed)) ||
|
||||
isSingleFingerDragMode;
|
||||
|
||||
if (dec.Count == 0 && (isSingleFingerDragMode || isInMultiTouchMode))
|
||||
{
|
||||
ResetTouchStates();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是单指拖动选中的墨迹,允许处理
|
||||
if (dec.Count == 1 && inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
var md = e.DeltaManipulation;
|
||||
var trans = md.Translation; // 获得位移矢量
|
||||
|
||||
if (trans.X != 0 || trans.Y != 0)
|
||||
{
|
||||
var m = new Matrix();
|
||||
m.Translate(trans.X, trans.Y); // 移动
|
||||
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in strokes)
|
||||
{
|
||||
stroke.Transform(m, false);
|
||||
}
|
||||
|
||||
// 更新选择框位置
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
if ((dec.Count >= 2 && (Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode ||
|
||||
StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
StackPanelPPTButtons.Visibility == Visibility.Collapsed)) ||
|
||||
isSingleFingerDragMode)
|
||||
if (shouldUseTwoFingerGesture)
|
||||
{
|
||||
var md = e.DeltaManipulation;
|
||||
var trans = md.Translation; // 获得位移矢量
|
||||
@@ -1007,20 +719,23 @@ namespace Ink_Canvas
|
||||
if (Settings.Gesture.IsEnableTwoFingerTranslate)
|
||||
m.Translate(trans.X, trans.Y); // 移动
|
||||
|
||||
// 计算中心点(用于缩放和旋转)
|
||||
var fe = e.Source as FrameworkElement;
|
||||
var center = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
|
||||
center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点
|
||||
|
||||
if (Settings.Gesture.IsEnableTwoFingerGestureTranslateOrRotation)
|
||||
{
|
||||
var rotate = md.Rotation; // 获得旋转角度
|
||||
var scale = md.Scale; // 获得缩放倍数
|
||||
|
||||
// Find center of element and then transform to get current location of center
|
||||
var fe = e.Source as FrameworkElement;
|
||||
var center = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
|
||||
center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点
|
||||
|
||||
if (Settings.Gesture.IsEnableTwoFingerRotation)
|
||||
m.RotateAt(rotate, center.X, center.Y); // 旋转
|
||||
if (Settings.Gesture.IsEnableTwoFingerZoom && !disableScale)
|
||||
m.ScaleAt(scale.X, scale.Y, center.X, center.Y); // 缩放
|
||||
}
|
||||
|
||||
if (Settings.Gesture.IsEnableTwoFingerZoom)
|
||||
{
|
||||
var scale = md.Scale; // 获得缩放倍数
|
||||
m.ScaleAt(scale.X, scale.Y, center.X, center.Y); // 缩放
|
||||
}
|
||||
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
@@ -1043,6 +758,13 @@ namespace Ink_Canvas
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Settings.Gesture.IsEnableTwoFingerZoom) continue;
|
||||
try
|
||||
{
|
||||
stroke.DrawingAttributes.Width *= md.Scale.X;
|
||||
stroke.DrawingAttributes.Height *= md.Scale.Y;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1124,8 +846,7 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
// 获取图片的RenderTransform,如果不存在则创建新的TransformGroup
|
||||
TransformGroup transformGroup = image.RenderTransform as TransformGroup;
|
||||
if (transformGroup == null)
|
||||
if (!(image.RenderTransform is TransformGroup transformGroup))
|
||||
{
|
||||
transformGroup = new TransformGroup();
|
||||
image.RenderTransform = transformGroup;
|
||||
@@ -1149,8 +870,7 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
// 获取媒体元素的RenderTransform,如果不存在则创建新的TransformGroup
|
||||
TransformGroup transformGroup = mediaElement.RenderTransform as TransformGroup;
|
||||
if (transformGroup == null)
|
||||
if (!(mediaElement.RenderTransform is TransformGroup transformGroup))
|
||||
{
|
||||
transformGroup = new TransformGroup();
|
||||
mediaElement.RenderTransform = transformGroup;
|
||||
@@ -1165,69 +885,6 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"应用媒体元素变换失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 退出多指书写模式,恢复InkCanvas的TouchDown事件绑定
|
||||
private void ExitMultiTouchModeIfNeeded()
|
||||
{
|
||||
if (isInMultiTouchMode)
|
||||
{
|
||||
inkCanvas.StylusDown -= MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove -= MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp -= MainWindow_StylusUp;
|
||||
inkCanvas.TouchDown -= MainWindow_TouchDown;
|
||||
inkCanvas.TouchDown += Main_Grid_TouchDown;
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = false;
|
||||
// 关闭多指书写时,恢复手掌擦开关
|
||||
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||
{
|
||||
Settings.Canvas.EnablePalmEraser = true;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 进入多指书写模式,绑定Main_Grid_TouchDown
|
||||
private void EnterMultiTouchModeIfNeeded()
|
||||
{
|
||||
if (!isInMultiTouchMode)
|
||||
{
|
||||
inkCanvas.StylusDown += MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove += MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp += MainWindow_StylusUp;
|
||||
inkCanvas.TouchDown += MainWindow_TouchDown;
|
||||
inkCanvas.TouchDown -= Main_Grid_TouchDown;
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = true;
|
||||
// 启用多指书写时,自动禁用手掌擦
|
||||
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||
Settings.Canvas.EnablePalmEraser = false;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,5 +49,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.18.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.0")]
|
||||
[assembly: AssemblyVersion("1.7.18.3")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.3")]
|
||||
|
||||
Binary file not shown.
@@ -157,6 +157,8 @@ namespace Ink_Canvas
|
||||
public UpdateChannel UpdateChannel { get; set; } = UpdateChannel.Release;
|
||||
[JsonProperty("skippedVersion")]
|
||||
public string SkippedVersion { get; set; } = "";
|
||||
[JsonProperty("autoUpdatePauseUntilDate")]
|
||||
public string AutoUpdatePauseUntilDate { get; set; } = "";
|
||||
[JsonProperty("isEnableNibMode")]
|
||||
public bool IsEnableNibMode { get; set; }
|
||||
[JsonProperty("isFoldAtStartup")]
|
||||
@@ -283,6 +285,18 @@ namespace Ink_Canvas
|
||||
[JsonProperty("enablePPTButtonLongPressPageTurn")]
|
||||
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
|
||||
|
||||
[JsonProperty("pptLSButtonOpacity")]
|
||||
public double PPTLSButtonOpacity { get; set; } = 0.5;
|
||||
|
||||
[JsonProperty("pptRSButtonOpacity")]
|
||||
public double PPTRSButtonOpacity { get; set; } = 0.5;
|
||||
|
||||
[JsonProperty("pptLBButtonOpacity")]
|
||||
public double PPTLBButtonOpacity { get; set; } = 0.5;
|
||||
|
||||
[JsonProperty("pptRBButtonOpacity")]
|
||||
public double PPTRBButtonOpacity { get; set; } = 0.5;
|
||||
|
||||
// -- new --
|
||||
|
||||
[JsonProperty("powerPointSupport")]
|
||||
@@ -317,6 +331,12 @@ namespace Ink_Canvas
|
||||
public bool EnablePowerPointEnhancement { get; set; } = false;
|
||||
[JsonProperty("showGestureButtonInSlideShow")]
|
||||
public bool ShowGestureButtonInSlideShow { get; set; } = false;
|
||||
[JsonProperty("skipAnimationsWhenGoNext")]
|
||||
public bool SkipAnimationsWhenGoNext { get; set; } = false;
|
||||
[JsonProperty("enablePPTTimeCapsule")]
|
||||
public bool EnablePPTTimeCapsule { get; set; } = true;
|
||||
[JsonProperty("pptTimeCapsulePosition")]
|
||||
public int PPTTimeCapsulePosition { get; set; } = 1;
|
||||
}
|
||||
|
||||
public class Automation
|
||||
@@ -461,6 +481,9 @@ namespace Ink_Canvas
|
||||
[JsonProperty("isSaveFullPageStrokes")]
|
||||
public bool IsSaveFullPageStrokes;
|
||||
|
||||
[JsonProperty("isSaveStrokesAsXML")]
|
||||
public bool IsSaveStrokesAsXML { get; set; } = false;
|
||||
|
||||
[JsonProperty("isAutoEnterAnnotationAfterKillHite")]
|
||||
public bool IsAutoEnterAnnotationAfterKillHite { get; set; }
|
||||
|
||||
@@ -602,6 +625,9 @@ namespace Ink_Canvas
|
||||
|
||||
[JsonProperty("enableUIAccessTopMost")]
|
||||
public bool EnableUIAccessTopMost { get; set; } = false;
|
||||
|
||||
[JsonProperty("windowMode")]
|
||||
public bool WindowMode { get; set; } = true;
|
||||
}
|
||||
|
||||
public class InkToShape
|
||||
@@ -621,7 +647,7 @@ namespace Ink_Canvas
|
||||
[JsonProperty("lineStraightenSensitivity")]
|
||||
public double LineStraightenSensitivity { get; set; } = 0.20;
|
||||
[JsonProperty("lineNormalizationThreshold")]
|
||||
public double LineNormalizationThreshold { get; set; } = 0.5;
|
||||
public double LineNormalizationThreshold { get; set; } = 0.5;
|
||||
}
|
||||
|
||||
public class RandSettings
|
||||
|
||||
@@ -43,27 +43,27 @@ namespace Ink_Canvas
|
||||
TimeSpan timeSpan = DateTime.Now - startTime;
|
||||
TimeSpan totalTimeSpan = new TimeSpan(hour, minute, second);
|
||||
double spentTimePercent = timeSpan.TotalMilliseconds / (totalSeconds * 1000.0);
|
||||
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (!isOvertimeMode)
|
||||
{
|
||||
TimeSpan leftTimeSpan = totalTimeSpan - timeSpan;
|
||||
if (leftTimeSpan.Milliseconds > 0) leftTimeSpan += new TimeSpan(0, 0, 1);
|
||||
|
||||
|
||||
ProcessBarTime.CurrentValue = 1 - spentTimePercent;
|
||||
TextBlockHour.Text = leftTimeSpan.Hours.ToString("00");
|
||||
TextBlockMinute.Text = leftTimeSpan.Minutes.ToString("00");
|
||||
TextBlockSecond.Text = leftTimeSpan.Seconds.ToString("00");
|
||||
TbCurrentTime.Text = leftTimeSpan.ToString(@"hh\:mm\:ss");
|
||||
|
||||
|
||||
if (spentTimePercent >= 1 && MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true)
|
||||
{
|
||||
isOvertimeMode = true;
|
||||
ProcessBarTime.CurrentValue = 0;
|
||||
ProcessBarTime.Visibility = Visibility.Collapsed;
|
||||
BorderStopTime.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
||||
// 播放提醒音
|
||||
PlayTimerSound();
|
||||
}
|
||||
@@ -87,7 +87,7 @@ namespace Ink_Canvas
|
||||
TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F"));
|
||||
}
|
||||
BorderStopTime.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
||||
// 播放提醒音
|
||||
PlayTimerSound();
|
||||
}
|
||||
@@ -99,7 +99,7 @@ namespace Ink_Canvas
|
||||
TextBlockMinute.Text = overtimeSpan.Minutes.ToString("00");
|
||||
TextBlockSecond.Text = overtimeSpan.Seconds.ToString("00");
|
||||
TbCurrentTime.Text = overtimeSpan.ToString(@"hh\:mm\:ss");
|
||||
|
||||
|
||||
if (MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true)
|
||||
{
|
||||
TextBlockHour.Foreground = Brushes.Red;
|
||||
@@ -124,7 +124,7 @@ namespace Ink_Canvas
|
||||
bool isTimerRunning = false;
|
||||
bool isPaused = false;
|
||||
bool useLegacyUI = false;
|
||||
bool isOvertimeMode = false;
|
||||
bool isOvertimeMode = false;
|
||||
|
||||
Timer timer = new Timer();
|
||||
|
||||
@@ -288,7 +288,7 @@ namespace Ink_Canvas
|
||||
TextBlockHour.Foreground = textForeground3;
|
||||
else
|
||||
TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F"));
|
||||
|
||||
|
||||
isOvertimeMode = false;
|
||||
ProcessBarTime.Visibility = Visibility.Visible;
|
||||
}
|
||||
@@ -311,7 +311,7 @@ namespace Ink_Canvas
|
||||
isPaused = false;
|
||||
ProcessBarTime.CurrentValue = 0;
|
||||
ProcessBarTime.IsPaused = false;
|
||||
|
||||
|
||||
isOvertimeMode = false;
|
||||
ProcessBarTime.Visibility = Visibility.Visible;
|
||||
}
|
||||
@@ -416,7 +416,7 @@ namespace Ink_Canvas
|
||||
|
||||
isPaused = false;
|
||||
isTimerRunning = true;
|
||||
isOvertimeMode = false;
|
||||
isOvertimeMode = false;
|
||||
ProcessBarTime.Visibility = Visibility.Visible;
|
||||
timer.Start();
|
||||
UpdateStopTime();
|
||||
|
||||
@@ -25,25 +25,25 @@ namespace Ink_Canvas.Windows
|
||||
public DlassSettingsWindow(MainWindow mainWindow = null)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
// 初始化班级下拉框
|
||||
CmbClassSelection.Items.Clear();
|
||||
CmbClassSelection.Items.Add("(等待连接)");
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
CmbClassSelection.IsEnabled = false;
|
||||
|
||||
|
||||
// 加载保存的token
|
||||
LoadUserToken();
|
||||
|
||||
|
||||
// 加载自动上传设置
|
||||
LoadAutoUploadSettings();
|
||||
|
||||
|
||||
// 初始化API客户端(优先使用用户token)
|
||||
InitializeApiClient();
|
||||
|
||||
|
||||
// 窗口关闭时释放资源
|
||||
Closed += (s, e) => _apiClient?.Dispose();
|
||||
|
||||
|
||||
// 测试连接
|
||||
_ = TestConnectionAsync();
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var userToken = GetUserToken();
|
||||
var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl;
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(apiBaseUrl) || apiBaseUrl.Contains("api.dlass.tech"))
|
||||
{
|
||||
apiBaseUrl = "https://dlass.tech";
|
||||
@@ -65,7 +65,7 @@ namespace Ink_Canvas.Windows
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
_apiClient = new DlassApiClient(APP_ID, APP_SECRET, baseUrl: apiBaseUrl, userToken: userToken);
|
||||
@@ -107,7 +107,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var savedTokens = GetSavedTokens();
|
||||
var currentToken = GetUserToken();
|
||||
|
||||
|
||||
CmbSavedTokens.Items.Clear();
|
||||
if (savedTokens.Count > 0)
|
||||
{
|
||||
@@ -138,9 +138,9 @@ namespace Ink_Canvas.Windows
|
||||
CmbSavedTokens.SelectedIndex = 0;
|
||||
CmbSavedTokens.IsEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
TxtNewToken.Text = string.Empty;
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(currentToken))
|
||||
{
|
||||
TxtTokenStatus.Text = "已选择Token";
|
||||
@@ -176,7 +176,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
MainWindow.Settings.Dlass.SavedTokens = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(token) && !MainWindow.Settings.Dlass.SavedTokens.Contains(token))
|
||||
{
|
||||
MainWindow.Settings.Dlass.SavedTokens.Add(token);
|
||||
@@ -203,7 +203,7 @@ namespace Ink_Canvas.Windows
|
||||
private void LoadClasses(List<WhiteboardInfo> whiteboards, UserInfo user = null)
|
||||
{
|
||||
CmbClassSelection.Items.Clear();
|
||||
|
||||
|
||||
if (whiteboards != null && whiteboards.Count > 0)
|
||||
{
|
||||
var teacherName = user?.Username ?? "未知教师";
|
||||
@@ -212,7 +212,7 @@ namespace Ink_Canvas.Windows
|
||||
.GroupBy(w => w.ClassName)
|
||||
.OrderBy(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
|
||||
foreach (var group in classGroups)
|
||||
{
|
||||
var className = group.Key;
|
||||
@@ -224,7 +224,7 @@ namespace Ink_Canvas.Windows
|
||||
TeacherName = teacherName
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var savedClassName = MainWindow.Settings?.Dlass?.SelectedClassName ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(savedClassName))
|
||||
{
|
||||
@@ -243,7 +243,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
|
||||
CmbClassSelection.IsEnabled = true;
|
||||
}
|
||||
else
|
||||
@@ -339,7 +339,7 @@ namespace Ink_Canvas.Windows
|
||||
delayMinutes = 60;
|
||||
TxtUploadDelayMinutes.Text = "60";
|
||||
}
|
||||
|
||||
|
||||
MainWindow.Settings.Dlass.AutoUploadDelayMinutes = delayMinutes;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
@@ -398,13 +398,13 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var selectedToken = CmbSavedTokens.SelectedItem.ToString();
|
||||
SaveUserToken(selectedToken);
|
||||
|
||||
|
||||
_apiClient?.Dispose();
|
||||
InitializeApiClient();
|
||||
|
||||
|
||||
TxtTokenStatus.Text = "已选择Token";
|
||||
TxtTokenStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94));
|
||||
|
||||
|
||||
_ = TestConnectionAsync();
|
||||
}
|
||||
}
|
||||
@@ -430,14 +430,14 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
AddTokenToList(token);
|
||||
SaveUserToken(token);
|
||||
|
||||
|
||||
_apiClient?.Dispose();
|
||||
InitializeApiClient();
|
||||
|
||||
|
||||
LoadUserToken();
|
||||
|
||||
|
||||
MessageBox.Show("Token已成功保存并已选择", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
|
||||
_ = TestConnectionAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -465,24 +465,24 @@ namespace Ink_Canvas.Windows
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
RemoveTokenFromList(selectedToken);
|
||||
|
||||
|
||||
if (GetUserToken() == selectedToken)
|
||||
{
|
||||
SaveUserToken(string.Empty);
|
||||
}
|
||||
|
||||
|
||||
_apiClient?.Dispose();
|
||||
InitializeApiClient();
|
||||
|
||||
|
||||
LoadUserToken();
|
||||
|
||||
|
||||
CmbClassSelection.Items.Clear();
|
||||
CmbClassSelection.Items.Add("(等待连接)");
|
||||
CmbClassSelection.SelectedIndex = 0;
|
||||
CmbClassSelection.IsEnabled = false;
|
||||
_currentWhiteboards.Clear();
|
||||
_currentUser = null;
|
||||
|
||||
|
||||
TxtConnectionStatus.Text = "未连接";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(161, 161, 170));
|
||||
}
|
||||
@@ -513,7 +513,7 @@ namespace Ink_Canvas.Windows
|
||||
// 示例:保存设置到服务器
|
||||
// var settings = new { ... };
|
||||
// await _apiClient.PostAsync<ApiResponse>("/api/settings", settings);
|
||||
|
||||
|
||||
MessageBox.Show("设置已保存", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
Close();
|
||||
}
|
||||
@@ -566,21 +566,21 @@ namespace Ink_Canvas.Windows
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
|
||||
var result = await _apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
|
||||
|
||||
|
||||
if (result != null && result.Success)
|
||||
{
|
||||
var whiteboards = result.Whiteboards ?? new List<WhiteboardInfo>();
|
||||
_currentWhiteboards = whiteboards;
|
||||
_currentUser = result.User;
|
||||
var whiteboardCount = whiteboards.Count;
|
||||
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
TxtConnectionStatus.Text = $"已连接 (找到 {whiteboardCount} 个白板)";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(34, 197, 94));
|
||||
|
||||
|
||||
// 加载班级列表
|
||||
LoadClasses(whiteboards, result.User);
|
||||
});
|
||||
@@ -596,11 +596,11 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
throw new Exception("Token格式可能不正确(长度过短,至少需要10个字符)");
|
||||
}
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile($"Token验证失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -609,7 +609,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
TxtConnectionStatus.Text = "连接失败";
|
||||
TxtConnectionStatus.Foreground = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(239, 68, 68));
|
||||
|
||||
|
||||
// 清空班级列表
|
||||
CmbClassSelection.Items.Clear();
|
||||
CmbClassSelection.Items.Add("(无可用班级)");
|
||||
@@ -620,9 +620,9 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region API响应模型
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// auth-with-token接口响应模型
|
||||
/// </summary>
|
||||
@@ -630,17 +630,17 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("whiteboards")]
|
||||
public List<WhiteboardInfo> Whiteboards { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("count")]
|
||||
public int Count { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("user")]
|
||||
public UserInfo User { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 白板信息模型
|
||||
/// </summary>
|
||||
@@ -648,32 +648,32 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("board_id")]
|
||||
public string BoardId { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("secret_key")]
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("class_name")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("is_online")]
|
||||
public bool IsOnline { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("last_heartbeat")]
|
||||
public string LastHeartbeat { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息模型
|
||||
/// </summary>
|
||||
@@ -681,14 +681,14 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("username")]
|
||||
public string Username { get; set; }
|
||||
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("email")]
|
||||
public string Email { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 班级选择项
|
||||
/// </summary>
|
||||
@@ -697,13 +697,13 @@ namespace Ink_Canvas.Windows
|
||||
public string DisplayText { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string TeacherName { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return DisplayText;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
@@ -23,23 +23,23 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
InitializeComponent();
|
||||
parentControl = parent;
|
||||
|
||||
|
||||
this.Left = 0;
|
||||
this.Top = 0;
|
||||
this.Width = SystemParameters.PrimaryScreenWidth;
|
||||
this.Height = SystemParameters.PrimaryScreenHeight;
|
||||
|
||||
updateTimer = new System.Timers.Timer(100);
|
||||
|
||||
updateTimer = new System.Timers.Timer(100);
|
||||
updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
updateTimer.Start();
|
||||
|
||||
|
||||
parentControl.TimerCompleted += ParentWindow_TimerCompleted;
|
||||
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
mainWindow.PauseTopmostMaintenance();
|
||||
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
@@ -47,11 +47,11 @@ namespace Ink_Canvas.Windows
|
||||
timerContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 确保窗口置顶
|
||||
Loaded += FullscreenTimerWindow_Loaded;
|
||||
}
|
||||
|
||||
|
||||
private void FullscreenTimerWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 使用延迟确保窗口完全加载后再应用置顶
|
||||
@@ -60,7 +60,7 @@ namespace Ink_Canvas.Windows
|
||||
ApplyTopmost();
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
|
||||
#region Win32 API 声明和置顶管理
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
@@ -118,29 +118,29 @@ namespace Ink_Canvas.Windows
|
||||
this.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
UpdateTimeDisplay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool ShouldCloseWindow()
|
||||
{
|
||||
if (parentControl == null) return true;
|
||||
|
||||
|
||||
if (MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true)
|
||||
{
|
||||
if (parentControl.IsTimerRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (remainingTime.HasValue && remainingTime.Value.TotalSeconds < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -198,17 +198,17 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
SetDigitDisplay("FullHour1Display", Math.Abs(hours / 10) % 10, shouldShowRed);
|
||||
SetDigitDisplay("FullHour2Display", (hours % 10 + 10) % 10, shouldShowRed);
|
||||
|
||||
|
||||
SetDigitDisplay("FullMinute1Display", minutes / 10, shouldShowRed);
|
||||
SetDigitDisplay("FullMinute2Display", minutes % 10, shouldShowRed);
|
||||
|
||||
|
||||
SetDigitDisplay("FullSecond1Display", seconds / 10, shouldShowRed);
|
||||
SetDigitDisplay("FullSecond2Display", seconds % 10, shouldShowRed);
|
||||
|
||||
|
||||
SetColonDisplay(shouldShowRed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ParentWindow_TimerCompleted(object sender, EventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
@@ -228,7 +228,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
path.Data = geometry;
|
||||
}
|
||||
|
||||
|
||||
// 设置颜色
|
||||
if (isRed)
|
||||
{
|
||||
@@ -249,7 +249,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var colon1 = this.FindName("FullColon1Display") as TextBlock;
|
||||
var colon2 = this.FindName("FullColon2Display") as TextBlock;
|
||||
|
||||
|
||||
if (colon1 != null)
|
||||
{
|
||||
if (isRed)
|
||||
@@ -261,7 +261,7 @@ namespace Ink_Canvas.Windows
|
||||
colon1.Foreground = Brushes.White;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (colon2 != null)
|
||||
{
|
||||
if (isRed)
|
||||
@@ -301,12 +301,12 @@ namespace Ink_Canvas.Windows
|
||||
if (mainWindow != null)
|
||||
{
|
||||
mainWindow.ResumeTopmostMaintenance();
|
||||
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null && previousTimerContainerVisibility == Visibility.Visible)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
|
||||
|
||||
// 重置5秒最小化计时
|
||||
if (parentControl != null)
|
||||
{
|
||||
@@ -314,12 +314,12 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted -= ParentWindow_TimerCompleted;
|
||||
}
|
||||
|
||||
|
||||
// 清理资源
|
||||
if (updateTimer != null)
|
||||
{
|
||||
|
||||
@@ -20,103 +20,105 @@
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="20" ShadowDepth="0" Opacity="0.1"/>
|
||||
</Border.Effect>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -201,7 +202,83 @@ namespace Ink_Canvas
|
||||
private async void RollbackButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (selectedItem == null) return;
|
||||
LogHelper.WriteLogToFile($"HistoryRollback | 用户点击回滚,目标版本: {selectedItem.Version}");
|
||||
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
Title = "暂停自动更新",
|
||||
PrimaryButtonText = "确定",
|
||||
SecondaryButtonText = "取消"
|
||||
};
|
||||
|
||||
var panel = new iNKORE.UI.WPF.Modern.Controls.SimpleStackPanel
|
||||
{
|
||||
Spacing = 16,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
var textBlock = new TextBlock
|
||||
{
|
||||
Text = "请选择在回滚后多久不再接收自动更新:",
|
||||
FontSize = 14,
|
||||
Foreground = (Brush)Resources["TextPrimaryBrush"]
|
||||
};
|
||||
|
||||
var daysComboBox = new ComboBox
|
||||
{
|
||||
Width = 200,
|
||||
Height = 36,
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
for (int i = 0; i <= 7; i++)
|
||||
{
|
||||
daysComboBox.Items.Add(new ComboBoxItem
|
||||
{
|
||||
Content = $"{i} 天",
|
||||
Tag = i
|
||||
});
|
||||
}
|
||||
|
||||
daysComboBox.SelectedIndex = 0;
|
||||
|
||||
panel.Children.Add(textBlock);
|
||||
panel.Children.Add(daysComboBox);
|
||||
dialog.Content = panel;
|
||||
|
||||
var dialogResult = await dialog.ShowAsync();
|
||||
|
||||
if (dialogResult == ContentDialogResult.Primary)
|
||||
{
|
||||
int days = 1;
|
||||
if (daysComboBox.SelectedItem is ComboBoxItem selectedItemCombo &&
|
||||
selectedItemCombo.Tag != null &&
|
||||
int.TryParse(selectedItemCombo.Tag.ToString(), out int selectedDays))
|
||||
{
|
||||
days = selectedDays;
|
||||
}
|
||||
|
||||
if (days == 0)
|
||||
{
|
||||
MainWindow.Settings.Startup.AutoUpdatePauseUntilDate = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime pauseUntilDate = DateTime.Now.AddDays(days);
|
||||
MainWindow.Settings.Startup.AutoUpdatePauseUntilDate = pauseUntilDate.ToString("yyyy-MM-dd");
|
||||
LogHelper.WriteLogToFile($"HistoryRollback | 用户选择暂停自动更新 {days} 天,截止日期: {pauseUntilDate:yyyy-MM-dd}");
|
||||
}
|
||||
|
||||
MainWindow.SaveSettingsToFile();
|
||||
|
||||
LogHelper.WriteLogToFile($"HistoryRollback | 用户选择暂停自动更新 {days} 天");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("HistoryRollback | 用户取消了回滚操作");
|
||||
return;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"HistoryRollback | 用户确认回滚,目标版本: {selectedItem.Version}");
|
||||
RollbackButton.IsEnabled = false;
|
||||
VersionComboBox.IsEnabled = false;
|
||||
DownloadProgressPanel.Visibility = Visibility.Visible;
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
@@ -5,8 +7,6 @@ using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Microsoft.Win32;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
@@ -21,36 +21,36 @@ namespace Ink_Canvas.Windows
|
||||
public MinimizedTimerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
updateTimer = new System.Timers.Timer(100);
|
||||
updateTimer.Elapsed += UpdateTimer_Elapsed;
|
||||
updateTimer.Start();
|
||||
|
||||
|
||||
ApplyTheme();
|
||||
|
||||
|
||||
// 监听主题变化事件
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
|
||||
|
||||
Unloaded += MinimizedTimerControl_Unloaded;
|
||||
}
|
||||
|
||||
|
||||
private void MinimizedTimerControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 取消订阅主题变化事件
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
|
||||
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted -= ParentControl_TimerCompleted;
|
||||
}
|
||||
|
||||
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Stop();
|
||||
updateTimer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
// 当主题变化时,重新应用主题
|
||||
@@ -59,7 +59,7 @@ namespace Ink_Canvas.Windows
|
||||
RefreshTheme();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 刷新主题
|
||||
/// </summary>
|
||||
@@ -69,7 +69,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
// 重新应用主题
|
||||
ApplyTheme();
|
||||
|
||||
|
||||
// 强制刷新UI
|
||||
InvalidateVisual();
|
||||
}
|
||||
@@ -85,9 +85,9 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
parentControl.TimerCompleted -= ParentControl_TimerCompleted;
|
||||
}
|
||||
|
||||
|
||||
parentControl = parent;
|
||||
|
||||
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.TimerCompleted += ParentControl_TimerCompleted;
|
||||
@@ -105,7 +105,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (ShouldHide())
|
||||
{
|
||||
this.Visibility = Visibility.Collapsed;
|
||||
@@ -116,34 +116,34 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
UpdateTimeDisplay();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool ShouldHide()
|
||||
{
|
||||
if (parentControl == null) return true;
|
||||
|
||||
|
||||
if (parentControl.IsFullscreenWindowOpen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true)
|
||||
{
|
||||
if (parentControl.IsTimerRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (remainingTime.HasValue && remainingTime.Value.TotalSeconds < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -201,17 +201,17 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
SetDigitDisplay("MinHour1Display", Math.Abs(hours / 10) % 10, shouldShowRed);
|
||||
SetDigitDisplay("MinHour2Display", (hours % 10 + 10) % 10, shouldShowRed);
|
||||
|
||||
|
||||
SetDigitDisplay("MinMinute1Display", minutes / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinMinute2Display", minutes % 10, shouldShowRed);
|
||||
|
||||
|
||||
SetDigitDisplay("MinSecond1Display", seconds / 10, shouldShowRed);
|
||||
SetDigitDisplay("MinSecond2Display", seconds % 10, shouldShowRed);
|
||||
|
||||
|
||||
SetColonDisplay(shouldShowRed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ParentControl_TimerCompleted(object sender, EventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
@@ -231,7 +231,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
path.Data = geometry;
|
||||
}
|
||||
|
||||
|
||||
if (isRed)
|
||||
{
|
||||
path.Fill = Brushes.Red;
|
||||
@@ -256,7 +256,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var colon1 = this.FindName("MinColon1Display") as TextBlock;
|
||||
var colon2 = this.FindName("MinColon2Display") as TextBlock;
|
||||
|
||||
|
||||
if (colon1 != null)
|
||||
{
|
||||
if (isRed)
|
||||
@@ -277,7 +277,7 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (colon2 != null)
|
||||
{
|
||||
if (isRed)
|
||||
@@ -322,7 +322,7 @@ namespace Ink_Canvas.Windows
|
||||
System.Diagnostics.Debug.WriteLine($"应用主题时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ApplyTheme(Settings settings)
|
||||
{
|
||||
try
|
||||
@@ -349,7 +349,7 @@ namespace Ink_Canvas.Windows
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 刷新数字和冒号显示的颜色
|
||||
if (parentControl != null)
|
||||
{
|
||||
@@ -361,7 +361,7 @@ namespace Ink_Canvas.Windows
|
||||
System.Diagnostics.Debug.WriteLine($"应用最小化计时器窗口主题出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
@@ -432,13 +432,13 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
|
||||
private bool isDragging = false;
|
||||
private bool isDragStarted = false;
|
||||
private Point dragStartPoint;
|
||||
private Point containerStartPosition;
|
||||
private const double DragThreshold = 5.0; // 拖动阈值,像素
|
||||
|
||||
|
||||
private void MainBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ClickCount == 2)
|
||||
@@ -447,13 +447,13 @@ namespace Ink_Canvas.Windows
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.UpdateActivityTime();
|
||||
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
@@ -474,18 +474,18 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var point = e.GetPosition(minimizedContainer);
|
||||
var mainWindowPoint = minimizedContainer.TransformToAncestor(mainWindow).Transform(point);
|
||||
|
||||
|
||||
// 初始化拖动状态,但不立即开始拖动
|
||||
isDragging = false;
|
||||
isDragStarted = false;
|
||||
dragStartPoint = mainWindowPoint;
|
||||
|
||||
|
||||
var margin = minimizedContainer.Margin;
|
||||
containerStartPosition = new Point(margin.Left, margin.Top);
|
||||
|
||||
|
||||
if (double.IsNaN(containerStartPosition.X) || containerStartPosition.X < 0) containerStartPosition.X = 0;
|
||||
if (double.IsNaN(containerStartPosition.Y) || containerStartPosition.Y < 0) containerStartPosition.Y = 0;
|
||||
|
||||
|
||||
// 捕获鼠标并订阅事件,等待判断是拖动还是点击
|
||||
minimizedContainer.CaptureMouse();
|
||||
minimizedContainer.MouseMove += MinimizedContainer_MouseMove;
|
||||
@@ -495,42 +495,42 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MinimizedContainer_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer == null) return;
|
||||
|
||||
|
||||
var currentPoint = e.GetPosition(mainWindow);
|
||||
var deltaX = currentPoint.X - dragStartPoint.X;
|
||||
var deltaY = currentPoint.Y - dragStartPoint.Y;
|
||||
var distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
|
||||
// 如果移动距离超过阈值,开始拖动
|
||||
if (!isDragStarted && distance > DragThreshold)
|
||||
{
|
||||
isDragStarted = true;
|
||||
isDragging = true;
|
||||
}
|
||||
|
||||
|
||||
// 如果已经开始拖动,更新位置
|
||||
if (isDragging)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
|
||||
|
||||
var newX = containerStartPosition.X + deltaX;
|
||||
var newY = containerStartPosition.Y + deltaY;
|
||||
|
||||
|
||||
if (newX < 0) newX = 0;
|
||||
if (newY < 0) newY = 0;
|
||||
|
||||
|
||||
minimizedContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
minimizedContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
minimizedContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
|
||||
if (timerContainer != null)
|
||||
{
|
||||
timerContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
@@ -539,12 +539,12 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MinimizedContainer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer != null)
|
||||
{
|
||||
@@ -552,14 +552,14 @@ namespace Ink_Canvas.Windows
|
||||
minimizedContainer.MouseMove -= MinimizedContainer_MouseMove;
|
||||
minimizedContainer.MouseLeftButtonUp -= MinimizedContainer_MouseLeftButtonUp;
|
||||
}
|
||||
|
||||
|
||||
// 如果没有开始拖动(移动距离小于阈值),则视为单击,恢复主窗口
|
||||
if (!isDragStarted)
|
||||
{
|
||||
if (parentControl != null)
|
||||
{
|
||||
parentControl.UpdateActivityTime();
|
||||
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
@@ -568,7 +568,7 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isDragging = false;
|
||||
isDragStarted = false;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
var resources = this.Resources;
|
||||
|
||||
|
||||
if (theme == "Light")
|
||||
{
|
||||
// 应用浅色主题资源
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -10,8 +12,6 @@ using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using System.Runtime.InteropServices;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -48,7 +48,7 @@ namespace Ink_Canvas
|
||||
|
||||
InitializeUI();
|
||||
ApplyTheme(MainWindow.Settings);
|
||||
|
||||
|
||||
// 初始化点名相关变量
|
||||
InitializeRollCallData();
|
||||
}
|
||||
@@ -63,10 +63,10 @@ namespace Ink_Canvas
|
||||
|
||||
InitializeUI();
|
||||
ApplyTheme(MainWindow.Settings);
|
||||
|
||||
|
||||
// 初始化点名相关变量
|
||||
InitializeRollCallData();
|
||||
|
||||
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
if (ControlOptionsGrid != null)
|
||||
@@ -85,7 +85,7 @@ namespace Ink_Canvas
|
||||
ResetBtn.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 单次抽模式:自动开始抽选
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
@@ -108,19 +108,19 @@ namespace Ink_Canvas
|
||||
|
||||
// 保存设置
|
||||
this.settings = settings;
|
||||
|
||||
|
||||
// 设置单次抽模式
|
||||
isSingleDrawMode = isSingleDraw;
|
||||
|
||||
InitializeUI();
|
||||
ApplyTheme(settings);
|
||||
|
||||
|
||||
// 初始化设置
|
||||
InitializeSettings();
|
||||
|
||||
|
||||
// 初始化点名相关变量
|
||||
InitializeRollCallData();
|
||||
|
||||
|
||||
// 单次抽模式:禁用控制面板,阻止用户点击按钮
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
@@ -141,7 +141,7 @@ namespace Ink_Canvas
|
||||
ResetBtn.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 单次抽模式:自动开始抽选
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
@@ -165,43 +165,43 @@ namespace Ink_Canvas
|
||||
private Timer rollCallTimer;
|
||||
private Random random = new Random();
|
||||
private DateTime lastActivityTime = DateTime.Now;
|
||||
|
||||
|
||||
// 机器学习相关
|
||||
private static RollCallHistoryData historyData = null;
|
||||
private static readonly object historyLock = new object();
|
||||
private static int maxRecentHistory = 20;
|
||||
private static double avoidanceWeight = 0.8;
|
||||
private const double FREQUENCY_WEIGHT = 0.2;
|
||||
|
||||
private static double avoidanceWeight = 0.8;
|
||||
private const double FREQUENCY_WEIGHT = 0.2;
|
||||
|
||||
// 概率相关
|
||||
private const double DEFAULT_PROBABILITY = 1.0;
|
||||
private const double BASE_PROBABILITY_DECAY_FACTOR = 0.5;
|
||||
private const double MIN_PROBABILITY = 0.01;
|
||||
private const double PROBABILITY_RECOVERY_RATE = 0.2;
|
||||
private const double FREQUENCY_BOOST_FACTOR = 2.0;
|
||||
|
||||
private const double DEFAULT_PROBABILITY = 1.0;
|
||||
private const double BASE_PROBABILITY_DECAY_FACTOR = 0.5;
|
||||
private const double MIN_PROBABILITY = 0.01;
|
||||
private const double PROBABILITY_RECOVERY_RATE = 0.2;
|
||||
private const double FREQUENCY_BOOST_FACTOR = 2.0;
|
||||
|
||||
// 单次抽相关
|
||||
private bool isSingleDrawMode = false;
|
||||
private Random singleDrawRandom = new Random();
|
||||
|
||||
|
||||
// 设置相关
|
||||
private Settings settings;
|
||||
private int autoCloseWaitTime = 2500; // 自动关闭等待时间(毫秒)
|
||||
|
||||
|
||||
// 点名模式
|
||||
private string selectedRollCallMode = "Random"; // 默认随机点名
|
||||
|
||||
|
||||
// 外部点名相关
|
||||
private string selectedExternalCaller = "ClassIsland";
|
||||
|
||||
|
||||
// 开始点名按钮的数据
|
||||
private string originalStartBtnIconData = "M5 7C5 8.06087 5.42143 9.07828 6.17157 9.82843C6.92172 10.5786 7.93913 11 9 11C10.0609 11 11.0783 10.5786 11.8284 9.82843C12.5786 9.07828 13 8.06087 13 7C13 5.93913 12.5786 4.92172 11.8284 4.17157C11.0783 3.42143 10.0609 3 9 3C7.93913 3 6.92172 3.42143 6.17157 4.17157C5.42143 4.92172 5 5.93913 5 7Z M3 21V19C3 17.9391 3.42143 16.9217 4.17157 16.1716C4.92172 15.4214 5.93913 15 7 15H11C12.0609 15 13.0783 15.4214 13.8284 16.1716C14.5786 16.9217 15 17.9391 15 19V21 M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88 M21 21V19C20.9949 18.1172 20.6979 17.2608 20.1553 16.5644C19.6126 15.868 18.8548 15.3707 18 15.15";
|
||||
private string originalStartBtnText = "开始点名";
|
||||
|
||||
|
||||
// 外部点名按钮的数据
|
||||
private string externalCallerBtnIconData = "M9 15L15 9 M11 6L11.463 5.464C12.4008 4.52633 13.6727 3.9996 14.9989 3.99969C16.325 3.99979 17.5968 4.52669 18.5345 5.4645C19.4722 6.40231 19.9989 7.67419 19.9988 9.00035C19.9987 10.3265 19.4718 11.5983 18.534 12.536L18 13 M13.0001 18L12.6031 18.534C11.6544 19.4722 10.3739 19.9984 9.03964 19.9984C7.70535 19.9984 6.42489 19.4722 5.47614 18.534C5.0085 18.0716 4.63724 17.521 4.38385 16.9141C4.13047 16.3073 4 15.6561 4 14.9985C4 14.3408 4.13047 13.6897 4.38385 13.0829C4.63724 12.476 5.0085 11.9254 5.47614 11.463L6.00014 11";
|
||||
private string externalCallerBtnText = "外部点名";
|
||||
|
||||
|
||||
// JSON文件路径
|
||||
private static readonly string ConfigsFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs");
|
||||
private static readonly string RollCallHistoryJsonPath = System.IO.Path.Combine(ConfigsFolder, "RollCallHistory.json");
|
||||
@@ -211,7 +211,7 @@ namespace Ink_Canvas
|
||||
private void InitializeUI()
|
||||
{
|
||||
UpdateCountDisplay();
|
||||
LoadNamesFromFile();
|
||||
LoadNamesFromFile();
|
||||
UpdateListCountDisplay();
|
||||
LoadRollCallHistory();
|
||||
LoadSettings();
|
||||
@@ -308,7 +308,7 @@ namespace Ink_Canvas
|
||||
private void InitializeRollCallData()
|
||||
{
|
||||
// 初始化点名定时器
|
||||
rollCallTimer = new Timer(100);
|
||||
rollCallTimer = new Timer(100);
|
||||
rollCallTimer.Elapsed += RollCallTimer_Elapsed;
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 更新窗口资源
|
||||
var resources = this.Resources;
|
||||
|
||||
|
||||
if (theme == "Light")
|
||||
{
|
||||
// 应用浅色主题资源
|
||||
@@ -459,10 +459,10 @@ namespace Ink_Canvas
|
||||
private List<string> SelectNamesSequentially(List<string> availableNames, int count)
|
||||
{
|
||||
if (availableNames.Count == 0) return new List<string>();
|
||||
|
||||
|
||||
var selectedNames = new List<string>();
|
||||
int startIndex = 0;
|
||||
|
||||
|
||||
// 从历史记录中找到上次选择的位置
|
||||
if (historyData.History != null && historyData.History.Count > 0)
|
||||
{
|
||||
@@ -476,13 +476,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < count && i < availableNames.Count; i++)
|
||||
{
|
||||
int index = (startIndex + i) % availableNames.Count;
|
||||
selectedNames.Add(availableNames[index]);
|
||||
}
|
||||
|
||||
|
||||
return selectedNames;
|
||||
}
|
||||
|
||||
@@ -492,15 +492,15 @@ namespace Ink_Canvas
|
||||
private List<string> SelectNamesInGroups(List<string> availableNames, int count)
|
||||
{
|
||||
if (availableNames.Count == 0) return new List<string>();
|
||||
|
||||
|
||||
var selectedNames = new List<string>();
|
||||
int groupSize = Math.Max(1, availableNames.Count / count);
|
||||
|
||||
|
||||
for (int i = 0; i < count && i * groupSize < availableNames.Count; i++)
|
||||
{
|
||||
int startIndex = i * groupSize;
|
||||
int endIndex = Math.Min(startIndex + groupSize, availableNames.Count);
|
||||
|
||||
|
||||
// 从当前组中随机选择一个人
|
||||
var group = availableNames.GetRange(startIndex, endIndex - startIndex);
|
||||
if (group.Count > 0)
|
||||
@@ -509,7 +509,7 @@ namespace Ink_Canvas
|
||||
selectedNames.Add(group[randomIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return selectedNames;
|
||||
}
|
||||
#endregion
|
||||
@@ -537,20 +537,34 @@ namespace Ink_Canvas
|
||||
bool enableML = MainWindow.Settings?.RandSettings?.EnableMLAvoidance ?? true;
|
||||
if (!enableML)
|
||||
{
|
||||
// 如果禁用机器学习,使用简单随机选择
|
||||
// 如果禁用机器学习,使用简单不放回随机选择
|
||||
return SelectNamesRandomly(availableNames, count, random);
|
||||
}
|
||||
|
||||
var candidatePool = new List<string>(availableNames);
|
||||
var selectedNames = new List<string>();
|
||||
var remainingNames = new List<string>(availableNames);
|
||||
|
||||
for (int i = 0; i < count && remainingNames.Count > 0; i++)
|
||||
if (count >= candidatePool.Count)
|
||||
{
|
||||
string selectedName = SelectSingleNameWithML(remainingNames, selectedNames, random);
|
||||
return new List<string>(candidatePool);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count && candidatePool.Count > 0; i++)
|
||||
{
|
||||
string selectedName = SelectSingleNameWithMLWithoutReplacement(candidatePool, selectedNames, random);
|
||||
if (!string.IsNullOrEmpty(selectedName))
|
||||
{
|
||||
selectedNames.Add(selectedName);
|
||||
remainingNames.Remove(selectedName);
|
||||
candidatePool.Remove(selectedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (candidatePool.Count > 0)
|
||||
{
|
||||
int randomIndex = random.Next(0, candidatePool.Count);
|
||||
selectedName = candidatePool[randomIndex];
|
||||
selectedNames.Add(selectedName);
|
||||
candidatePool.RemoveAt(randomIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,21 +572,34 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简单随机选择点名人员
|
||||
/// 简单不放回随机选择点名人员
|
||||
/// </summary>
|
||||
private static List<string> SelectNamesRandomly(List<string> availableNames, int count, Random random)
|
||||
{
|
||||
if (availableNames == null || availableNames.Count == 0)
|
||||
return new List<string>();
|
||||
|
||||
var selectedNames = new List<string>();
|
||||
var remainingNames = new List<string>(availableNames);
|
||||
|
||||
for (int i = 0; i < count && remainingNames.Count > 0; i++)
|
||||
// 如果请求的数量大于或等于可用名单大小,返回所有名单
|
||||
if (count >= availableNames.Count)
|
||||
{
|
||||
int randomIndex = random.Next(remainingNames.Count);
|
||||
selectedNames.Add(remainingNames[randomIndex]);
|
||||
remainingNames.RemoveAt(randomIndex);
|
||||
return new List<string>(availableNames);
|
||||
}
|
||||
|
||||
var candidatePool = new List<string>(availableNames);
|
||||
var selectedNames = new List<string>();
|
||||
|
||||
for (int i = 0; i < count && candidatePool.Count > 0; i++)
|
||||
{
|
||||
int randomIndex = random.Next(0, candidatePool.Count);
|
||||
|
||||
selectedNames.Add(candidatePool[randomIndex]);
|
||||
|
||||
int lastIndex = candidatePool.Count - 1;
|
||||
if (randomIndex != lastIndex)
|
||||
{
|
||||
candidatePool[randomIndex] = candidatePool[lastIndex];
|
||||
}
|
||||
candidatePool.RemoveAt(lastIndex);
|
||||
}
|
||||
|
||||
return selectedNames;
|
||||
@@ -581,10 +608,10 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 使用概率算法选择单个人员
|
||||
/// </summary>
|
||||
private static string SelectSingleNameWithML(List<string> availableNames, List<string> alreadySelected, Random random)
|
||||
private static string SelectSingleNameWithMLWithoutReplacement(List<string> candidatePool, List<string> alreadySelected, Random random)
|
||||
{
|
||||
if (availableNames.Count == 0) return null;
|
||||
if (availableNames.Count == 1) return availableNames[0];
|
||||
if (candidatePool.Count == 0) return null;
|
||||
if (candidatePool.Count == 1) return candidatePool[0];
|
||||
|
||||
// 确保历史数据已初始化
|
||||
if (historyData == null)
|
||||
@@ -598,21 +625,60 @@ namespace Ink_Canvas
|
||||
historyData.NameProbabilities = new Dictionary<string, double>();
|
||||
}
|
||||
|
||||
// 获取每个人员的概率
|
||||
// 过滤掉已选择的人员
|
||||
var validCandidates = candidatePool.Where(name => !alreadySelected.Contains(name)).ToList();
|
||||
if (validCandidates.Count == 0) return null;
|
||||
if (validCandidates.Count == 1) return validCandidates[0];
|
||||
|
||||
// 检查极差:当极差达到3时,从被抽选次数最少的人中抽选
|
||||
if (historyData.NameFrequency != null && historyData.NameFrequency.Count > 0)
|
||||
{
|
||||
// 获取所有候选人员的被抽选次数
|
||||
var candidateFrequencies = new Dictionary<string, int>();
|
||||
foreach (string name in validCandidates)
|
||||
{
|
||||
int count = historyData.NameFrequency.ContainsKey(name) ? historyData.NameFrequency[name] : 0;
|
||||
candidateFrequencies[name] = count;
|
||||
}
|
||||
|
||||
// 计算极差(最大值 - 最小值)
|
||||
if (candidateFrequencies.Count > 0)
|
||||
{
|
||||
int maxCount = candidateFrequencies.Values.Max();
|
||||
int minCount = candidateFrequencies.Values.Min();
|
||||
int range = maxCount - minCount;
|
||||
|
||||
// 当极差达到3时,只从被抽选次数最少的人中抽选
|
||||
if (range >= 3)
|
||||
{
|
||||
var leastSelectedNames = candidateFrequencies
|
||||
.Where(kvp => kvp.Value == minCount)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
if (leastSelectedNames.Count > 0)
|
||||
{
|
||||
// 只从被抽选次数最少的人中不放回随机选择
|
||||
int randomIndex = random.Next(0, leastSelectedNames.Count);
|
||||
return leastSelectedNames[randomIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取每个候选人员的概率
|
||||
var nameProbabilities = new Dictionary<string, double>();
|
||||
|
||||
foreach (string name in availableNames)
|
||||
foreach (string name in validCandidates)
|
||||
{
|
||||
if (alreadySelected.Contains(name)) continue;
|
||||
|
||||
// 获取基础概率
|
||||
double baseProbability = GetNameProbability(name);
|
||||
|
||||
|
||||
// 根据最近历史记录调整概率
|
||||
double adjustedProbability = AdjustProbabilityByRecentHistory(name, baseProbability);
|
||||
|
||||
|
||||
double finalProbability = AdjustProbabilityByFrequency(name, adjustedProbability);
|
||||
|
||||
|
||||
nameProbabilities[name] = finalProbability;
|
||||
}
|
||||
|
||||
@@ -620,6 +686,7 @@ namespace Ink_Canvas
|
||||
return ProbabilityBasedRandomSelection(nameProbabilities, random);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取人员的概率
|
||||
/// </summary>
|
||||
@@ -653,13 +720,13 @@ namespace Ink_Canvas
|
||||
int recentCount = recentHistory.Count(n => n == name);
|
||||
|
||||
if (recentCount == 0)
|
||||
return baseProbability;
|
||||
return baseProbability;
|
||||
|
||||
double recentFrequency = (double)recentCount / Math.Min(recentHistory.Count, maxRecentHistory);
|
||||
|
||||
|
||||
double reductionFactor = 1.0 - (recentFrequency * avoidanceWeight);
|
||||
reductionFactor = Math.Max(reductionFactor, MIN_PROBABILITY / DEFAULT_PROBABILITY); // 确保不会降得太低
|
||||
|
||||
|
||||
return baseProbability * reductionFactor;
|
||||
}
|
||||
|
||||
@@ -693,20 +760,20 @@ namespace Ink_Canvas
|
||||
double frequencyRatio = nameFrequency / averageFrequency;
|
||||
|
||||
double frequencyGap = 1.0 - frequencyRatio;
|
||||
double boostFactor = FREQUENCY_BOOST_FACTOR * frequencyGap * frequencyGap;
|
||||
|
||||
double boostFactor = FREQUENCY_BOOST_FACTOR * frequencyGap * frequencyGap;
|
||||
|
||||
// 增加概率
|
||||
double boostedProbability = baseProbability * (1.0 + boostFactor);
|
||||
|
||||
|
||||
return Math.Min(boostedProbability, DEFAULT_PROBABILITY * 10.0);
|
||||
}
|
||||
else if (nameFrequency > averageFrequency)
|
||||
{
|
||||
double frequencyRatio = nameFrequency / averageFrequency;
|
||||
|
||||
double reductionFactor = 1.0 - (frequencyRatio - 1.0) * 0.3;
|
||||
|
||||
double reductionFactor = 1.0 - (frequencyRatio - 1.0) * 0.3;
|
||||
reductionFactor = Math.Max(reductionFactor, MIN_PROBABILITY / DEFAULT_PROBABILITY);
|
||||
|
||||
|
||||
return baseProbability * reductionFactor;
|
||||
}
|
||||
|
||||
@@ -743,8 +810,8 @@ namespace Ink_Canvas
|
||||
int nameCount = kvp.Value;
|
||||
|
||||
// 获取当前保存的概率(如果不存在则使用默认值)
|
||||
double currentProbability = historyData.NameProbabilities.ContainsKey(name)
|
||||
? historyData.NameProbabilities[name]
|
||||
double currentProbability = historyData.NameProbabilities.ContainsKey(name)
|
||||
? historyData.NameProbabilities[name]
|
||||
: DEFAULT_PROBABILITY;
|
||||
|
||||
// 计算该名字的选中频率
|
||||
@@ -756,24 +823,24 @@ namespace Ink_Canvas
|
||||
// 计算频率差异比例
|
||||
double frequencyRatio = nameFrequency / averageFrequency;
|
||||
double frequencyGap = 1.0 - frequencyRatio;
|
||||
double boostFactor = FREQUENCY_BOOST_FACTOR * frequencyGap * frequencyGap;
|
||||
|
||||
double boostFactor = FREQUENCY_BOOST_FACTOR * frequencyGap * frequencyGap;
|
||||
|
||||
// 增加概率
|
||||
double boostedProbability = currentProbability * (1.0 + boostFactor);
|
||||
|
||||
|
||||
// 限制最大概率,避免过高
|
||||
boostedProbability = Math.Min(boostedProbability, DEFAULT_PROBABILITY * 10.0);
|
||||
|
||||
|
||||
// 保存更新后的概率
|
||||
historyData.NameProbabilities[name] = boostedProbability;
|
||||
}
|
||||
else if (nameFrequency > averageFrequency)
|
||||
{
|
||||
double frequencyRatio = nameFrequency / averageFrequency;
|
||||
|
||||
double reductionFactor = 1.0 - (frequencyRatio - 1.0) * 0.3;
|
||||
|
||||
double reductionFactor = 1.0 - (frequencyRatio - 1.0) * 0.3;
|
||||
reductionFactor = Math.Max(reductionFactor, MIN_PROBABILITY / DEFAULT_PROBABILITY);
|
||||
|
||||
|
||||
double reducedProbability = currentProbability * reductionFactor;
|
||||
historyData.NameProbabilities[name] = reducedProbability;
|
||||
}
|
||||
@@ -839,30 +906,6 @@ namespace Ink_Canvas
|
||||
return 1.0 - frequency;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加权随机选择(保留用于兼容,实际已改用概率选择)
|
||||
/// </summary>
|
||||
private static string WeightedRandomSelection(Dictionary<string, double> nameWeights, Random random)
|
||||
{
|
||||
if (nameWeights.Count == 0) return null;
|
||||
|
||||
double totalWeight = nameWeights.Values.Sum();
|
||||
if (totalWeight <= 0) return nameWeights.Keys.First();
|
||||
|
||||
double randomValue = random.NextDouble() * totalWeight;
|
||||
double currentWeight = 0;
|
||||
|
||||
foreach (var kvp in nameWeights)
|
||||
{
|
||||
currentWeight += kvp.Value;
|
||||
if (randomValue <= currentWeight)
|
||||
{
|
||||
return kvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return nameWeights.Keys.Last();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新点名历史记录
|
||||
@@ -924,19 +967,19 @@ namespace Ink_Canvas
|
||||
{
|
||||
double nameFrequency = (double)historyData.NameFrequency[name] / totalSelections;
|
||||
double averageFrequency = 1.0 / uniqueNamesCount;
|
||||
|
||||
|
||||
if (nameFrequency > averageFrequency)
|
||||
{
|
||||
double frequencyRatio = nameFrequency / averageFrequency;
|
||||
frequencyBasedDecay = 1.0 - (frequencyRatio - 1.0) * 0.2;
|
||||
frequencyBasedDecay = 1.0 - (frequencyRatio - 1.0) * 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double decayFactor = BASE_PROBABILITY_DECAY_FACTOR * (1.0 + avoidanceWeight) * frequencyBasedDecay;
|
||||
decayFactor = Math.Min(decayFactor, 0.85);
|
||||
|
||||
decayFactor = Math.Min(decayFactor, 0.85);
|
||||
|
||||
double newProbability = currentProbability * decayFactor;
|
||||
newProbability = Math.Max(newProbability, MIN_PROBABILITY); // 确保不低于最小概率
|
||||
historyData.NameProbabilities[name] = newProbability;
|
||||
@@ -1081,7 +1124,7 @@ namespace Ink_Canvas
|
||||
MainResultDisplay.Text = "";
|
||||
MainResultDisplay.Visibility = Visibility.Collapsed;
|
||||
MultiResultScrollViewer.Visibility = Visibility.Visible;
|
||||
|
||||
|
||||
// 显示所有结果(最多20个)
|
||||
Result1Display.Text = results.Count > 0 ? results[0] : "";
|
||||
Result2Display.Text = results.Count > 1 ? results[1] : "";
|
||||
@@ -1111,10 +1154,10 @@ namespace Ink_Canvas
|
||||
private void CountPlus_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (isRollCalling) return;
|
||||
|
||||
|
||||
// 获取老点名UI的设置
|
||||
int maxPeopleLimit = settings?.RandSettings?.RandWindowOnceMaxStudents ?? 10;
|
||||
|
||||
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
// 单次抽模式:最多选择60个数字,但受设置限制
|
||||
@@ -1137,7 +1180,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
currentCount = Math.Min(currentCount + 1, maxCount);
|
||||
}
|
||||
|
||||
|
||||
UpdateCountDisplay();
|
||||
}
|
||||
|
||||
@@ -1155,7 +1198,7 @@ namespace Ink_Canvas
|
||||
// 打开名单导入窗口,与老点名UI保持一致
|
||||
var namesInputWindow = new NamesInputWindow();
|
||||
namesInputWindow.ShowDialog();
|
||||
|
||||
|
||||
// 重新加载名单
|
||||
LoadNamesFromFile();
|
||||
UpdateListCountDisplay();
|
||||
@@ -1210,9 +1253,29 @@ namespace Ink_Canvas
|
||||
|
||||
private void ClearList_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
nameList.Clear();
|
||||
UpdateListCountDisplay();
|
||||
UpdateStatusDisplay("名单已清空");
|
||||
try
|
||||
{
|
||||
// 清空名单
|
||||
nameList.Clear();
|
||||
UpdateListCountDisplay();
|
||||
|
||||
// 清空点名历史记录
|
||||
lock (historyLock)
|
||||
{
|
||||
// 重置历史记录数据
|
||||
historyData = new RollCallHistoryData();
|
||||
|
||||
// 保存到文件
|
||||
SaveRollCallHistory();
|
||||
}
|
||||
|
||||
UpdateStatusDisplay("名单和历史记录已清空");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"清空名单和历史记录失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
LogHelper.WriteLogToFile($"清空名单和历史记录失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetModeSelection(string mode)
|
||||
@@ -1221,7 +1284,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 存储选择的模式
|
||||
selectedRollCallMode = mode;
|
||||
|
||||
|
||||
// 重置所有按钮状态
|
||||
RandomModeText.FontWeight = FontWeights.Normal;
|
||||
RandomModeText.Opacity = 0.6;
|
||||
@@ -1232,7 +1295,7 @@ namespace Ink_Canvas
|
||||
GroupModeText.FontWeight = FontWeights.Normal;
|
||||
GroupModeText.Opacity = 0.6;
|
||||
GroupModeText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102));
|
||||
|
||||
|
||||
// 重置外部点名模式按钮状态
|
||||
ExternalCallerModeText.FontWeight = FontWeights.Normal;
|
||||
ExternalCallerModeText.Opacity = 0.6;
|
||||
@@ -1249,13 +1312,13 @@ namespace Ink_Canvas
|
||||
RandomModeText.Foreground = new SolidColorBrush(Colors.White);
|
||||
SegmentedIndicator.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
SegmentedIndicator.CornerRadius = new CornerRadius(7.5, 0, 0, 7.5);
|
||||
|
||||
|
||||
// 添加动画效果
|
||||
var randomAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
|
||||
new Thickness(0, 0, 0, 0),
|
||||
TimeSpan.FromMilliseconds(200));
|
||||
SegmentedIndicator.BeginAnimation(Border.MarginProperty, randomAnimation);
|
||||
|
||||
|
||||
// 恢复开始点名按钮的原始图标和文字
|
||||
RestoreStartRollCallButton();
|
||||
UpdateStatusDisplay("已选择点名模式: 随机点名");
|
||||
@@ -1266,13 +1329,13 @@ namespace Ink_Canvas
|
||||
SequentialModeText.Foreground = new SolidColorBrush(Colors.White);
|
||||
SegmentedIndicator.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
SegmentedIndicator.CornerRadius = new CornerRadius(0, 0, 0, 0);
|
||||
|
||||
|
||||
// 添加动画效果 - 移动到中间位置
|
||||
var sequentialAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
|
||||
new Thickness(100, 0, 0, 0),
|
||||
TimeSpan.FromMilliseconds(200));
|
||||
SegmentedIndicator.BeginAnimation(Border.MarginProperty, sequentialAnimation);
|
||||
|
||||
|
||||
// 恢复开始点名按钮的原始图标和文字
|
||||
RestoreStartRollCallButton();
|
||||
UpdateStatusDisplay("已选择点名模式: 顺序点名");
|
||||
@@ -1283,13 +1346,13 @@ namespace Ink_Canvas
|
||||
GroupModeText.Foreground = new SolidColorBrush(Colors.White);
|
||||
SegmentedIndicator.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
SegmentedIndicator.CornerRadius = new CornerRadius(0, 7.5, 7.5, 0);
|
||||
|
||||
|
||||
// 添加动画效果 - 移动到右侧位置
|
||||
var groupAnimation = new System.Windows.Media.Animation.ThicknessAnimation(
|
||||
new Thickness(200, 0, 0, 0),
|
||||
TimeSpan.FromMilliseconds(200));
|
||||
SegmentedIndicator.BeginAnimation(Border.MarginProperty, groupAnimation);
|
||||
|
||||
|
||||
// 恢复开始点名按钮的原始图标和文字
|
||||
RestoreStartRollCallButton();
|
||||
UpdateStatusDisplay("已选择点名模式: 分组点名");
|
||||
@@ -1300,10 +1363,10 @@ namespace Ink_Canvas
|
||||
ExternalCallerModeText.Opacity = 1.0;
|
||||
ExternalCallerModeText.Foreground = new SolidColorBrush(Colors.White);
|
||||
ExternalCallerModeIndicator.Visibility = Visibility.Visible;
|
||||
|
||||
|
||||
// 隐藏其他模式的指示器
|
||||
SegmentedIndicator.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
||||
// 切换到外部点名按钮的图标和文字
|
||||
UpdateStartRollCallButtonForExternal();
|
||||
UpdateStatusDisplay($"已选择点名模式: 外部点名 ({selectedExternalCaller})");
|
||||
@@ -1315,7 +1378,7 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"设置点名模式选择时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 更新开始点名按钮为外部点名样式
|
||||
/// </summary>
|
||||
@@ -1330,14 +1393,14 @@ namespace Ink_Canvas
|
||||
// 外部点名使用按钮前景色而不是主按钮前景色
|
||||
StartRollCallBtnIcon.Stroke = (Brush)FindResource("NewRollCallWindowButtonForeground");
|
||||
}
|
||||
|
||||
|
||||
// 更新文字
|
||||
if (StartRollCallBtnText != null)
|
||||
{
|
||||
StartRollCallBtnText.Text = externalCallerBtnText;
|
||||
StartRollCallBtnText.Foreground = (Brush)FindResource("NewRollCallWindowButtonForeground");
|
||||
}
|
||||
|
||||
|
||||
// 更新按钮背景色为普通按钮背景
|
||||
StartRollCallBtn.Background = (Brush)FindResource("NewRollCallWindowButtonBackground");
|
||||
}
|
||||
@@ -1346,7 +1409,7 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"更新开始点名按钮为外部点名样式时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 恢复开始点名按钮的原始样式
|
||||
/// </summary>
|
||||
@@ -1360,14 +1423,14 @@ namespace Ink_Canvas
|
||||
StartRollCallBtnIcon.Data = Geometry.Parse(originalStartBtnIconData);
|
||||
StartRollCallBtnIcon.Stroke = (Brush)FindResource("NewRollCallWindowPrimaryButtonForeground");
|
||||
}
|
||||
|
||||
|
||||
// 恢复文字
|
||||
if (StartRollCallBtnText != null)
|
||||
{
|
||||
StartRollCallBtnText.Text = originalStartBtnText;
|
||||
StartRollCallBtnText.Foreground = (Brush)FindResource("NewRollCallWindowPrimaryButtonForeground");
|
||||
}
|
||||
|
||||
|
||||
// 恢复按钮背景色为主按钮背景
|
||||
StartRollCallBtn.Background = (Brush)FindResource("NewRollCallWindowPrimaryButtonBackground");
|
||||
}
|
||||
@@ -1404,7 +1467,7 @@ namespace Ink_Canvas
|
||||
if (ExternalCallerTypeComboBox.SelectedItem is ComboBoxItem selectedItem)
|
||||
{
|
||||
selectedExternalCaller = selectedItem.Content.ToString();
|
||||
|
||||
|
||||
if (selectedRollCallMode == "External")
|
||||
{
|
||||
UpdateStatusDisplay($"已选择外部点名: {selectedExternalCaller}");
|
||||
@@ -1417,7 +1480,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private static bool isExternalCallerFirstClick = true;
|
||||
private static bool isExternalCallerFirstClick = true;
|
||||
|
||||
private void ExternalCaller_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -1474,7 +1537,7 @@ namespace Ink_Canvas
|
||||
ExternalCaller_Click(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
// 单次抽模式:直接开始抽选
|
||||
@@ -1537,18 +1600,18 @@ namespace Ink_Canvas
|
||||
{
|
||||
const int animationTimes = 100; // 动画次数
|
||||
const int sleepTime = 5; // 每次动画间隔(毫秒)
|
||||
|
||||
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
List<string> usedNames = new List<string>();
|
||||
|
||||
|
||||
// 确保动画期间主显示区域可见
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainResultDisplay.Visibility = Visibility.Visible;
|
||||
MultiResultScrollViewer.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
|
||||
|
||||
for (int i = 0; i < animationTimes; i++)
|
||||
{
|
||||
// 随机选择一个名字进行动画显示
|
||||
@@ -1556,7 +1619,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
int randomIndex = new Random().Next(0, nameList.Count);
|
||||
string displayName = nameList[randomIndex];
|
||||
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 确保主显示区域在动画期间保持可见
|
||||
@@ -1564,16 +1627,16 @@ namespace Ink_Canvas
|
||||
MainResultDisplay.Text = displayName;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
System.Threading.Thread.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
|
||||
// 动画结束,显示最终结果
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 根据选择的模式进行不同的点名逻辑
|
||||
var selectedNames = SelectNamesByMode(nameList, currentCount);
|
||||
|
||||
|
||||
// 更新历史记录
|
||||
UpdateRollCallHistory(selectedNames);
|
||||
|
||||
@@ -1607,40 +1670,40 @@ namespace Ink_Canvas
|
||||
{
|
||||
const int animationTimes = 100; // 动画次数
|
||||
const int sleepTime = 5; // 每次动画间隔(毫秒)
|
||||
|
||||
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
List<int> usedNumbers = new List<int>();
|
||||
|
||||
|
||||
// 确保动画期间主显示区域可见
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainResultDisplay.Visibility = Visibility.Visible;
|
||||
MultiResultScrollViewer.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
|
||||
|
||||
for (int i = 0; i < animationTimes; i++)
|
||||
{
|
||||
// 随机选择一个数字进行动画显示
|
||||
int randomNumber = new Random().Next(1, 61); // 1-60
|
||||
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 确保主显示区域在动画期间保持可见
|
||||
MainResultDisplay.Visibility = Visibility.Visible;
|
||||
MainResultDisplay.Text = randomNumber.ToString();
|
||||
});
|
||||
|
||||
|
||||
System.Threading.Thread.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
|
||||
// 动画结束,显示最终结果
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 根据选择的模式进行不同的抽选逻辑
|
||||
var numberList = Enumerable.Range(1, 60).Select(n => n.ToString()).ToList();
|
||||
var selectedNumbers = SelectNamesByMode(numberList, currentCount);
|
||||
|
||||
|
||||
// 更新历史记录
|
||||
UpdateRollCallHistory(selectedNumbers);
|
||||
|
||||
@@ -1685,7 +1748,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
const int animationTimes = 100; // 动画次数
|
||||
const int sleepTime = 5; // 每次动画间隔(毫秒),参考老点名窗口
|
||||
|
||||
|
||||
new System.Threading.Thread(() =>
|
||||
{
|
||||
if (nameList.Count > 0)
|
||||
@@ -1707,7 +1770,7 @@ namespace Ink_Canvas
|
||||
private void StartSingleDrawNameAnimation(int animationTimes, int sleepTime)
|
||||
{
|
||||
List<string> usedNames = new List<string>();
|
||||
|
||||
|
||||
for (int i = 0; i < animationTimes; i++)
|
||||
{
|
||||
// 随机选择一个名字进行动画显示,避免立即重复
|
||||
@@ -1716,23 +1779,23 @@ namespace Ink_Canvas
|
||||
{
|
||||
randomName = nameList[singleDrawRandom.Next(0, nameList.Count)];
|
||||
} while (usedNames.Count > 0 && usedNames[usedNames.Count - 1] == randomName);
|
||||
|
||||
|
||||
usedNames.Add(randomName);
|
||||
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainResultDisplay.Text = randomName;
|
||||
});
|
||||
|
||||
|
||||
System.Threading.Thread.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
|
||||
// 动画结束,显示最终结果
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 根据选择的模式进行不同的抽选逻辑
|
||||
var selectedNames = SelectNamesByMode(nameList, currentCount);
|
||||
|
||||
|
||||
// 更新历史记录
|
||||
UpdateRollCallHistory(selectedNames);
|
||||
|
||||
@@ -1744,7 +1807,7 @@ namespace Ink_Canvas
|
||||
isRollCalling = false;
|
||||
StartRollCallBtn.Visibility = Visibility.Visible;
|
||||
StopRollCallBtn.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
new System.Threading.Thread(() =>
|
||||
@@ -1780,7 +1843,7 @@ namespace Ink_Canvas
|
||||
private void StartSingleDrawNumberAnimation(int animationTimes, int sleepTime)
|
||||
{
|
||||
List<int> usedNumbers = new List<int>();
|
||||
|
||||
|
||||
for (int i = 0; i < animationTimes; i++)
|
||||
{
|
||||
// 随机选择一个数字进行动画显示,避免立即重复
|
||||
@@ -1789,27 +1852,27 @@ namespace Ink_Canvas
|
||||
{
|
||||
randomNumber = singleDrawRandom.Next(1, 61); // 1-60
|
||||
} while (usedNumbers.Count > 0 && usedNumbers[usedNumbers.Count - 1] == randomNumber);
|
||||
|
||||
|
||||
usedNumbers.Add(randomNumber);
|
||||
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
MainResultDisplay.Text = randomNumber.ToString();
|
||||
});
|
||||
|
||||
|
||||
System.Threading.Thread.Sleep(sleepTime);
|
||||
}
|
||||
|
||||
|
||||
// 动画结束,显示最终结果
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 根据选择的模式进行不同的抽选逻辑
|
||||
var numberList = Enumerable.Range(1, 60).Select(n => n.ToString()).ToList();
|
||||
var selectedNumbers = SelectNamesByMode(numberList, currentCount);
|
||||
|
||||
|
||||
// 更新历史记录
|
||||
UpdateRollCallHistory(selectedNumbers);
|
||||
|
||||
|
||||
if (selectedNumbers.Count == 1)
|
||||
{
|
||||
MainResultDisplay.Text = selectedNumbers[0];
|
||||
@@ -1819,19 +1882,19 @@ namespace Ink_Canvas
|
||||
{
|
||||
MainResultDisplay.Text = "抽选结果";
|
||||
MultiResultPanel.Visibility = Visibility.Visible;
|
||||
|
||||
|
||||
Result1Display.Text = selectedNumbers.Count > 0 ? selectedNumbers[0] : "";
|
||||
Result2Display.Text = selectedNumbers.Count > 1 ? selectedNumbers[1] : "";
|
||||
Result3Display.Text = selectedNumbers.Count > 2 ? selectedNumbers[2] : "";
|
||||
|
||||
|
||||
UpdateStatusDisplay($"抽选完成,共选择 {selectedNumbers.Count} 个数字");
|
||||
}
|
||||
|
||||
|
||||
// 停止点名状态
|
||||
isRollCalling = false;
|
||||
StartRollCallBtn.Visibility = Visibility.Visible;
|
||||
StopRollCallBtn.Visibility = Visibility.Collapsed;
|
||||
|
||||
|
||||
if (isSingleDrawMode)
|
||||
{
|
||||
new System.Threading.Thread(() =>
|
||||
@@ -1869,21 +1932,21 @@ namespace Ink_Canvas
|
||||
{
|
||||
var selectedNumbers = new List<string>();
|
||||
var usedNumbers = new List<int>();
|
||||
|
||||
|
||||
for (int i = 0; i < count && usedNumbers.Count < 60; i++)
|
||||
{
|
||||
int randomNumber = singleDrawRandom.Next(1, 61); // 1-60
|
||||
|
||||
|
||||
// 避免重复选择
|
||||
while (usedNumbers.Contains(randomNumber))
|
||||
{
|
||||
randomNumber = singleDrawRandom.Next(1, 61);
|
||||
}
|
||||
|
||||
|
||||
usedNumbers.Add(randomNumber);
|
||||
selectedNumbers.Add(randomNumber.ToString());
|
||||
}
|
||||
|
||||
|
||||
return selectedNumbers;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,12 @@
|
||||
<!-- 结果显示区域 -->
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<!-- 主结果显示 -->
|
||||
<TextBlock x:Name="MainResultDisplay" Text="点击开始点名" FontSize="48" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center" Margin="0,0,0,20"/>
|
||||
<Viewbox Stretch="Uniform" MaxWidth="460" MaxHeight="120" Margin="0,0,0,20">
|
||||
<TextBlock x:Name="MainResultDisplay" Text="点击开始点名" FontSize="48" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource NewRollCallWindowDigitForeground}"
|
||||
TextAlignment="Center"/>
|
||||
</Viewbox>
|
||||
|
||||
<!-- 多结果显示区域 - 支持最多20个结果 -->
|
||||
<ScrollViewer x:Name="MultiResultScrollViewer" MaxHeight="200" VerticalScrollBarVisibility="Auto"
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.PPTTimeCapsule"
|
||||
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:Converter="clr-namespace:Ink_Canvas.Converter"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="60" d:DesignWidth="200">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="DigitResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- 小时滚动动画:新时间从上方滚入 -->
|
||||
<Storyboard x:Key="HourScrollAnimation" x:Name="HourScrollStoryboard">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HourContentTransform"
|
||||
Storyboard.TargetProperty="(TranslateTransform.Y)">
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="-40">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00.25" Value="0">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="HourPanel"
|
||||
Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00.25" Value="1">
|
||||
</EasingDoubleKeyFrame>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
|
||||
<!-- 分钟滚动动画:新时间从上方滚入 -->
|
||||
<Storyboard x:Key="MinuteScrollAnimation" x:Name="MinuteScrollStoryboard">
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="MinuteContentTransform"
|
||||
Storyboard.TargetProperty="(TranslateTransform.Y)">
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="-40">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00.25" Value="0">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="MinutePanel"
|
||||
Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0">
|
||||
<EasingDoubleKeyFrame.EasingFunction>
|
||||
<ExponentialEase EasingMode="EaseOut"/>
|
||||
</EasingDoubleKeyFrame.EasingFunction>
|
||||
</EasingDoubleKeyFrame>
|
||||
<EasingDoubleKeyFrame KeyTime="00:00:00.25" Value="1">
|
||||
</EasingDoubleKeyFrame>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
|
||||
<!-- 胶囊伸长动画 -->
|
||||
<Storyboard x:Key="CapsuleExpandAnimation" x:Name="CapsuleExpandStoryboard">
|
||||
<DoubleAnimation Storyboard.TargetName="MainCapsule"
|
||||
Storyboard.TargetProperty="(FrameworkElement.Width)"
|
||||
Duration="0:0:0.3">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<CubicEase EasingMode="EaseOut"/>
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
|
||||
<!-- 胶囊缩短动画 -->
|
||||
<Storyboard x:Key="CapsuleShrinkAnimation" x:Name="CapsuleShrinkStoryboard">
|
||||
<DoubleAnimation Storyboard.TargetName="MainCapsule"
|
||||
Storyboard.TargetProperty="(FrameworkElement.Width)"
|
||||
Duration="0:0:0.3">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<CubicEase EasingMode="EaseIn"/>
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
|
||||
<!-- 冒号闪动动画:1秒完成一次完整闪动 -->
|
||||
<Storyboard x:Key="ColonBlinkAnimation" x:Name="ColonBlinkStoryboard" RepeatBehavior="Forever">
|
||||
<DoubleAnimation Storyboard.TargetName="ColonDisplay"
|
||||
Storyboard.TargetProperty="(UIElement.Opacity)"
|
||||
From="1.0"
|
||||
To="0.3"
|
||||
Duration="0:0:0.5"
|
||||
AutoReverse="True">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<SineEase EasingMode="EaseInOut"/>
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<!-- 主胶囊容器 -->
|
||||
<Border x:Name="MainCapsule"
|
||||
CornerRadius="8"
|
||||
Padding="16,8"
|
||||
Width="120"
|
||||
Cursor="Hand"
|
||||
MouseLeftButtonDown="MainCapsule_MouseLeftButtonDown">
|
||||
<Border.Background>
|
||||
<SolidColorBrush x:Name="CapsuleBackgroundBrush" Color="#CCFFFFFF"/>
|
||||
</Border.Background>
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="8" ShadowDepth="0" Opacity="0.2" Color="Black"/>
|
||||
</Border.Effect>
|
||||
|
||||
<StackPanel x:Name="ContentPanel" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<!-- 时间显示区域 -->
|
||||
<StackPanel x:Name="TimeDisplayPanel" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<!-- 小时部分 -->
|
||||
<Border x:Name="HourContainer" ClipToBounds="True" Height="18" VerticalAlignment="Center">
|
||||
<StackPanel x:Name="HourPanel" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<StackPanel.RenderTransform>
|
||||
<TranslateTransform x:Name="HourContentTransform"/>
|
||||
</StackPanel.RenderTransform>
|
||||
<Path x:Name="Hour1Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="#FF000000"
|
||||
Width="14" Height="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,2,0"/>
|
||||
<Path x:Name="Hour2Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="#FF000000"
|
||||
Width="14" Height="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,2,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 冒号 -->
|
||||
<TextBlock x:Name="ColonDisplay" Text=":" FontSize="16" FontWeight="Bold"
|
||||
Foreground="#FF000000" VerticalAlignment="Center" Margin="0,0,2,0"
|
||||
LineHeight="16" LineStackingStrategy="BlockLineHeight"/>
|
||||
|
||||
<!-- 分钟部分 -->
|
||||
<Border x:Name="MinuteContainer" ClipToBounds="True" Height="18" VerticalAlignment="Center">
|
||||
<StackPanel x:Name="MinutePanel" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<StackPanel.RenderTransform>
|
||||
<TranslateTransform x:Name="MinuteContentTransform"/>
|
||||
</StackPanel.RenderTransform>
|
||||
<Path x:Name="Minute1Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="#FF000000"
|
||||
Width="14" Height="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,2,0"/>
|
||||
<Path x:Name="Minute2Display"
|
||||
Data="{StaticResource Digit0}"
|
||||
Fill="#FF000000"
|
||||
Width="14" Height="18"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 倒计时显示区域(动态显示) -->
|
||||
<StackPanel x:Name="CountdownPanel" Orientation="Horizontal" VerticalAlignment="Center"
|
||||
Visibility="Collapsed" Margin="8,0,0,0">
|
||||
<TextBlock x:Name="CountdownText" Text="00:00" FontSize="20" FontWeight="Bold"
|
||||
Foreground="#80000000" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,670 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT时间显示胶囊控件
|
||||
/// </summary>
|
||||
public partial class PPTTimeCapsule : UserControl
|
||||
{
|
||||
private System.Timers.Timer timeUpdateTimer;
|
||||
private System.Timers.Timer countdownUpdateTimer; // 倒计时更新定时器(参考MinimizedTimerControl)
|
||||
private DateTime lastTime = DateTime.MinValue;
|
||||
private TimerControl parentControl; // 父计时器控件引用(参考MinimizedTimerControl)
|
||||
private bool wasTimerRunning = false; // 上次检查时计时器是否运行
|
||||
private bool isOvertime = false;
|
||||
private Storyboard capsuleExpandStoryboard;
|
||||
private Storyboard capsuleShrinkStoryboard;
|
||||
private Storyboard colonBlinkStoryboard;
|
||||
private double originalCapsuleWidth = 0;
|
||||
|
||||
public PPTTimeCapsule()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeTimers();
|
||||
ApplyTheme();
|
||||
|
||||
// 监听主题变化
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
|
||||
Loaded += PPTTimeCapsule_Loaded;
|
||||
Unloaded += PPTTimeCapsule_Unloaded;
|
||||
IsVisibleChanged += PPTTimeCapsule_IsVisibleChanged;
|
||||
}
|
||||
|
||||
private void PPTTimeCapsule_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
|
||||
private void PPTTimeCapsule_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 记录初始宽度
|
||||
if (MainCapsule != null && originalCapsuleWidth == 0)
|
||||
{
|
||||
originalCapsuleWidth = MainCapsule.ActualWidth > 0 ? MainCapsule.ActualWidth : 120;
|
||||
}
|
||||
UpdateTimeDisplay();
|
||||
StartTimeUpdate();
|
||||
}
|
||||
|
||||
private void PPTTimeCapsule_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
StopTimeUpdate();
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
|
||||
if (countdownUpdateTimer != null)
|
||||
{
|
||||
countdownUpdateTimer.Stop();
|
||||
countdownUpdateTimer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeTimers()
|
||||
{
|
||||
// 时间更新定时器(每秒更新)
|
||||
timeUpdateTimer = new System.Timers.Timer(1000);
|
||||
timeUpdateTimer.Elapsed += TimeUpdateTimer_Elapsed;
|
||||
|
||||
// 倒计时更新定时器
|
||||
countdownUpdateTimer = new System.Timers.Timer(100);
|
||||
countdownUpdateTimer.Elapsed += CountdownUpdateTimer_Elapsed;
|
||||
}
|
||||
|
||||
private void TimeUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
UpdateTimeDisplay();
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
|
||||
private void CountdownUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (this.Visibility != Visibility.Visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateCountdownDisplay();
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
|
||||
private void StartTimeUpdate()
|
||||
{
|
||||
if (timeUpdateTimer != null && !timeUpdateTimer.Enabled)
|
||||
{
|
||||
timeUpdateTimer.Start();
|
||||
}
|
||||
if (countdownUpdateTimer != null && !countdownUpdateTimer.Enabled)
|
||||
{
|
||||
countdownUpdateTimer.Start();
|
||||
}
|
||||
// 启动冒号闪动动画
|
||||
StartColonBlinkAnimation();
|
||||
}
|
||||
|
||||
private void StopTimeUpdate()
|
||||
{
|
||||
if (timeUpdateTimer != null)
|
||||
{
|
||||
timeUpdateTimer.Stop();
|
||||
}
|
||||
if (countdownUpdateTimer != null)
|
||||
{
|
||||
countdownUpdateTimer.Stop();
|
||||
}
|
||||
// 停止冒号闪动动画
|
||||
StopColonBlinkAnimation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置父计时器控件
|
||||
/// </summary>
|
||||
public void SetParentControl(TimerControl parent)
|
||||
{
|
||||
parentControl = parent;
|
||||
if (parentControl != null)
|
||||
{
|
||||
UpdateCountdownDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimeDisplay()
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
|
||||
// 检查小时是否改变
|
||||
if (lastTime != DateTime.MinValue && lastTime.Hour != now.Hour)
|
||||
{
|
||||
// 先更新数字内容
|
||||
SetDigitDisplay("Hour1Display", now.Hour / 10);
|
||||
SetDigitDisplay("Hour2Display", now.Hour % 10);
|
||||
|
||||
// 重置Transform位置到上方
|
||||
HourContentTransform.Y = -40;
|
||||
HourPanel.Opacity = 0;
|
||||
|
||||
// 播放小时滚动动画:从上方滚入
|
||||
PlayHourScrollAnimation();
|
||||
}
|
||||
else if (lastTime == DateTime.MinValue)
|
||||
{
|
||||
// 首次加载,直接更新显示
|
||||
SetDigitDisplay("Hour1Display", now.Hour / 10);
|
||||
SetDigitDisplay("Hour2Display", now.Hour % 10);
|
||||
}
|
||||
|
||||
// 检查分钟是否改变
|
||||
if (lastTime != DateTime.MinValue && lastTime.Minute != now.Minute)
|
||||
{
|
||||
// 先更新数字内容
|
||||
SetDigitDisplay("Minute1Display", now.Minute / 10);
|
||||
SetDigitDisplay("Minute2Display", now.Minute % 10);
|
||||
|
||||
// 重置Transform位置到上方
|
||||
MinuteContentTransform.Y = -40;
|
||||
MinutePanel.Opacity = 0;
|
||||
|
||||
// 播放分钟滚动动画:从上方滚入
|
||||
PlayMinuteScrollAnimation();
|
||||
}
|
||||
else if (lastTime == DateTime.MinValue)
|
||||
{
|
||||
// 首次加载,直接更新显示
|
||||
SetDigitDisplay("Minute1Display", now.Minute / 10);
|
||||
SetDigitDisplay("Minute2Display", now.Minute % 10);
|
||||
}
|
||||
|
||||
lastTime = now;
|
||||
}
|
||||
|
||||
private void PlayHourScrollAnimation()
|
||||
{
|
||||
// 新时间从上方滚入(-25到0)
|
||||
var scrollAnimation = (Storyboard)Resources["HourScrollAnimation"];
|
||||
scrollAnimation.Begin();
|
||||
}
|
||||
|
||||
private void PlayMinuteScrollAnimation()
|
||||
{
|
||||
// 新时间从上方滚入(-25到0)
|
||||
var scrollAnimation = (Storyboard)Resources["MinuteScrollAnimation"];
|
||||
scrollAnimation.Begin();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据数字值设置SVG数字显示
|
||||
/// </summary>
|
||||
/// <param name="pathName">Path控件的名称</param>
|
||||
/// <param name="digit">要显示的数字(0-9)</param>
|
||||
private void SetDigitDisplay(string pathName, int digit)
|
||||
{
|
||||
var path = this.FindName(pathName) as System.Windows.Shapes.Path;
|
||||
if (path != null)
|
||||
{
|
||||
digit = Math.Max(0, Math.Min(9, digit));
|
||||
|
||||
string resourceKey = $"Digit{digit}";
|
||||
var geometry = this.FindResource(resourceKey) as Geometry;
|
||||
if (geometry != null)
|
||||
{
|
||||
path.Data = geometry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置SVG数字的填充颜色
|
||||
/// </summary>
|
||||
/// <param name="pathName">Path控件的名称</param>
|
||||
/// <param name="color">填充颜色</param>
|
||||
private void SetDigitFill(string pathName, Color color)
|
||||
{
|
||||
var path = this.FindName(pathName) as System.Windows.Shapes.Path;
|
||||
if (path != null)
|
||||
{
|
||||
path.Fill = new SolidColorBrush(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartColonBlinkAnimation()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (colonBlinkStoryboard == null)
|
||||
{
|
||||
colonBlinkStoryboard = (Storyboard)Resources["ColonBlinkAnimation"];
|
||||
}
|
||||
if (colonBlinkStoryboard != null)
|
||||
{
|
||||
colonBlinkStoryboard.Begin(ColonDisplay, true); // true表示HandoffBehavior.SnapshotAndReplace
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动冒号闪动动画失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StopColonBlinkAnimation()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (colonBlinkStoryboard != null)
|
||||
{
|
||||
colonBlinkStoryboard.Stop(ColonDisplay);
|
||||
// 恢复冒号透明度
|
||||
if (ColonDisplay != null)
|
||||
{
|
||||
ColonDisplay.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"停止冒号闪动动画失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 停止倒计时
|
||||
/// </summary>
|
||||
public void StopCountdown()
|
||||
{
|
||||
bool wasRunning = wasTimerRunning;
|
||||
wasTimerRunning = false;
|
||||
CountdownPanel.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 重置超时状态
|
||||
isOvertime = false;
|
||||
// 根据主题恢复倒计时文本颜色
|
||||
ApplyTheme();
|
||||
|
||||
// 播放胶囊缩短动画
|
||||
if (wasRunning)
|
||||
{
|
||||
PlayCapsuleShrinkAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCountdownDisplay()
|
||||
{
|
||||
if (parentControl == null) return;
|
||||
|
||||
// 检查计时器是否正在运行(参考MinimizedTimerControl)
|
||||
bool isRunning = parentControl.IsTimerRunning;
|
||||
|
||||
// 如果状态改变,更新UI
|
||||
if (isRunning != wasTimerRunning)
|
||||
{
|
||||
wasTimerRunning = isRunning;
|
||||
if (isRunning)
|
||||
{
|
||||
// 计时器开始运行,显示倒计时面板并播放伸长动画
|
||||
CountdownPanel.Visibility = Visibility.Visible;
|
||||
// 确保倒计时文本使用主题颜色
|
||||
ApplyTheme();
|
||||
PlayCapsuleExpandAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 计时器停止,隐藏倒计时面板并播放缩短动画
|
||||
CountdownPanel.Visibility = Visibility.Collapsed;
|
||||
PlayCapsuleShrinkAnimation();
|
||||
isOvertime = false;
|
||||
// 根据主题恢复倒计时文本颜色
|
||||
ApplyTheme();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果计时器未运行,不更新显示
|
||||
if (!isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接从parentControl获取剩余时间
|
||||
var remainingTime = parentControl.GetRemainingTime();
|
||||
if (!remainingTime.HasValue)
|
||||
{
|
||||
// 如果无法获取剩余时间(可能是暂停状态),不更新显示
|
||||
return;
|
||||
}
|
||||
|
||||
var timeSpan = remainingTime.Value;
|
||||
bool isOvertimeMode = timeSpan.TotalSeconds < 0;
|
||||
|
||||
// 处理超时状态
|
||||
if (isOvertimeMode)
|
||||
{
|
||||
if (!isOvertime)
|
||||
{
|
||||
isOvertime = true;
|
||||
OnTimerOvertime();
|
||||
}
|
||||
|
||||
// 确保倒计时文本为红色(如果启用了超时红色文本设置)
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null && MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true)
|
||||
{
|
||||
CountdownText.Foreground = new SolidColorBrush(Colors.Red);
|
||||
}
|
||||
|
||||
// 显示超时时间
|
||||
var overtimeSpan = -timeSpan;
|
||||
if (overtimeSpan.TotalHours >= 1)
|
||||
{
|
||||
int hours = (int)overtimeSpan.TotalHours;
|
||||
CountdownText.Text = $"{hours:D2}:{overtimeSpan.Minutes:D2}:{overtimeSpan.Seconds:D2}";
|
||||
}
|
||||
else
|
||||
{
|
||||
CountdownText.Text = $"{overtimeSpan.Minutes:D2}:{overtimeSpan.Seconds:D2}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 正常倒计时
|
||||
if (isOvertime)
|
||||
{
|
||||
// 从超时状态恢复
|
||||
isOvertime = false;
|
||||
// 根据主题恢复倒计时文本颜色(如果未启用超时红色文本)
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null || MainWindow.Settings.RandSettings?.EnableOvertimeRedText != true)
|
||||
{
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 确保正常倒计时时使用主题颜色(如果未启用超时红色文本)
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null || MainWindow.Settings.RandSettings?.EnableOvertimeRedText != true)
|
||||
{
|
||||
// 检查当前颜色是否是红色,如果不是红色,则应用主题
|
||||
if (CountdownText.Foreground is SolidColorBrush brush && brush.Color != Colors.Red)
|
||||
{
|
||||
ApplyTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (timeSpan.TotalHours >= 1)
|
||||
{
|
||||
int hours = (int)timeSpan.TotalHours;
|
||||
CountdownText.Text = $"{hours:D2}:{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
|
||||
}
|
||||
else
|
||||
{
|
||||
CountdownText.Text = $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCapsuleExpandAnimation()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainCapsule != null)
|
||||
{
|
||||
// 记录原始宽度
|
||||
if (originalCapsuleWidth == 0)
|
||||
{
|
||||
originalCapsuleWidth = MainCapsule.ActualWidth > 0 ? MainCapsule.ActualWidth : 120;
|
||||
}
|
||||
|
||||
// 计算目标宽度(根据倒计时文本长度估算)
|
||||
double targetWidth = originalCapsuleWidth + 80; // 增加约80像素用于显示倒计时
|
||||
|
||||
if (capsuleExpandStoryboard == null)
|
||||
{
|
||||
capsuleExpandStoryboard = (Storyboard)Resources["CapsuleExpandAnimation"];
|
||||
}
|
||||
|
||||
if (capsuleExpandStoryboard != null)
|
||||
{
|
||||
// 设置动画的目标值
|
||||
var animation = capsuleExpandStoryboard.Children[0] as DoubleAnimation;
|
||||
if (animation != null)
|
||||
{
|
||||
animation.From = originalCapsuleWidth;
|
||||
animation.To = targetWidth;
|
||||
}
|
||||
|
||||
capsuleExpandStoryboard.Begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"播放胶囊伸长动画失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCapsuleShrinkAnimation()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainCapsule != null && originalCapsuleWidth > 0)
|
||||
{
|
||||
if (capsuleShrinkStoryboard == null)
|
||||
{
|
||||
capsuleShrinkStoryboard = (Storyboard)Resources["CapsuleShrinkAnimation"];
|
||||
}
|
||||
|
||||
if (capsuleShrinkStoryboard != null)
|
||||
{
|
||||
// 设置动画的目标值
|
||||
var animation = capsuleShrinkStoryboard.Children[0] as DoubleAnimation;
|
||||
if (animation != null)
|
||||
{
|
||||
animation.From = MainCapsule.ActualWidth;
|
||||
animation.To = originalCapsuleWidth;
|
||||
}
|
||||
|
||||
capsuleShrinkStoryboard.Begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"播放胶囊缩短动画失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTimerOvertime()
|
||||
{
|
||||
// 改变倒计时文字颜色为红色
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null && MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true)
|
||||
{
|
||||
CountdownText.Foreground = new SolidColorBrush(Colors.Red);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理计时器完成事件
|
||||
/// </summary>
|
||||
public void OnTimerCompleted()
|
||||
{
|
||||
// 确保在UI线程上执行
|
||||
if (!Dispatcher.CheckAccess())
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => OnTimerCompleted()), DispatcherPriority.Normal);
|
||||
return;
|
||||
}
|
||||
|
||||
// 停止倒计时
|
||||
StopCountdown();
|
||||
}
|
||||
|
||||
private void MainCapsule_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 点击恢复主计时器窗口(不重置计时器)
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
// 显示主计时器窗口
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 隐藏最小化计时器容器
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer != null)
|
||||
{
|
||||
minimizedContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (mainWindow.TimerControl != null)
|
||||
{
|
||||
mainWindow.TimerControl.UpdateActivityTime();
|
||||
|
||||
mainWindow.TimerControl.CloseRequested -= TimerControl_CloseRequested;
|
||||
mainWindow.TimerControl.CloseRequested += TimerControl_CloseRequested;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerControl_CloseRequested(object sender, EventArgs e)
|
||||
{
|
||||
// 当计时器窗口关闭时,隐藏TimerContainer
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
timerContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (minimizedContainer != null)
|
||||
{
|
||||
minimizedContainer.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ApplyTheme();
|
||||
}), DispatcherPriority.Normal);
|
||||
}
|
||||
|
||||
private void ApplyTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检测系统主题
|
||||
bool isDarkTheme = IsDarkTheme();
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
// 深色主题:使用80%不透明度的深色背景
|
||||
CapsuleBackgroundBrush.Color = Color.FromArgb(204, 32, 32, 32); // #CC202020,约80%不透明度
|
||||
SetDigitFill("Hour1Display", Colors.White);
|
||||
SetDigitFill("Hour2Display", Colors.White);
|
||||
SetDigitFill("Minute1Display", Colors.White);
|
||||
SetDigitFill("Minute2Display", Colors.White);
|
||||
ColonDisplay.Foreground = new SolidColorBrush(Colors.White);
|
||||
CountdownText.Foreground = new SolidColorBrush(Color.FromArgb(200, 255, 255, 255));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 浅色主题:使用80%不透明度的白色背景
|
||||
CapsuleBackgroundBrush.Color = Color.FromArgb(204, 255, 255, 255); // #CCFFFFFF,约80%不透明度
|
||||
SetDigitFill("Hour1Display", Colors.Black);
|
||||
SetDigitFill("Hour2Display", Colors.Black);
|
||||
SetDigitFill("Minute1Display", Colors.Black);
|
||||
SetDigitFill("Minute2Display", Colors.Black);
|
||||
ColonDisplay.Foreground = new SolidColorBrush(Colors.Black);
|
||||
CountdownText.Foreground = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用PPT时间胶囊主题失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDarkTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
string settingsPath = Path.Combine(App.RootPath, "Configs", "Settings.json");
|
||||
if (File.Exists(settingsPath))
|
||||
{
|
||||
string jsonText = File.ReadAllText(settingsPath);
|
||||
var settings = JsonConvert.DeserializeObject<Settings>(jsonText);
|
||||
|
||||
if (settings?.Appearance != null)
|
||||
{
|
||||
if (settings.Appearance.Theme == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (settings.Appearance.Theme == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
int systemTheme = (int)Microsoft.Win32.Registry.GetValue(
|
||||
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
||||
"AppsUseLightTheme", 1);
|
||||
return systemTheme == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int fallbackTheme = (int)Microsoft.Win32.Registry.GetValue(
|
||||
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
||||
"AppsUseLightTheme", 1);
|
||||
return fallbackTheme == 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前倒计时状态
|
||||
/// </summary>
|
||||
public bool IsCountdownRunning => parentControl != null && parentControl.IsTimerRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否超时
|
||||
/// </summary>
|
||||
public bool IsOvertime => isOvertime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
|
||||
@@ -49,9 +49,11 @@
|
||||
Stretch="Uniform"
|
||||
Margin="0,0,8,0"/>
|
||||
<!-- 快抽文字 -->
|
||||
<TextBlock Text="快抽" FontSize="20" FontWeight="Bold"
|
||||
Foreground="{DynamicResource QuickDrawWindowTitleForeground}"
|
||||
x:Name="TitleText"/>
|
||||
<Viewbox Stretch="Uniform" MaxWidth="100" MaxHeight="30">
|
||||
<TextBlock Text="快抽" FontSize="20" FontWeight="Bold"
|
||||
Foreground="{DynamicResource QuickDrawWindowTitleForeground}"
|
||||
x:Name="TitleText"/>
|
||||
</Viewbox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -61,14 +63,16 @@
|
||||
<!-- 结果显示区域 -->
|
||||
<Grid x:Name="ResultGrid" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<!-- 主结果显示 -->
|
||||
<TextBlock x:Name="MainResultDisplay"
|
||||
Text="准备抽选..."
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource QuickDrawWindowDigitForeground}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"/>
|
||||
<Viewbox Stretch="Uniform" MaxWidth="360" MaxHeight="120">
|
||||
<TextBlock x:Name="MainResultDisplay"
|
||||
Text="准备抽选..."
|
||||
FontSize="48"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource QuickDrawWindowDigitForeground}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextAlignment="Center"/>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -107,8 +107,7 @@ namespace Ink_Canvas
|
||||
if (settings.RandSettings.SelectedBackgroundIndex <= 0)
|
||||
{
|
||||
// 没有自定义背景时,使用主题背景色
|
||||
var backgroundBrush = Application.Current.FindResource("RandWindowBackground") as SolidColorBrush;
|
||||
if (backgroundBrush != null)
|
||||
if (Application.Current.FindResource("RandWindowBackground") is SolidColorBrush backgroundBrush)
|
||||
{
|
||||
MainBorder.Background = backgroundBrush;
|
||||
}
|
||||
@@ -212,59 +211,84 @@ namespace Ink_Canvas
|
||||
Random random = new Random();// randSeed + DateTime.Now.Millisecond / 10 % 10);
|
||||
string outputString = "";
|
||||
List<string> outputs = new List<string>();
|
||||
List<int> rands = new List<int>();
|
||||
|
||||
LabelOutput2.Visibility = Visibility.Collapsed;
|
||||
LabelOutput3.Visibility = Visibility.Collapsed;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
var animationPool = new List<int>();
|
||||
for (int num = 1; num <= PeopleCount; num++)
|
||||
{
|
||||
animationPool.Add(num);
|
||||
}
|
||||
int lastDisplayedIndex = -1;
|
||||
|
||||
for (int i = 0; i < RandWaitingTimes; i++)
|
||||
{
|
||||
int rand = random.Next(1, PeopleCount + 1);
|
||||
while (rands.Contains(rand))
|
||||
if (animationPool.Count == 0)
|
||||
{
|
||||
rand = random.Next(1, PeopleCount + 1);
|
||||
animationPool.Clear();
|
||||
for (int num = 1; num <= PeopleCount; num++)
|
||||
{
|
||||
animationPool.Add(num);
|
||||
}
|
||||
}
|
||||
rands.Add(rand);
|
||||
if (rands.Count >= PeopleCount) rands = new List<int>();
|
||||
|
||||
int randomIndex = random.Next(0, animationPool.Count);
|
||||
int selectedNumber = animationPool[randomIndex];
|
||||
|
||||
int lastIndex = animationPool.Count - 1;
|
||||
if (randomIndex != lastIndex)
|
||||
{
|
||||
animationPool[randomIndex] = animationPool[lastIndex];
|
||||
}
|
||||
animationPool.RemoveAt(lastIndex);
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (Names.Count != 0)
|
||||
{
|
||||
LabelOutput.Content = Names[rand - 1];
|
||||
LabelOutput.Content = Names[selectedNumber - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
LabelOutput.Content = rand.ToString();
|
||||
LabelOutput.Content = selectedNumber.ToString();
|
||||
}
|
||||
});
|
||||
|
||||
Thread.Sleep(RandWaitingThreadSleepTime);
|
||||
}
|
||||
|
||||
rands = new List<int>();
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
for (int i = 0; i < TotalCount; i++)
|
||||
var candidatePool = new List<int>();
|
||||
for (int num = 1; num <= PeopleCount; num++)
|
||||
{
|
||||
int rand = random.Next(1, PeopleCount + 1);
|
||||
while (rands.Contains(rand))
|
||||
candidatePool.Add(num);
|
||||
}
|
||||
|
||||
for (int i = 0; i < TotalCount && candidatePool.Count > 0; i++)
|
||||
{
|
||||
int randomIndex = random.Next(0, candidatePool.Count);
|
||||
int selectedNumber = candidatePool[randomIndex];
|
||||
|
||||
int lastIndex = candidatePool.Count - 1;
|
||||
if (randomIndex != lastIndex)
|
||||
{
|
||||
rand = random.Next(1, PeopleCount + 1);
|
||||
candidatePool[randomIndex] = candidatePool[lastIndex];
|
||||
}
|
||||
rands.Add(rand);
|
||||
if (rands.Count >= PeopleCount) rands = new List<int>();
|
||||
candidatePool.RemoveAt(lastIndex);
|
||||
|
||||
if (Names.Count != 0)
|
||||
{
|
||||
outputs.Add(Names[rand - 1]);
|
||||
outputString += Names[rand - 1] + Environment.NewLine;
|
||||
outputs.Add(Names[selectedNumber - 1]);
|
||||
outputString += Names[selectedNumber - 1] + Environment.NewLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputs.Add(rand.ToString());
|
||||
outputString += rand + Environment.NewLine;
|
||||
outputs.Add(selectedNumber.ToString());
|
||||
outputString += selectedNumber + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
if (TotalCount <= 5)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Newtonsoft.Json;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -95,26 +94,26 @@ namespace Ink_Canvas
|
||||
// 显示统计信息
|
||||
int totalCount = historyData.History.Count;
|
||||
string lastUpdate = historyData.LastUpdate.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
|
||||
// 计算累计统计信息
|
||||
var statsLines = new System.Collections.Generic.List<string>();
|
||||
statsLines.Add($"");
|
||||
statsLines.Add($"");
|
||||
statsLines.Add($"累计抽选次数统计:");
|
||||
|
||||
|
||||
// 按累计次数降序排序显示
|
||||
var sortedStats = nameCountDict.OrderByDescending(kvp => kvp.Value).ToList();
|
||||
foreach (var kvp in sortedStats)
|
||||
{
|
||||
statsLines.Add($" {kvp.Key}: {kvp.Value}次");
|
||||
}
|
||||
|
||||
|
||||
statsLines.Add($"");
|
||||
statsLines.Add($"共 {totalCount} 条记录,最后更新:{lastUpdate}");
|
||||
|
||||
|
||||
// 组合历史记录和统计信息
|
||||
TextBoxHistory.Text = string.Join(Environment.NewLine, historyLines) +
|
||||
Environment.NewLine +
|
||||
TextBoxHistory.Text = string.Join(Environment.NewLine, historyLines) +
|
||||
Environment.NewLine +
|
||||
string.Join(Environment.NewLine, statsLines);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -184,7 +183,7 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
var resources = this.Resources;
|
||||
|
||||
|
||||
if (theme == "Light")
|
||||
{
|
||||
// 应用浅色主题资源
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.AdvancedPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// AdvancedPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class AdvancedPanel : UserControl
|
||||
{
|
||||
public AdvancedPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.AutomationPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// AutomationPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class AutomationPanel : UserControl
|
||||
{
|
||||
public AutomationPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.CanvasAndInkPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using iNKORE.UI.WPF.Helpers;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// CanvasAndInkPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CanvasAndInkPanel : UserControl
|
||||
{
|
||||
public CanvasAndInkPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.CrashActionPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// CrashActionPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CrashActionPanel : UserControl
|
||||
{
|
||||
public CrashActionPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.GesturesPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// GesturesPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class GesturesPanel : UserControl
|
||||
{
|
||||
public GesturesPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.InkRecognitionPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// InkRecognitionPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class InkRecognitionPanel : UserControl
|
||||
{
|
||||
public InkRecognitionPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.LuckyRandomPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// LuckyRandomPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LuckyRandomPanel : UserControl
|
||||
{
|
||||
public LuckyRandomPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.PowerPointPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// PowerPointPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class PowerPointPanel : UserControl
|
||||
{
|
||||
public PowerPointPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.SearchPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="691" d:DesignWidth="910">
|
||||
<Grid Background="#fafafa" Margin="250,0,0,0">
|
||||
<!-- 搜索框区域 -->
|
||||
<Grid Height="48" VerticalAlignment="Top" Margin="0,0,0,0">
|
||||
<Border Height="48" CornerRadius="0,6,0,0" Background="#fafafa">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Direction="-90" ShadowDepth="3" BlurRadius="4" Color="#000000" Opacity="0.25"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
<Grid Margin="0,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- 搜索输入框 -->
|
||||
<Border Grid.Column="0" Margin="60,8,12,8" Background="White" CornerRadius="8" BorderBrush="#e6e6e6" BorderThickness="1">
|
||||
<Grid>
|
||||
<Image Width="16" Height="16" Margin="12,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V17 H16 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FF222222" Geometry="F1 M16,17z M0,0z M6.9333,0.111572C3.11689,0.111572 -5.16259E-07,3.22999 -5.16259E-07,7.04827 -5.16259E-07,10.8665 3.11689,13.9829 6.9333,13.9829 8.45757,13.9829 9.86954,13.4831 11.0166,12.6427L14.1583,15.7858C15.1805,16.7869,16.6805,15.2528,15.6583,14.2518L12.5333,11.1252C13.3704,9.97933 13.8666,8.57003 13.8666,7.04827 13.8666,3.22999 10.7497,0.111572 6.9333,0.111572z M6.9333,2.24594C9.59676,2.24594 11.7333,4.38351 11.7333,7.04827 11.7333,9.71302 9.59676,11.8485 6.9333,11.8485 4.26985,11.8485 2.13332,9.71302 2.13332,7.04827 2.13332,4.38351 4.26985,2.24594 6.9333,2.24594z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<TextBox Name="SearchTextBox"
|
||||
FontSize="14"
|
||||
Foreground="#2e3436"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="40,0,12,0"
|
||||
VerticalContentAlignment="Center"
|
||||
KeyDown="SearchTextBox_KeyDown"
|
||||
TextChanged="SearchTextBox_TextChanged"
|
||||
GotFocus="SearchTextBox_GotFocus"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- 关闭按钮 -->
|
||||
<Border Grid.Column="1" CornerRadius="8" Background="#33ef4444" Width="34" Height="34" Margin="0,0,8,0" HorizontalAlignment="Right" VerticalAlignment="Center" MouseLeftButtonDown="CloseSearchButton_Click" Cursor="Hand">
|
||||
<Image Width="12" Height="12">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V12 H12 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#991b1b" Geometry="F1 M12,12z M0,0z M0.999846,0C0.734646,4.07258E-05 0.480321,0.105424 0.292816,0.29297 0.105327,0.4805 0,0.734821 0,1 0,1.26518 0.105327,1.5195 0.292816,1.70703L4.58579,6 0.292816,10.293C0.105327,10.4805 0,10.7348 0,11 0,11.2652 0.105327,11.5195 0.292816,11.707 0.480347,11.8945 0.734668,11.9998 0.999846,11.9998 1.26503,11.9998 1.51935,11.8945 1.70688,11.707L5.99985,7.41406 10.2928,11.707C10.4803,11.8945 10.7347,11.9998 10.9998,11.9998 11.265,11.9998 11.5193,11.8945 11.7069,11.707 11.8944,11.5195 11.9997,11.2652 11.9997,11 11.9997,10.7348 11.8944,10.4805 11.7069,10.293L7.41391,6 11.7069,1.70703C11.8944,1.5195 11.9997,1.26518 11.9997,1 11.9997,0.734821 11.8944,0.4805 11.7069,0.29297 11.5194,0.105424 11.265,4.07258E-05 10.9998,0 10.7346,4.07258E-05 10.4803,0.105424 10.2928,0.29297L5.99985,4.58594 1.70688,0.29297C1.51937,0.105424,1.26505,4.07258E-05,0.999846,0z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- 搜索结果区域 -->
|
||||
<ScrollViewer Margin="0,48,0,0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="60,12,60,24" Name="SearchResultsPanel">
|
||||
<!-- 精准和拼音匹配结果 -->
|
||||
<StackPanel Name="ExactMatchPanel" Visibility="Collapsed">
|
||||
<TextBlock Text="精准匹配" FontSize="13" FontWeight="Bold" Foreground="#2e3436" Margin="0,0,0,8"/>
|
||||
<ItemsControl Name="ExactMatchItemsControl">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="White" CornerRadius="6" Margin="0,0,0,6" Padding="16,12" MouseLeftButtonDown="SearchResultItem_Click" Cursor="Hand" Tag="{Binding}">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="{Binding Title}" FontSize="14" Foreground="#2e3436" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Category}" FontSize="11" Foreground="#9a9996" Margin="0,4,0,0"/>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" Foreground="#9a9996" Margin="0,2,0,0" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 模糊匹配结果 -->
|
||||
<StackPanel Name="FuzzyMatchPanel" Visibility="Collapsed" Margin="0,16,0,0">
|
||||
<TextBlock Text="模糊匹配" FontSize="13" FontWeight="Bold" Foreground="#2e3436" Margin="0,0,0,8"/>
|
||||
<ItemsControl Name="FuzzyMatchItemsControl">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="White" CornerRadius="6" Margin="0,0,0,6" Padding="16,12" MouseLeftButtonDown="SearchResultItem_Click" Cursor="Hand" Tag="{Binding}">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="{Binding Title}" FontSize="14" Foreground="#2e3436" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Category}" FontSize="11" Foreground="#9a9996" Margin="0,4,0,0"/>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" Foreground="#9a9996" Margin="0,2,0,0" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 相关项结果 -->
|
||||
<StackPanel Name="RelatedItemsPanel" Visibility="Collapsed" Margin="0,16,0,0">
|
||||
<TextBlock Text="相关项" FontSize="13" FontWeight="Bold" Foreground="#2e3436" Margin="0,0,0,8"/>
|
||||
<ItemsControl Name="RelatedItemsControl">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="White" CornerRadius="6" Margin="0,0,0,6" Padding="16,12" MouseLeftButtonDown="SearchResultItem_Click" Cursor="Hand" Tag="{Binding}">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Text="{Binding Title}" FontSize="14" Foreground="#2e3436" FontWeight="Bold"/>
|
||||
<TextBlock Text="{Binding Category}" FontSize="11" Foreground="#9a9996" Margin="0,4,0,0"/>
|
||||
<TextBlock Text="{Binding Description}" FontSize="11" Foreground="#9a9996" Margin="0,2,0,0" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 无结果提示 -->
|
||||
<TextBlock Name="NoResultsText"
|
||||
Text="未找到相关设置项"
|
||||
FontSize="14"
|
||||
Foreground="#9a9996"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,40,0,0"
|
||||
Visibility="Collapsed"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.International.Converters.PinYinConverter;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// SearchPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SearchPanel : UserControl
|
||||
{
|
||||
public event EventHandler<string> NavigateToItem;
|
||||
public event EventHandler CloseSearch;
|
||||
|
||||
private ObservableCollection<SearchResultItem> _exactMatches = new ObservableCollection<SearchResultItem>();
|
||||
private ObservableCollection<SearchResultItem> _fuzzyMatches = new ObservableCollection<SearchResultItem>();
|
||||
private ObservableCollection<SearchResultItem> _relatedItems = new ObservableCollection<SearchResultItem>();
|
||||
|
||||
private List<SettingItem> _allSettings = new List<SettingItem>();
|
||||
|
||||
public SearchPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeSettings();
|
||||
ExactMatchItemsControl.ItemsSource = _exactMatches;
|
||||
FuzzyMatchItemsControl.ItemsSource = _fuzzyMatches;
|
||||
RelatedItemsControl.ItemsSource = _relatedItems;
|
||||
}
|
||||
|
||||
private void InitializeSettings()
|
||||
{
|
||||
// 初始化所有设置项数据
|
||||
_allSettings = new List<SettingItem>
|
||||
{
|
||||
// 启动时行为
|
||||
new SettingItem { Title = "启动时行为", Category = "启动时行为", ItemName = "StartupItem", Type = SettingItemType.Category },
|
||||
|
||||
// 画板和墨迹
|
||||
new SettingItem { Title = "画板和墨迹", Category = "画板和墨迹", ItemName = "CanvasAndInkItem", Type = SettingItemType.Category },
|
||||
|
||||
// 手势操作
|
||||
new SettingItem { Title = "手势操作", Category = "手势操作", ItemName = "GesturesItem", Type = SettingItemType.Category },
|
||||
|
||||
// 墨迹纠正
|
||||
new SettingItem { Title = "墨迹纠正", Category = "墨迹纠正", ItemName = "InkRecognitionItem", Type = SettingItemType.Category },
|
||||
|
||||
// 个性化设置
|
||||
new SettingItem { Title = "个性化设置", Category = "个性化设置", ItemName = "ThemeItem", Type = SettingItemType.Category },
|
||||
|
||||
// 快捷键设置
|
||||
new SettingItem { Title = "快捷键设置", Category = "快捷键设置", ItemName = "ShortcutsItem", Type = SettingItemType.Category },
|
||||
|
||||
// 崩溃处理
|
||||
new SettingItem { Title = "崩溃处理", Category = "崩溃处理", ItemName = "CrashActionItem", Type = SettingItemType.Category },
|
||||
|
||||
// PowerPoint 支持
|
||||
new SettingItem { Title = "PowerPoint 支持", Category = "PowerPoint 支持", ItemName = "PowerPointItem", Type = SettingItemType.Category },
|
||||
|
||||
// 自动化行为
|
||||
new SettingItem { Title = "自动化行为", Category = "自动化行为", ItemName = "AutomationItem", Type = SettingItemType.Category },
|
||||
|
||||
// 随机点名
|
||||
new SettingItem { Title = "随机点名", Category = "随机点名", ItemName = "LuckyRandomItem", Type = SettingItemType.Category },
|
||||
|
||||
// 存储空间
|
||||
new SettingItem { Title = "存储空间", Category = "存储空间", ItemName = "StorageItem", Type = SettingItemType.Category },
|
||||
|
||||
// 截图和屏幕捕捉
|
||||
new SettingItem { Title = "截图和屏幕捕捉", Category = "截图和屏幕捕捉", ItemName = "SnapshotItem", Type = SettingItemType.Category },
|
||||
|
||||
// 高级选项
|
||||
new SettingItem { Title = "高级选项", Category = "高级选项", ItemName = "AdvancedItem", Type = SettingItemType.Category },
|
||||
|
||||
// 关于
|
||||
new SettingItem { Title = "关于 InkCanvasForClass", Category = "关于", ItemName = "AboutItem", Type = SettingItemType.Category },
|
||||
};
|
||||
}
|
||||
|
||||
public void PerformSearch(string searchText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
ClearResults();
|
||||
return;
|
||||
}
|
||||
|
||||
_exactMatches.Clear();
|
||||
_fuzzyMatches.Clear();
|
||||
_relatedItems.Clear();
|
||||
|
||||
var searchLower = searchText.ToLower();
|
||||
var exactMatchSet = new HashSet<string>();
|
||||
var fuzzyMatchSet = new HashSet<string>();
|
||||
|
||||
// 精准匹配和拼音匹配
|
||||
foreach (var setting in _allSettings)
|
||||
{
|
||||
var titleLower = setting.Title.ToLower();
|
||||
var categoryLower = setting.Category.ToLower();
|
||||
|
||||
// 精准匹配
|
||||
if (titleLower.Contains(searchLower) || categoryLower.Contains(searchLower))
|
||||
{
|
||||
if (!exactMatchSet.Contains(setting.ItemName))
|
||||
{
|
||||
_exactMatches.Add(new SearchResultItem
|
||||
{
|
||||
Title = setting.Title,
|
||||
Category = setting.Category,
|
||||
ItemName = setting.ItemName,
|
||||
Type = setting.Type,
|
||||
MatchType = MatchType.Exact
|
||||
});
|
||||
exactMatchSet.Add(setting.ItemName);
|
||||
}
|
||||
}
|
||||
// 拼音匹配
|
||||
else if (ContainsPinyinMatch(setting.Title, searchText) || ContainsPinyinMatch(setting.Category, searchText))
|
||||
{
|
||||
if (!exactMatchSet.Contains(setting.ItemName))
|
||||
{
|
||||
_exactMatches.Add(new SearchResultItem
|
||||
{
|
||||
Title = setting.Title,
|
||||
Category = setting.Category,
|
||||
ItemName = setting.ItemName,
|
||||
Type = setting.Type,
|
||||
MatchType = MatchType.Pinyin
|
||||
});
|
||||
exactMatchSet.Add(setting.ItemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模糊匹配
|
||||
foreach (var setting in _allSettings)
|
||||
{
|
||||
if (exactMatchSet.Contains(setting.ItemName))
|
||||
continue;
|
||||
|
||||
var searchableText = $"{setting.Title} {setting.Category} {setting.Description}".ToLower();
|
||||
if (FuzzyMatch(searchableText, searchLower))
|
||||
{
|
||||
if (!fuzzyMatchSet.Contains(setting.ItemName))
|
||||
{
|
||||
_fuzzyMatches.Add(new SearchResultItem
|
||||
{
|
||||
Title = setting.Title,
|
||||
Category = setting.Category,
|
||||
ItemName = setting.ItemName,
|
||||
Type = setting.Type,
|
||||
MatchType = MatchType.Fuzzy
|
||||
});
|
||||
fuzzyMatchSet.Add(setting.ItemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 相关项
|
||||
var allMatched = new HashSet<string>(exactMatchSet.Concat(fuzzyMatchSet));
|
||||
foreach (var setting in _allSettings)
|
||||
{
|
||||
if (!allMatched.Contains(setting.ItemName))
|
||||
{
|
||||
// 简单的相关性判断
|
||||
if (IsRelated(setting, searchText))
|
||||
{
|
||||
_relatedItems.Add(new SearchResultItem
|
||||
{
|
||||
Title = setting.Title,
|
||||
Category = setting.Category,
|
||||
ItemName = setting.ItemName,
|
||||
Type = setting.Type,
|
||||
MatchType = MatchType.Related
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateResultsVisibility();
|
||||
}
|
||||
|
||||
private bool ContainsPinyinMatch(string text, string search)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(search))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// 将搜索词转换为小写
|
||||
var searchLower = search.ToLower();
|
||||
|
||||
// 获取文本的拼音首字母和全拼
|
||||
var pinyinInitials = GetPinyinInitials(text);
|
||||
var pinyinFull = GetPinyinFull(text);
|
||||
|
||||
// 检查搜索词是否匹配拼音首字母或全拼
|
||||
if (pinyinInitials.ToLower().Contains(searchLower) ||
|
||||
pinyinFull.ToLower().Contains(searchLower))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果拼音转换失败,返回false
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetPinyinInitials(string text)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (IsChinese(c))
|
||||
{
|
||||
try
|
||||
{
|
||||
var chineseChar = new ChineseChar(c);
|
||||
if (chineseChar.PinyinCount > 0)
|
||||
{
|
||||
var pinyin = chineseChar.Pinyins[0];
|
||||
if (!string.IsNullOrEmpty(pinyin) && pinyin.Length > 0)
|
||||
{
|
||||
// 获取首字母(移除音调数字后取第一个字母)
|
||||
var firstChar = Regex.Replace(pinyin, @"\d", "")[0];
|
||||
sb.Append(firstChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GetPinyinFull(string text)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (IsChinese(c))
|
||||
{
|
||||
try
|
||||
{
|
||||
var chineseChar = new ChineseChar(c);
|
||||
if (chineseChar.PinyinCount > 0)
|
||||
{
|
||||
var pinyin = chineseChar.Pinyins[0];
|
||||
// 移除音调数字
|
||||
pinyin = Regex.Replace(pinyin, @"\d", "");
|
||||
sb.Append(pinyin);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private bool IsChinese(char c)
|
||||
{
|
||||
return c >= 0x4e00 && c <= 0x9fbb;
|
||||
}
|
||||
|
||||
private bool FuzzyMatch(string text, string search)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text) || string.IsNullOrWhiteSpace(search))
|
||||
return false;
|
||||
|
||||
// 简单的模糊匹配:检查搜索词的字符是否按顺序出现在文本中
|
||||
int searchIndex = 0;
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (searchIndex < search.Length && c == search[searchIndex])
|
||||
{
|
||||
searchIndex++;
|
||||
if (searchIndex == search.Length)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsRelated(SettingItem setting, string search)
|
||||
{
|
||||
// 简单的相关性判断,可以根据需要改进
|
||||
// 例如:检查是否有共同的关键词等
|
||||
return false; // 暂时禁用相关项功能
|
||||
}
|
||||
|
||||
private void UpdateResultsVisibility()
|
||||
{
|
||||
ExactMatchPanel.Visibility = _exactMatches.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
FuzzyMatchPanel.Visibility = _fuzzyMatches.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
RelatedItemsPanel.Visibility = _relatedItems.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
bool hasResults = _exactMatches.Count > 0 || _fuzzyMatches.Count > 0 || _relatedItems.Count > 0;
|
||||
NoResultsText.Visibility = hasResults ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
private void ClearResults()
|
||||
{
|
||||
_exactMatches.Clear();
|
||||
_fuzzyMatches.Clear();
|
||||
_relatedItems.Clear();
|
||||
UpdateResultsVisibility();
|
||||
}
|
||||
|
||||
private void SearchTextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
{
|
||||
CloseSearch?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var textBox = sender as TextBox;
|
||||
if (textBox != null)
|
||||
{
|
||||
PerformSearch(textBox.Text);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchTextBox_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var textBox = sender as TextBox;
|
||||
textBox?.SelectAll();
|
||||
}
|
||||
|
||||
private void CloseSearchButton_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
CloseSearch?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void SearchResultItem_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var border = sender as Border;
|
||||
if (border?.Tag is SearchResultItem item)
|
||||
{
|
||||
NavigateToItem?.Invoke(this, item.ItemName);
|
||||
}
|
||||
}
|
||||
|
||||
public void FocusSearchBox()
|
||||
{
|
||||
SearchTextBox.Focus();
|
||||
}
|
||||
|
||||
public void SetSearchText(string text)
|
||||
{
|
||||
SearchTextBox.Text = text;
|
||||
PerformSearch(text);
|
||||
}
|
||||
}
|
||||
|
||||
public class SettingItem
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Category { get; set; }
|
||||
public string ItemName { get; set; }
|
||||
public string Description { get; set; } = "";
|
||||
public SettingItemType Type { get; set; }
|
||||
}
|
||||
|
||||
public class SearchResultItem
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Category { get; set; }
|
||||
public string ItemName { get; set; }
|
||||
public SettingItemType Type { get; set; }
|
||||
public MatchType MatchType { get; set; }
|
||||
public string Description { get; set; } = "";
|
||||
}
|
||||
|
||||
public enum SettingItemType
|
||||
{
|
||||
Category,
|
||||
Setting
|
||||
}
|
||||
|
||||
public enum MatchType
|
||||
{
|
||||
Exact,
|
||||
Pinyin,
|
||||
Fuzzy,
|
||||
Related
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.ShortcutsPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// ShortcutsPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ShortcutsPanel : UserControl
|
||||
{
|
||||
public ShortcutsPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.SnapshotPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// SnapshotPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SnapshotPanel : UserControl
|
||||
{
|
||||
public SnapshotPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.StartupPanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// StartupPanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class StartupPanel : UserControl
|
||||
{
|
||||
public StartupPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.StoragePanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// StoragePanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class StoragePanel : UserControl
|
||||
{
|
||||
public StoragePanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Ink_Canvas.Windows.SettingsViews.ThemePanel"
|
||||
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.Windows.SettingsViews"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="950" d:DesignWidth="640">
|
||||
<ScrollViewer ScrollChanged="ScrollViewerEx_ScrollChanged" IsManipulationEnabled="True" Name="ScrollViewerEx" IsDeferredScrollingEnabled="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" IsTabStop="False" TabIndex="-1" Margin="0,0,2,2">
|
||||
<StackPanel Margin="60,12,60,24">
|
||||
<!-- 设置项已清空,仅保留页面框架 -->
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows.SettingsViews
|
||||
{
|
||||
/// <summary>
|
||||
/// ThemePanel.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ThemePanel : UserControl
|
||||
{
|
||||
public ThemePanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedShadowEffect;
|
||||
public event EventHandler<RoutedEventArgs> IsTopBarNeedNoShadowEffect;
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
IsTopBarNeedShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
else
|
||||
{
|
||||
IsTopBarNeedNoShadowEffect?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,10 @@ namespace Ink_Canvas.Windows
|
||||
public SettingsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 初始化搜索面板事件
|
||||
SearchPanelControl.NavigateToItem += SearchPanel_NavigateToItem;
|
||||
SearchPanelControl.CloseSearch += SearchPanel_CloseSearch;
|
||||
|
||||
// 初始化侧边栏项目
|
||||
SidebarItemsControl.ItemsSource = SidebarItems;
|
||||
@@ -169,19 +173,19 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
SettingsPaneScrollViewers = new ScrollViewer[] {
|
||||
SettingsAboutPanel.AboutScrollViewerEx,
|
||||
CanvasAndInkScrollViewerEx,
|
||||
GesturesScrollViewerEx,
|
||||
StartupScrollViewerEx,
|
||||
ThemeScrollViewerEx,
|
||||
ShortcutsScrollViewerEx,
|
||||
CrashActionScrollViewerEx,
|
||||
InkRecognitionScrollViewerEx,
|
||||
AutomationScrollViewerEx,
|
||||
PowerPointScrollViewerEx,
|
||||
LuckyRandomScrollViewerEx,
|
||||
StorageScrollViewerEx,
|
||||
SnapshotScrollViewerEx,
|
||||
AdvancedScrollViewerEx
|
||||
CanvasAndInkPanel.ScrollViewerEx,
|
||||
GesturesPanel.ScrollViewerEx,
|
||||
StartupPanel.ScrollViewerEx,
|
||||
ThemePanel.ScrollViewerEx,
|
||||
ShortcutsPanel.ScrollViewerEx,
|
||||
CrashActionPanel.ScrollViewerEx,
|
||||
InkRecognitionPanel.ScrollViewerEx,
|
||||
AutomationPanel.ScrollViewerEx,
|
||||
PowerPointPanel.ScrollViewerEx,
|
||||
LuckyRandomPanel.ScrollViewerEx,
|
||||
StoragePanel.ScrollViewerEx,
|
||||
SnapshotPanel.ScrollViewerEx,
|
||||
AdvancedPanel.ScrollViewerEx
|
||||
};
|
||||
|
||||
SettingsPaneTitles = new string[] {
|
||||
@@ -221,6 +225,34 @@ namespace Ink_Canvas.Windows
|
||||
SettingsAboutPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
SettingsAboutPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
|
||||
// 订阅所有UserControl的滚动事件
|
||||
CanvasAndInkPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
CanvasAndInkPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
GesturesPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
GesturesPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
StartupPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
StartupPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
InkRecognitionPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
InkRecognitionPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
AutomationPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
AutomationPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
PowerPointPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
PowerPointPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
ThemePanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
ThemePanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
ShortcutsPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
ShortcutsPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
CrashActionPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
CrashActionPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
LuckyRandomPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
LuckyRandomPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
StoragePanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
StoragePanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
SnapshotPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
SnapshotPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
AdvancedPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25;
|
||||
AdvancedPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0;
|
||||
|
||||
_selectedSidebarItemName = "CanvasAndInkItem";
|
||||
UpdateSidebarItemsSelection();
|
||||
|
||||
@@ -306,19 +338,6 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollViewerEx_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
if (scrollViewer.VerticalOffset >= 10)
|
||||
{
|
||||
DropShadowEffectTopBar.Opacity = 0.25;
|
||||
}
|
||||
else
|
||||
{
|
||||
DropShadowEffectTopBar.Opacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollBar_Scroll(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var scrollbar = (ScrollBar)sender;
|
||||
@@ -430,7 +449,43 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
private void SearchButton_Click(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 搜索功能 - 可以显示搜索框或搜索对话框
|
||||
// 显示搜索界面
|
||||
SearchPane.Visibility = Visibility.Visible;
|
||||
SearchPanelControl.FocusSearchBox();
|
||||
}
|
||||
|
||||
private void SearchPanel_NavigateToItem(object sender, string itemName)
|
||||
{
|
||||
// 隐藏搜索界面
|
||||
SearchPane.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 导航到对应的设置项
|
||||
NavigateToSidebarItem(itemName);
|
||||
}
|
||||
|
||||
private void SearchPanel_CloseSearch(object sender, EventArgs e)
|
||||
{
|
||||
// 隐藏搜索界面
|
||||
SearchPane.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void NavigateToSidebarItem(string itemName)
|
||||
{
|
||||
// 查找对应的侧边栏项并选中
|
||||
foreach (var item in SidebarItems)
|
||||
{
|
||||
if (item.Name == itemName)
|
||||
{
|
||||
SelectSidebarItem(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectSidebarItem(SidebarItem item)
|
||||
{
|
||||
_selectedSidebarItemName = item.Name;
|
||||
UpdateSidebarItemsSelection();
|
||||
}
|
||||
|
||||
private void MenuButton_Click(object sender, MouseButtonEventArgs e)
|
||||
|
||||
@@ -34,7 +34,11 @@
|
||||
<!-- 主要内容区域 -->
|
||||
<Grid>
|
||||
<!-- 使用Viewbox自动缩放内容 -->
|
||||
<Viewbox x:Name="MainViewController" Margin="20,20,20,20">
|
||||
<Viewbox x:Name="MainViewController"
|
||||
Margin="20,20,20,20"
|
||||
StretchDirection="DownOnly"
|
||||
MaxWidth="900"
|
||||
MaxHeight="500">
|
||||
<Grid Height="400" Width="900">
|
||||
<!-- 顶部标题栏 -->
|
||||
<Grid Height="50"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Media;
|
||||
@@ -7,10 +9,6 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Newtonsoft.Json;
|
||||
using System.Windows.Threading;
|
||||
using Microsoft.Win32;
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
@@ -41,25 +39,73 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
// 应用主题
|
||||
ApplyTheme();
|
||||
|
||||
|
||||
// 初始化隐藏定时器
|
||||
hideTimer = new Timer(1000); // 每秒检查一次
|
||||
hideTimer.Elapsed += HideTimer_Elapsed;
|
||||
lastActivityTime = DateTime.Now;
|
||||
|
||||
|
||||
// 监听主题变化事件
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
|
||||
|
||||
// 监听卸载事件,清理资源
|
||||
Unloaded += TimerControl_Unloaded;
|
||||
|
||||
// 监听加载事件,调整DPI相关的尺寸
|
||||
Loaded += TimerControl_Loaded;
|
||||
}
|
||||
|
||||
|
||||
private void TimerControl_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 根据DPI缩放因子调整Viewbox的最大尺寸,确保在高DPI屏幕上不会过大
|
||||
try
|
||||
{
|
||||
var source = System.Windows.PresentationSource.FromVisual(this);
|
||||
if (source != null)
|
||||
{
|
||||
var dpiScaleX = source.CompositionTarget.TransformToDevice.M11;
|
||||
var dpiScaleY = source.CompositionTarget.TransformToDevice.M22;
|
||||
|
||||
// 如果DPI缩放因子大于1.25,则适当缩小最大尺寸
|
||||
// 这样可以确保在高DPI屏幕上,计时器窗口的物理像素大小不会过大
|
||||
if (dpiScaleX > 1.25 || dpiScaleY > 1.25)
|
||||
{
|
||||
// 使用较小的缩放因子来限制最大尺寸
|
||||
double scaleFactor = Math.Min(dpiScaleX, dpiScaleY);
|
||||
|
||||
if (MainViewController != null)
|
||||
{
|
||||
// 计算目标物理像素大小(约1350x750物理像素)
|
||||
// 然后转换为逻辑像素
|
||||
double targetPhysicalWidth = 1350;
|
||||
double targetPhysicalHeight = 750;
|
||||
|
||||
// 转换为逻辑像素
|
||||
double maxWidth = targetPhysicalWidth / scaleFactor;
|
||||
double maxHeight = targetPhysicalHeight / scaleFactor;
|
||||
|
||||
// 确保不会小于原始尺寸的70%
|
||||
maxWidth = Math.Max(maxWidth, 900 * 0.7);
|
||||
maxHeight = Math.Max(maxHeight, 500 * 0.7);
|
||||
|
||||
MainViewController.MaxWidth = maxWidth;
|
||||
MainViewController.MaxHeight = maxHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调整计时器窗口DPI尺寸失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void TimerControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 取消订阅主题变化事件
|
||||
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
|
||||
}
|
||||
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
// 当主题变化时,重新应用主题
|
||||
@@ -68,9 +114,9 @@ namespace Ink_Canvas.Windows
|
||||
RefreshTheme();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 刷新主题(供外部调用)
|
||||
/// 刷新主题
|
||||
/// </summary>
|
||||
public void RefreshTheme()
|
||||
{
|
||||
@@ -78,7 +124,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
// 重新应用主题
|
||||
ApplyTheme();
|
||||
|
||||
|
||||
// 强制刷新UI
|
||||
InvalidateVisual();
|
||||
}
|
||||
@@ -87,23 +133,23 @@ namespace Ink_Canvas.Windows
|
||||
LogHelper.WriteLogToFile($"刷新计时器窗口主题出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region 事件定义
|
||||
/// <summary>
|
||||
/// 计时器完成事件
|
||||
/// </summary>
|
||||
public event EventHandler TimerCompleted;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 关闭事件 - 通知主窗口隐藏容器
|
||||
/// </summary>
|
||||
public event EventHandler CloseRequested;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 显示最小化视图事件
|
||||
/// </summary>
|
||||
public event EventHandler ShowMinimizedRequested;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏最小化视图事件
|
||||
/// </summary>
|
||||
@@ -129,7 +175,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
TimeSpan leftTimeSpan = totalTimeSpan - timeSpan;
|
||||
if (leftTimeSpan.Milliseconds > 0) leftTimeSpan += new TimeSpan(0, 0, 1);
|
||||
|
||||
|
||||
int totalHours = (int)leftTimeSpan.TotalHours;
|
||||
int displayHours = totalHours;
|
||||
|
||||
@@ -141,10 +187,10 @@ namespace Ink_Canvas.Windows
|
||||
SetDigitDisplay("Digit4Display", leftTimeSpan.Minutes % 10);
|
||||
SetDigitDisplay("Digit5Display", leftTimeSpan.Seconds / 10);
|
||||
SetDigitDisplay("Digit6Display", leftTimeSpan.Seconds % 10);
|
||||
|
||||
|
||||
SetColonDisplay(false);
|
||||
|
||||
if (leftTimeSpan.TotalSeconds <= 6 && leftTimeSpan.TotalSeconds > 0 &&
|
||||
|
||||
if (leftTimeSpan.TotalSeconds <= 6 && leftTimeSpan.TotalSeconds > 0 &&
|
||||
MainWindow.Settings.RandSettings?.EnableProgressiveReminder == true &&
|
||||
!hasPlayedProgressiveReminder)
|
||||
{
|
||||
@@ -165,19 +211,19 @@ namespace Ink_Canvas.Windows
|
||||
SetDigitDisplay("Digit4Display", 0);
|
||||
SetDigitDisplay("Digit5Display", 0);
|
||||
SetDigitDisplay("Digit6Display", 0);
|
||||
|
||||
|
||||
SetColonDisplay(false);
|
||||
timer.Stop();
|
||||
isTimerRunning = false;
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
PlayTimerSound();
|
||||
|
||||
|
||||
// 禁用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
FullscreenBtn.IsEnabled = false;
|
||||
}
|
||||
|
||||
|
||||
TimerCompleted?.Invoke(this, EventArgs.Empty);
|
||||
HandleTimerCompletion();
|
||||
}
|
||||
@@ -206,7 +252,7 @@ namespace Ink_Canvas.Windows
|
||||
SetDigitDisplay("Digit4Display", minutesOnes, shouldShowRed);
|
||||
SetDigitDisplay("Digit5Display", secondsTens, shouldShowRed);
|
||||
SetDigitDisplay("Digit6Display", secondsOnes, shouldShowRed);
|
||||
|
||||
|
||||
SetColonDisplay(shouldShowRed);
|
||||
}
|
||||
});
|
||||
@@ -224,22 +270,22 @@ namespace Ink_Canvas.Windows
|
||||
|
||||
bool isTimerRunning = false;
|
||||
bool isPaused = false;
|
||||
bool isOvertimeMode = false;
|
||||
bool isOvertimeMode = false;
|
||||
TimeSpan remainingTime = TimeSpan.Zero;
|
||||
bool hasPlayedProgressiveReminder = false;
|
||||
|
||||
bool hasPlayedProgressiveReminder = false;
|
||||
|
||||
Timer timer = new Timer();
|
||||
private Timer hideTimer;
|
||||
private DateTime lastActivityTime;
|
||||
private DateTime lastActivityTime;
|
||||
public TimeSpan? GetTotalTimeSpan()
|
||||
{
|
||||
return new TimeSpan(hour, minute, second);
|
||||
}
|
||||
|
||||
|
||||
public TimeSpan? GetElapsedTime()
|
||||
{
|
||||
if (isPaused) return null;
|
||||
|
||||
|
||||
return DateTime.Now - startTime;
|
||||
}
|
||||
|
||||
@@ -336,7 +382,7 @@ namespace Ink_Canvas.Windows
|
||||
SetDarkThemeBorder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 刷新数字和冒号显示的颜色
|
||||
UpdateDigitDisplays();
|
||||
}
|
||||
@@ -379,7 +425,7 @@ namespace Ink_Canvas.Windows
|
||||
SetDigitDisplay("Digit4Display", minute % 10);
|
||||
SetDigitDisplay("Digit5Display", second / 10);
|
||||
SetDigitDisplay("Digit6Display", second % 10);
|
||||
|
||||
|
||||
SetColonDisplay(false);
|
||||
}
|
||||
|
||||
@@ -394,10 +440,10 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
// 计算已经过去的时间
|
||||
TimeSpan elapsedTime = DateTime.Now - startTime;
|
||||
|
||||
|
||||
// 计算新的总时间
|
||||
TimeSpan newTotalTime = new TimeSpan(hour, minute, second);
|
||||
|
||||
|
||||
// 如果新设置的时间小于已经过去的时间,则设置为0
|
||||
if (newTotalTime <= elapsedTime)
|
||||
{
|
||||
@@ -428,10 +474,10 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
// 计算已经过去的时间
|
||||
TimeSpan elapsedTime = DateTime.Now - startTime;
|
||||
|
||||
|
||||
// 计算新的总时间
|
||||
TimeSpan newTotalTime = new TimeSpan(newHour, newMinute, newSecond);
|
||||
|
||||
|
||||
// 如果新设置的时间小于已经过去的时间,则设置为0
|
||||
if (newTotalTime <= elapsedTime)
|
||||
{
|
||||
@@ -456,13 +502,13 @@ namespace Ink_Canvas.Windows
|
||||
public TimeSpan? GetRemainingTime()
|
||||
{
|
||||
if (isPaused) return null;
|
||||
|
||||
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
var totalTimeSpan = new TimeSpan(hour, minute, second);
|
||||
var leftTimeSpan = totalTimeSpan - elapsed;
|
||||
|
||||
|
||||
if (leftTimeSpan.Milliseconds > 0) leftTimeSpan += new TimeSpan(0, 0, 1);
|
||||
|
||||
|
||||
return leftTimeSpan;
|
||||
}
|
||||
|
||||
@@ -486,14 +532,14 @@ namespace Ink_Canvas.Windows
|
||||
if (path != null)
|
||||
{
|
||||
digit = Math.Max(0, Math.Min(9, digit));
|
||||
|
||||
|
||||
string resourceKey = $"Digit{digit}";
|
||||
var geometry = this.FindResource(resourceKey) as Geometry;
|
||||
if (geometry != null)
|
||||
{
|
||||
path.Data = geometry;
|
||||
}
|
||||
|
||||
|
||||
if (isRed)
|
||||
{
|
||||
path.Fill = Brushes.Red;
|
||||
@@ -521,7 +567,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var colon1 = this.FindName("Colon1Display") as TextBlock;
|
||||
var colon2 = this.FindName("Colon2Display") as TextBlock;
|
||||
|
||||
|
||||
if (colon1 != null)
|
||||
{
|
||||
if (isRed)
|
||||
@@ -541,7 +587,7 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (colon2 != null)
|
||||
{
|
||||
if (isRed)
|
||||
@@ -816,15 +862,15 @@ namespace Ink_Canvas.Windows
|
||||
isPaused = false;
|
||||
isTimerRunning = true;
|
||||
isOvertimeMode = false;
|
||||
hasPlayedProgressiveReminder = false;
|
||||
hasPlayedProgressiveReminder = false;
|
||||
timer.Start();
|
||||
|
||||
|
||||
// 启动隐藏定时器
|
||||
hideTimer.Start();
|
||||
|
||||
// 保存到最近计时记录
|
||||
SaveRecentTimer();
|
||||
|
||||
|
||||
// 启用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
@@ -836,31 +882,31 @@ namespace Ink_Canvas.Windows
|
||||
private void Reset_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
|
||||
|
||||
if (isTimerRunning)
|
||||
{
|
||||
// 停止计时器
|
||||
timer.Stop();
|
||||
isTimerRunning = false;
|
||||
isPaused = false;
|
||||
|
||||
|
||||
if (hideTimer != null)
|
||||
{
|
||||
hideTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UpdateDigitDisplays();
|
||||
SetColonDisplay(false);
|
||||
|
||||
|
||||
if (StartPauseIcon != null)
|
||||
{
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
}
|
||||
|
||||
|
||||
isOvertimeMode = false;
|
||||
hasPlayedProgressiveReminder = false;
|
||||
|
||||
|
||||
// 禁用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
@@ -1146,10 +1192,10 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
// 如果存在重复,将其移到最前面
|
||||
string duplicateTimer = GetRecentTimerByIndex(existingIndex);
|
||||
|
||||
|
||||
// 移除重复项
|
||||
RemoveRecentTimerByIndex(existingIndex);
|
||||
|
||||
|
||||
// 将重复项添加到最前面
|
||||
recentTimer6 = recentTimer5;
|
||||
recentTimer5 = recentTimer4;
|
||||
@@ -1296,7 +1342,7 @@ namespace Ink_Canvas.Windows
|
||||
recentTimer6 = "--:--";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
recentTimer1 = "--:--";
|
||||
recentTimer2 = "--:--";
|
||||
@@ -1354,9 +1400,9 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
|
||||
private FullscreenTimerWindow fullscreenWindow;
|
||||
|
||||
|
||||
public bool IsFullscreenWindowOpen => fullscreenWindow != null && fullscreenWindow.IsVisible;
|
||||
|
||||
|
||||
private void Fullscreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (fullscreenWindow != null && fullscreenWindow.IsVisible)
|
||||
@@ -1365,7 +1411,7 @@ namespace Ink_Canvas.Windows
|
||||
fullscreenWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (isTimerRunning && !isPaused)
|
||||
{
|
||||
fullscreenWindow = new FullscreenTimerWindow(this);
|
||||
@@ -1374,7 +1420,7 @@ namespace Ink_Canvas.Windows
|
||||
HideMinimizedRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MainBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
@@ -1393,7 +1439,7 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
UpdateActivityTime();
|
||||
@@ -1412,25 +1458,25 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool isDragging = false;
|
||||
private Point dragStartPoint;
|
||||
private Point containerStartPosition;
|
||||
|
||||
|
||||
private void DragTimerContainer(MainWindow mainWindow, Point startPoint, MouseButtonEventArgs e)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer == null) return;
|
||||
|
||||
|
||||
isDragging = true;
|
||||
dragStartPoint = startPoint;
|
||||
|
||||
if (timerContainer.HorizontalAlignment == HorizontalAlignment.Center ||
|
||||
|
||||
if (timerContainer.HorizontalAlignment == HorizontalAlignment.Center ||
|
||||
timerContainer.VerticalAlignment == VerticalAlignment.Center)
|
||||
{
|
||||
var timerPoint = timerContainer.TransformToAncestor(mainWindow).Transform(new Point(0, 0));
|
||||
containerStartPosition = new Point(timerPoint.X, timerPoint.Y);
|
||||
|
||||
|
||||
timerContainer.Margin = new Thickness(containerStartPosition.X, containerStartPosition.Y, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
@@ -1439,44 +1485,44 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
var margin = timerContainer.Margin;
|
||||
containerStartPosition = new Point(margin.Left, margin.Top);
|
||||
|
||||
|
||||
if (double.IsNaN(containerStartPosition.X) || containerStartPosition.X < 0) containerStartPosition.X = 0;
|
||||
if (double.IsNaN(containerStartPosition.Y) || containerStartPosition.Y < 0) containerStartPosition.Y = 0;
|
||||
}
|
||||
|
||||
|
||||
timerContainer.CaptureMouse();
|
||||
timerContainer.MouseMove += TimerContainer_MouseMove;
|
||||
timerContainer.MouseLeftButtonUp += TimerContainer_MouseLeftButtonUp;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
private void TimerContainer_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isDragging) return;
|
||||
|
||||
|
||||
UpdateActivityTime();
|
||||
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
if (timerContainer == null) return;
|
||||
|
||||
|
||||
var currentPoint = e.GetPosition(mainWindow);
|
||||
var deltaX = currentPoint.X - dragStartPoint.X;
|
||||
var deltaY = currentPoint.Y - dragStartPoint.Y;
|
||||
|
||||
|
||||
var newX = containerStartPosition.X + deltaX;
|
||||
var newY = containerStartPosition.Y + deltaY;
|
||||
|
||||
|
||||
if (newX < 0) newX = 0;
|
||||
if (newY < 0) newY = 0;
|
||||
|
||||
|
||||
timerContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
timerContainer.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
timerContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
|
||||
|
||||
if (minimizedContainer != null && minimizedContainer.Visibility == Visibility.Visible)
|
||||
{
|
||||
minimizedContainer.Margin = new Thickness(newX, newY, 0, 0);
|
||||
@@ -1484,16 +1530,16 @@ namespace Ink_Canvas.Windows
|
||||
minimizedContainer.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void TimerContainer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!isDragging) return;
|
||||
|
||||
|
||||
isDragging = false;
|
||||
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow == null) return;
|
||||
|
||||
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
if (timerContainer != null)
|
||||
{
|
||||
@@ -1502,19 +1548,85 @@ namespace Ink_Canvas.Windows
|
||||
timerContainer.MouseLeftButtonUp -= TimerContainer_MouseLeftButtonUp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void HandleTimerCompletion()
|
||||
{
|
||||
// 计时器结束时,如果显示的是最小化视图,恢复到主窗口视图
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
// 如果最小化视图可见,恢复到主窗口视图
|
||||
if (minimizedContainer != null && minimizedContainer.Visibility == Visibility.Visible)
|
||||
{
|
||||
HideMinimizedRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 重置计时器状态
|
||||
ResetTimerState();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 重置计时器状态
|
||||
/// </summary>
|
||||
public void ResetTimerState()
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
// 停止计时器
|
||||
if (isTimerRunning)
|
||||
{
|
||||
timer.Stop();
|
||||
isTimerRunning = false;
|
||||
isPaused = false;
|
||||
|
||||
if (hideTimer != null)
|
||||
{
|
||||
hideTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// 重置时间到默认值
|
||||
hour = 0;
|
||||
minute = 5;
|
||||
second = 0;
|
||||
|
||||
// 更新显示
|
||||
UpdateDigitDisplays();
|
||||
SetColonDisplay(false);
|
||||
|
||||
// 重置图标
|
||||
if (StartPauseIcon != null)
|
||||
{
|
||||
StartPauseIcon.Data = Geometry.Parse(PlayIconData);
|
||||
}
|
||||
|
||||
// 重置状态标志
|
||||
isOvertimeMode = false;
|
||||
hasPlayedProgressiveReminder = false;
|
||||
|
||||
// 禁用全屏按钮
|
||||
if (FullscreenBtn != null)
|
||||
{
|
||||
FullscreenBtn.IsEnabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void HideTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (!isTimerRunning || isPaused) return;
|
||||
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var timeSinceLastActivity = DateTime.Now - lastActivityTime;
|
||||
|
||||
|
||||
if (timeSinceLastActivity.TotalSeconds >= 5)
|
||||
{
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
@@ -1529,17 +1641,17 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void UpdateActivityTime()
|
||||
{
|
||||
lastActivityTime = DateTime.Now;
|
||||
|
||||
|
||||
var mainWindow = Application.Current.MainWindow as MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
var timerContainer = mainWindow.FindName("TimerContainer") as FrameworkElement;
|
||||
var minimizedContainer = mainWindow.FindName("MinimizedTimerContainer") as FrameworkElement;
|
||||
|
||||
|
||||
if (timerContainer != null && minimizedContainer != null)
|
||||
{
|
||||
if (timerContainer.Visibility == Visibility.Collapsed && minimizedContainer.Visibility == Visibility.Visible)
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="#doc-CJKmkp" title="Documentation">📖</a> <a href="#code-CJKmkp" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="#doc-CJKmkp" title="Documentation">📖</a> <a href="#code-CJKmkp" title="Code">💻</a> <a href="#design-CJKmkp" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CreeperAWA"><img src="https://avatars.githubusercontent.com/u/134939494?v=4?s=100" width="100px;" alt="CreeperAWA"/><br /><sub><b>CreeperAWA</b></sub></a><br /><a href="#code-CreeperAWA" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/2-2-3-trimethylpentane"><img src="https://avatars.githubusercontent.com/u/141403762?v=4?s=100" width="100px;" alt="2,2,3-三甲基戊烷"/><br /><sub><b>2,2,3-三甲基戊烷</b></sub></a><br /><a href="#blog-2-2-3-trimethylpentane" title="Blogposts">📝</a> <a href="#doc-2-2-3-trimethylpentane" title="Documentation">📖</a> <a href="#design-2-2-3-trimethylpentane" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alan-CRL"><img src="https://avatars.githubusercontent.com/u/92425617?v=4?s=100" width="100px;" alt="Alan-CRL"/><br /><sub><b>Alan-CRL</b></sub></a><br /><a href="#code-Alan-CRL" title="Code">💻</a> <a href="#infra-Alan-CRL" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#doc-Alan-CRL" title="Documentation">📖</a> <a href="#financial-Alan-CRL" title="Financial">💵</a></td>
|
||||
@@ -86,6 +86,7 @@
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://blog.jursin.top"><img src="https://avatars.githubusercontent.com/u/127487914?v=4?s=100" width="100px;" alt="Jursin"/><br /><sub><b>Jursin</b></sub></a><br /><a href="#design-Jursin" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tayasui-rainnya"><img src="https://avatars.githubusercontent.com/u/156585442?v=4?s=100" width="100px;" alt="tayasui rainnya!"/><br /><sub><b>tayasui rainnya!</b></sub></a><br /><a href="#design-Tayasui-rainnya" title="Design">🎨</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doudou0720"><img src="https://avatars.githubusercontent.com/u/98651603?v=4?s=100" width="100px;" alt="doudou0720"/><br /><sub><b>doudou0720</b></sub></a><br /><a href="#code-doudou0720" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
; 脚本由 Inno Setup 脚本向导生成。
|
||||
; 有关创建 Inno Setup 脚本文件的详细信息,请参阅帮助文档!
|
||||
|
||||
#define MyAppName "InkCanvasForClass CE"
|
||||
#define MyAppVersion "1.7.18.1"
|
||||
#define MyAppPublisher "CJK_mkp"
|
||||
#define MyAppURL "https://inkcanvasforclass.github.io"
|
||||
#define MyAppExeName "InkCanvasForClass.exe"
|
||||
#define MyAppAssocName MyAppName + ""
|
||||
#define MyAppAssocExt ".exe"
|
||||
#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt
|
||||
|
||||
[Setup]
|
||||
; 注意:AppId 的值唯一标识此应用程序。不要在其他应用程序的安装程序中使用相同的 AppId 值。
|
||||
; (若要生成新的 GUID,请在 IDE 中单击 "工具|生成 GUID"。)
|
||||
AppId={{CA801226-FD02-4C78-BCF8-753B38E70CB3}}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={autopf}\{#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
ChangesAssociations=yes
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
LicenseFile=LICENSE
|
||||
; 取消注释以下行以在非管理员安装模式下运行 (仅为当前用户安装)。
|
||||
;PrivilegesRequired=lowest
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
OutputDir=.
|
||||
OutputBaseFilename=InkCanvasForClass CE Setup
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
|
||||
[Languages]
|
||||
Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
||||
Name: "english"; MessagesFile: "compiler:Languages\EnglishBritish.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "release\InkCanvasForClass.exe.config"; DestDir: "{app}"; Flags: ignoreversion
|
||||
; 注意:不要在任何共享系统文件上使用 "Flags: ignoreversion"
|
||||
|
||||
[Registry]
|
||||
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
|
||||
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
|
||||
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
|
||||
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||
@@ -0,0 +1,95 @@
|
||||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
|
||||
[changelog]
|
||||
# A Tera template to be rendered for each release in the changelog.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | striptags | trim | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
|
||||
{% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{{ commit.message | upper_first }} ({{commit.id}} by {{commit.author.name}})\
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
"""
|
||||
# Remove leading and trailing whitespaces from the changelog's body.
|
||||
trim = true
|
||||
# Render body even when there are no releases to process.
|
||||
render_always = true
|
||||
# An array of regex based postprocessors to modify the changelog.
|
||||
postprocessors = [
|
||||
# Replace the placeholder <REPO> with a URL.
|
||||
#{ pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" },
|
||||
]
|
||||
# render body even when there are no releases to process
|
||||
# render_always = true
|
||||
# output file path
|
||||
# output = "test.md"
|
||||
|
||||
[git]
|
||||
# Parse commits according to the conventional commits specification.
|
||||
# See https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# Exclude commits that do not match the conventional commits specification.
|
||||
filter_unconventional = true
|
||||
# Require all commits to be conventional.
|
||||
# Takes precedence over filter_unconventional.
|
||||
require_conventional = false
|
||||
# Split commits on newlines, treating each line as an individual commit.
|
||||
split_commits = false
|
||||
# An array of regex based parsers to modify commit messages prior to further processing.
|
||||
commit_preprocessors = [
|
||||
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
|
||||
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
|
||||
# Check spelling of the commit message using https://github.com/crate-ci/typos.
|
||||
# If the spelling is incorrect, it will be fixed automatically.
|
||||
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
|
||||
]
|
||||
# Prevent commits that are breaking from being excluded by commit parsers.
|
||||
protect_breaking_commits = false
|
||||
# An array of regex based parsers for extracting data from the commit message.
|
||||
# Assigns commits to groups.
|
||||
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
|
||||
|
||||
commit_parsers = [
|
||||
{ message = "^(add|新增|添加)", group = "<!-- 0 -->🚀 新增功能" },
|
||||
{ message = "^(fix|修复)", group = "<!-- 1 -->🐛 修复" },
|
||||
{ message = "^doc", group = "<!-- 5 -->📚 文档更改" },
|
||||
{ message = "^(improve|改进|优化)", group = "<!-- 3 -->⚡ 体验优化" },
|
||||
{ message = "^refactor", group = "<!-- 2 -->🚜 重构" },
|
||||
{ message = "^style", group = "<!-- 6 -->🎨 格式化" },
|
||||
{ message = "^(delete|删除|移除)", group = "<!-- 4 -->❌ 删除功能" },
|
||||
{ message = "^test", group = "<!-- 7 -->🧪 测试" },
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true },
|
||||
{ message = "^chore\\(deps.*\\)", skip = true },
|
||||
{ message = "^chore\\(pr\\)", skip = true },
|
||||
{ message = "^chore\\(pull\\)", skip = true },
|
||||
{ message = "^chore|^ci", group = "<!-- 8 -->⚙️ 杂项" },
|
||||
{ body = ".*security", group = "<!-- 9 -->🛡️ 安全" },
|
||||
{ message = "^revert", group = "<!-- 10 -->◀️ 回退" },
|
||||
{ message = "(版本|version|更新版本号)", group = "<!-- 11 -->🎉 版本号更新" },
|
||||
{ message = ".*", group = "<!-- 12 -->💼 其他更改" },
|
||||
]
|
||||
# Exclude commits that are not matched by any commit parser.
|
||||
filter_commits = false
|
||||
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
|
||||
link_parsers = []
|
||||
# Include only the tags that belong to the current branch.
|
||||
use_branch_tags = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order_commits = true
|
||||
# Order of commits in each group/release within the changelog.
|
||||
# Allowed values: newest, oldest
|
||||
sort_commits = "oldest"
|
||||
# Process submodules commits
|
||||
recurse_submodules = false
|
||||
Reference in New Issue
Block a user