diff --git a/.all-contributorsrc b/.all-contributorsrc deleted file mode 100644 index 41dcfa6e..00000000 --- a/.all-contributorsrc +++ /dev/null @@ -1,93 +0,0 @@ -{ - "projectName": "community", - "projectOwner": "InkCanvasForClass", - "files": [ - "README.md" - ], - "commitType": "docs", - "commitConvention": "angular", - "contributorsPerLine": 7, - "contributors": [ - { - "login": "CJKmkp", - "name": "CJK_mkp", - "avatar_url": "https://avatars.githubusercontent.com/u/113243675?v=4", - "profile": "https://github.com/CJKmkp", - "contributions": [ - "maintenance", - "doc", - "code" - ] - }, - { - "login": "Hydro11451", - "name": "Hydrogen", - "avatar_url": "https://avatars.githubusercontent.com/u/214308559?v=4", - "profile": "http://hydro11451.qzz.io", - "contributions": [ - "code" - ] - }, - { - "login": "CreeperAWA", - "name": "CreeperAWA", - "avatar_url": "https://avatars.githubusercontent.com/u/134939494?v=4", - "profile": "https://github.com/CreeperAWA", - "contributions": [ - "code" - ] - }, - { - "login": "2-2-3-trimethylpentane", - "name": "2,2,3-三甲基戊烷", - "avatar_url": "https://avatars.githubusercontent.com/u/141403762?v=4", - "profile": "https://github.com/2-2-3-trimethylpentane", - "contributions": [ - "blog", - "doc", - "design" - ] - }, - { - "login": "Alan-CRL", - "name": "Alan-CRL", - "avatar_url": "https://avatars.githubusercontent.com/u/92425617?v=4", - "profile": "https://github.com/Alan-CRL", - "contributions": [ - "code", - "infra", - "doc", - "financial" - ] - }, - { - "login": "MKStoler1024", - "name": "MKStoler1024", - "avatar_url": "https://avatars.githubusercontent.com/u/158786854?v=4", - "profile": "https://github.com/MKStoler1024", - "contributions": [ - "doc", - "code", - "design" - ] - }, - { - "login": "awesome-iwb", - "name": "Awesome Iwb", - "avatar_url": "https://avatars.githubusercontent.com/u/184760810?v=4", - "profile": "https://github.com/awesome-iwb", - "contributions": [ - "doc" - ] - }, - { - "login": "PrefacedCorg", - "name": "PrefacedCorg", - "avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4", - "profile": "https://github.com/PrefacedCorg", - "contributions": [ - "code" - ] - } - ] -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..27d4f745 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +## Description + + +## Reproduction + + +## Expected behavior + + +## Screenshots + + +## Additional context diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index f6c695cc..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Bug 报告 | Bug Report -description: 反馈软件缺陷或异常 | Report a bug to help us improve -labels: [bug] -body: - - type: markdown - attributes: - value: | - 感谢你的反馈!请详细填写以下内容,便于我们定位问题。 - Thank you for your feedback! Please fill out the following information to help us locate the issue. - - type: input - id: version - attributes: - label: 软件版本 | App Version - description: 可在设置中的“关于”界面查看 | You can find it on the "About" interface in the settings - placeholder: 例如 v1.2.3 | e.g. v1.2.3 - validations: - required: true - - type: input - id: os - attributes: - label: 操作系统及版本 | OS & Version - placeholder: 例如 Windows 10 22H2 64位 | e.g. Windows 10 22H2 64bit - validations: - required: true - - type: textarea - id: description - attributes: - label: 问题描述 | Description - description: 简要描述遇到的问题 | Briefly describe the problem - validations: - required: true - - type: textarea - id: steps - attributes: - label: 复现步骤 | Steps to Reproduce - description: 如何复现该问题?如有必要可附截图/录屏 | How to reproduce this bug? Screenshots/recordings if needed - placeholder: | - 1. - 2. - 3. - validations: - required: false - - type: textarea - id: expected - attributes: - label: 期望结果 | Expected Behavior - description: 你期望的正确行为或结果 | What did you expect to happen? - validations: - required: false - - type: textarea - id: extra - attributes: - label: 其他补充信息 | Additional Info - description: 其他相关信息(如日志、配置、特殊环境等)| Any other context, logs, configs, special environment, etc. - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 3ba13e0c..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..f909f8a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,10 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +## Description diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index ac8291f1..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: 功能请求 | Feature Request -description: 提出你对本项目的功能建议 | Suggest an idea for this project -labels: [enhancement] -body: - - type: markdown - attributes: - value: | - 感谢你的建议!请详细描述你的需求。 - Thank you for your suggestion! Please describe your needs in detail. - - type: textarea - id: description - attributes: - label: 功能描述 | Description - description: 请描述你希望添加的功能 | Describe the feature you want - validations: - required: true - - type: textarea - id: motivation - attributes: - label: 需求动机 | Motivation - description: 为什么需要这个功能?| Why do you need this feature? - validations: - required: false - - type: textarea - id: design - attributes: - label: 期望设计 | Expected Design - description: (可选)描述或画出你期望的界面或交互 | (Optional) Describe or sketch the expected UI/UX - validations: - required: false - - type: textarea - id: extra - attributes: - label: 其他补充信息 | Additional Info - description: 其他补充说明或建议 | Any other context or suggestions - validations: - required: false diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml deleted file mode 100644 index f1df45fb..00000000 --- a/.github/workflows/dotnet-desktop.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: .NET Build - -on: - push: - branches: [ main,beta ] - pull_request: - branches: [ main ] - -jobs: - - build: - runs-on: windows-latest - - steps: - - uses: actions/checkout@v4.2.2 - - - 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: Build the Solution - run: | - msbuild -t:restore /p:GitFlow="Github Action" - msbuild /p:platform="Any CPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj" - - - name: Upload to artifact - uses: actions/upload-artifact@v4.5.0 - with: - name: InkCanvasForClass - path: "Ink Canvas/bin/Any CPU/Release/net472/" diff --git a/.gitignore b/.gitignore index 2b9a7ea6..eb0f4f36 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,16 @@ -obj/ -bin/ -.vs/ -/Ink Canvas/obj +InkCanvasForClass/obj +InkCanvasForClass/bin +InkCanvasForClassX/obj +InkCanvasForClassX/bin +InkCanvasForClass.IACoreHelper/obj +InkCanvasForClass.IACoreHelper/bin +InkCanvasForClass.PowerPoint.InteropHelper/obj +InkCanvasForClass.PowerPoint.InteropHelper/bin +InkCanvasForClass.PowerPoint.VstoPlugin/obj +InkCanvasForClass.PowerPoint.VstoPlugin/bin +InkCanvasForClass.IccInkCanvas/obj +InkCanvasForClass.IccInkCanvas/bin +InkCanvasForClass.IccInkCanvas.Demo/obj +InkCanvasForClass.IccInkCanvas.Demo/bin +.vs +.idea \ No newline at end of file diff --git a/.idea/.idea.Ink Canvas/.idea/.gitignore b/.idea/.idea.Ink Canvas/.idea/.gitignore index b4ff7879..3383a903 100644 --- a/.idea/.idea.Ink Canvas/.idea/.gitignore +++ b/.idea/.idea.Ink Canvas/.idea/.gitignore @@ -1,12 +1,12 @@ -# Default ignored files +# 默认忽略的文件 /shelf/ /workspace.xml -# Rider ignored files +# Rider 忽略的文件 +/contentModel.xml /projectSettingsUpdater.xml /modules.xml -/contentModel.xml /.idea.Ink Canvas.iml -# Editor-based HTTP Client requests +# 基于编辑器的 HTTP 客户端请求 /httpRequests/ # Datasource local storage ignored files /dataSources/ diff --git a/.idea/.idea.Ink Canvas/.idea/indexLayout.xml b/.idea/.idea.Ink Canvas/.idea/indexLayout.xml index dd163eb2..7b08163c 100644 --- a/.idea/.idea.Ink Canvas/.idea/indexLayout.xml +++ b/.idea/.idea.Ink Canvas/.idea/indexLayout.xml @@ -1,9 +1,7 @@ - - ../../ICC CE main - + diff --git a/.idea/.idea.Ink Canvas/.idea/vcs.xml b/.idea/.idea.Ink Canvas/.idea/vcs.xml index 35eb1ddf..94a25f7f 100644 --- a/.idea/.idea.Ink Canvas/.idea/vcs.xml +++ b/.idea/.idea.Ink Canvas/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt deleted file mode 100644 index f2f7432d..00000000 --- a/AutomaticUpdateVersionControl.txt +++ /dev/null @@ -1 +0,0 @@ -1.7.6.0 diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 00000000..784ef4ff --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,36 @@ +rev-1.0.0.0: +1. 重写大部分代码中... + +5.0.5.0: +1. 支持自动收纳希沃轻白板5C,希沃白板3,鸿合轻量白板。 +2. 支持自动查杀鸿合屏幕书写。 +3. 带来FitToCurve,给平滑墨迹加上了开关。 +4. 修复了FitToCurve导致的部分形状绘制出错的问题也保留了其他墨迹的FitToCurve。 +5. 白板,浮动工具栏,子面板,设置UI几乎全部优化。 +6. 添加修改橡皮大小,橡皮形状的菜单。 +7. PPT现在会自动提示是否关闭自动播放和排练计时。 +8. 条件性的墨迹识别,可以关闭部分不需要的形状识别。可以修改是否为墨迹识别的三角形或矩形应用模拟压感值。 +9. 带来了荧光笔功能并为荧光笔适配了形状识别。 +10. 允许清空墨迹时可删除历史记录。 +11. 带来了Quick Panel。 +12. 浮动工具栏缩放调节,取消收纳按钮图标修改。 +13. 修复了多指书写StylusDown事件可能触发在工具栏而不是inkCanvas上导致的Bug。 +14. 修复了鼠标和触摸屏(无RealTimeStylus和非多指书写模式)的绘制时Pointer捕获Bug。 +15. Merge了Ink Canvas的新撤回行为 +16. 修复了白板模式下直接新增页面导致的TimeMachine不记录历史记录的Bug +17. 添加了一个简陋的白板页面列表。 +18. 优化了墨迹重播,支持暂停,重新开始和倍速,拥有更人性化的UI。 +19. 优化自动收纳行为和FoldFloatingBar函数的相关Bug修复。 +20. 添加了EdgeGestureUtil实验性选项。 +21. 添加了ForceFullScreen实验性选项 +22. 添加了FullScreenHelper实验性选项。 +23. 添加了ResolutionChangeDetection和DPIChangeDetection实验性选项。 +24. 修复了同时打开ICC和ICA的冲突。 +25. 修复浮动工具栏手势按钮开启多指书写时InkCanvas的InkCanvasEditingMode被修改为None的Bug。 +26. 修复Merge ICA仓库代码后导致浮动工具栏批注按钮UI MouseLeave反馈的问题。 +27. 为大部分UI的按钮添加了lastBorderMouseDown的检测代码和MouseDown的视觉反馈。 +28. 支持自动收纳安道系列,艺云系列和 MAXHUB 白板书写。 + +4.5.8.0: +1. 将ICA的代码换了个名字变成了InkCanvasForClass,万物起源。 +2. 优化了ICA的部分UI界面设计。 \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 30434597..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -QQ:2564608840. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/Images/icc ce.png b/Images/icc ce.png deleted file mode 100644 index cab75f9f..00000000 Binary files a/Images/icc ce.png and /dev/null differ diff --git a/Ink Canvas.sln b/Ink Canvas.sln index f100002a..4cb9c4f9 100644 --- a/Ink Canvas.sln +++ b/Ink Canvas.sln @@ -3,7 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33530.505 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "Ink Canvas\InkCanvasForClass.csproj", "{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClassX", "InkCanvasForClassX\InkCanvasForClassX.csproj", "{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "InkCanvasForClass\InkCanvasForClass.csproj", "{2474F5B0-6FA7-4D70-8A00-167BBB03264D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IACoreHelper", "InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj", "{693973B6-69C1-4A37-B329-F366A07BF60A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.PowerPoint.InteropHelper", "InkCanvasForClass.PowerPoint.InteropHelper\InkCanvasForClass.PowerPoint.InteropHelper.csproj", "{2D8A9217-465A-4F57-BD58-CE02450390C4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.PowerPoint.VstoPlugin", "InkCanvasForClass.PowerPoint.VstoPlugin\InkCanvasForClass.PowerPoint.VstoPlugin.csproj", "{8C593467-E54D-4FA7-881C-78F3CC48A867}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IccInkCanvas", "InkCanvasForClass.IccInkCanvas\InkCanvasForClass.IccInkCanvas.csproj", "{43929D8F-5630-4786-B75D-E203EA3E992F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IccInkCanvas.Demo", "InkCanvasForClass.IccInkCanvas.Demo\InkCanvasForClass.IccInkCanvas.Demo.csproj", "{94E97F70-CACD-453B-8114-08DFFDDA5A46}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -17,28 +29,223 @@ Global Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 + x86 Debug|Any CPU = x86 Debug|Any CPU + x86 Debug|ARM = x86 Debug|ARM + x86 Debug|ARM64 = x86 Debug|ARM64 + x86 Debug|x64 = x86 Debug|x64 + x86 Debug|x86 = x86 Debug|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.ActiveCfg = Debug|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.Build.0 = Debug|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.Build.0 = Debug|ARM64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.ActiveCfg = Debug|x64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.Build.0 = Debug|x64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.ActiveCfg = Debug|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.Build.0 = Debug|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.Build.0 = Release|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.ActiveCfg = Release|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.Build.0 = Release|Any CPU - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.ActiveCfg = Release|ARM64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.Build.0 = Release|ARM64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.ActiveCfg = Release|x64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|x64 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|x86 - {8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|x86 + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM64.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x64.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x64.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x86.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x86.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|Any CPU.Build.0 = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM.ActiveCfg = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM.Build.0 = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM64.ActiveCfg = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM64.Build.0 = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x64.ActiveCfg = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x64.Build.0 = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x86.ActiveCfg = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x86.Build.0 = Release|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM64.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x64.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x64.Build.0 = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x86.ActiveCfg = Debug|Any CPU + {98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x86.Build.0 = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM.Build.0 = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM64.Build.0 = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x64.ActiveCfg = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x64.Build.0 = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x86.ActiveCfg = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x86.Build.0 = Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|Any CPU.Build.0 = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM.ActiveCfg = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM.Build.0 = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM64.ActiveCfg = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM64.Build.0 = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x64.ActiveCfg = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x64.Build.0 = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x86.ActiveCfg = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x86.Build.0 = Release|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|Any CPU.ActiveCfg = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|Any CPU.Build.0 = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM.ActiveCfg = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM.Build.0 = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM64.ActiveCfg = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM64.Build.0 = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x64.ActiveCfg = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x64.Build.0 = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x86.ActiveCfg = x86 Debug|Any CPU + {2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x86.Build.0 = x86 Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM64.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x64.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x64.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x86.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x86.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|Any CPU.Build.0 = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM.ActiveCfg = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM.Build.0 = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM64.ActiveCfg = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM64.Build.0 = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x64.ActiveCfg = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x64.Build.0 = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x86.ActiveCfg = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x86.Build.0 = Release|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM64.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x64.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x64.Build.0 = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x86.ActiveCfg = Debug|Any CPU + {693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x86.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM64.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x64.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x86.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|Any CPU.Build.0 = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM.ActiveCfg = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM.Build.0 = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM64.ActiveCfg = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM64.Build.0 = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x64.ActiveCfg = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x64.Build.0 = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x86.ActiveCfg = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x86.Build.0 = Release|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM64.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x64.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x64.Build.0 = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x86.ActiveCfg = Debug|Any CPU + {2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x86.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM64.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x64.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x64.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x86.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x86.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|Any CPU.Build.0 = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM.ActiveCfg = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM.Build.0 = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM64.ActiveCfg = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM64.Build.0 = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x64.ActiveCfg = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x64.Build.0 = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x86.ActiveCfg = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x86.Build.0 = Release|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM64.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x64.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x64.Build.0 = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x86.ActiveCfg = Debug|Any CPU + {8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x86.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM64.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x64.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x64.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x86.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x86.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|Any CPU.Build.0 = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM.ActiveCfg = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM.Build.0 = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM64.ActiveCfg = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM64.Build.0 = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x64.ActiveCfg = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x64.Build.0 = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x86.ActiveCfg = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x86.Build.0 = Release|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM64.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x64.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x64.Build.0 = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x86.ActiveCfg = Debug|Any CPU + {43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x86.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM64.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x64.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x64.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x86.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x86.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|Any CPU.Build.0 = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM.ActiveCfg = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM.Build.0 = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM64.ActiveCfg = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM64.Build.0 = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x64.ActiveCfg = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x64.Build.0 = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x86.ActiveCfg = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x86.Build.0 = Release|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM64.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x64.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x64.Build.0 = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x86.ActiveCfg = Debug|Any CPU + {94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x86.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ink Canvas.sln.DotSettings.user b/Ink Canvas.sln.DotSettings.user index 0401f804..61f8f815 100644 --- a/Ink Canvas.sln.DotSettings.user +++ b/Ink Canvas.sln.DotSettings.user @@ -1,4 +1,5 @@  WARNING - C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe - 1114112 \ No newline at end of file + <AssemblyExplorer> + <Assembly Path="D:\vs\ica\InkCanvasForClass\IAWinFX.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs deleted file mode 100644 index 299c22e5..00000000 --- a/Ink Canvas/App.xaml.cs +++ /dev/null @@ -1,1213 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; -using System.Threading; -using System.Windows; -using System.Windows.Forms; -using System.Windows.Input; -using System.Windows.Threading; -using Hardcodet.Wpf.TaskbarNotification; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; -using Microsoft.Win32; -using Newtonsoft.Json; -using Application = System.Windows.Application; -using MessageBox = System.Windows.MessageBox; -using Timer = System.Threading.Timer; - -namespace Ink_Canvas -{ - /// - /// Interaction logic for App.xaml - /// - public partial class App : Application - { - Mutex mutex; - - public static string[] StartArgs; - public static string RootPath = Environment.GetEnvironmentVariable("APPDATA") + "\\Ink Canvas\\"; - - // 新增:保存看门狗进程对象 - private static Process watchdogProcess; - // 新增:标记是否为软件内主动退出 - public static bool IsAppExitByUser; - // 新增:退出信号文件路径 - private static string watchdogExitSignalFile = Path.Combine(Path.GetTempPath(), "icc_watchdog_exit_" + Process.GetCurrentProcess().Id + ".flag"); - // 新增:崩溃日志文件路径 - private static string crashLogFile = Path.Combine(Environment.GetEnvironmentVariable("APPDATA"), "Ink Canvas", "crash_logs"); - // 新增:进程ID - private static int currentProcessId = Process.GetCurrentProcess().Id; - // 新增:应用启动时间 - private static DateTime appStartTime = DateTime.Now; - // 新增:最后一次错误信息 - private static string lastErrorMessage = string.Empty; - // 新增:是否已初始化崩溃监听器 - private static bool crashListenersInitialized; - - public App() - { - // 配置TLS协议以支持Windows 7 - ConfigureTlsForWindows7(); - - // 如果是看门狗子进程,直接进入看门狗主循环并终止主流程 - var args = Environment.GetCommandLineArgs(); - if (args.Length >= 2 && args[1] == "--watchdog") - { - RunWatchdogIfNeeded(); - Environment.Exit(0); - return; - } - - // 启动时优先同步设置,确保CrashAction为最新 - SyncCrashActionFromSettings(); - - Startup += App_Startup; - DispatcherUnhandledException += App_DispatcherUnhandledException; - StartHeartbeatMonitor(); - - // 新增:初始化全局异常和进程结束处理 - InitializeCrashListeners(); - - // 仅在崩溃后操作为静默重启时才启动看门狗 - if (CrashAction == CrashActionType.SilentRestart) - { - StartWatchdogIfNeeded(); - } - Exit += App_Exit; // 注册退出事件 - } - - // 新增:配置TLS协议以支持Windows 7 - private void ConfigureTlsForWindows7() - { - try - { - // 检测操作系统版本 - var osVersion = Environment.OSVersion; - bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1; - - if (isWindows7) - { - LogHelper.WriteLogToFile("检测到Windows 7系统,配置TLS协议支持"); - - // 启用所有TLS版本以支持Windows 7 - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - - // 配置ServicePointManager以支持Windows 7 - ServicePointManager.DefaultConnectionLimit = 10; - ServicePointManager.Expect100Continue = false; - ServicePointManager.UseNagleAlgorithm = false; - - LogHelper.WriteLogToFile("TLS协议配置完成,已启用TLS 1.2/1.1/1.0支持"); - } - else - { - // 对于更新的Windows版本,不进行任何TLS配置,使用系统默认设置 - LogHelper.WriteLogToFile($"检测到Windows版本: {osVersion.VersionString},使用系统默认TLS配置"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"配置TLS协议时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 新增:初始化崩溃监听器 - private void InitializeCrashListeners() - { - if (crashListenersInitialized) return; - - try - { - // 确保崩溃日志目录存在 - if (!Directory.Exists(crashLogFile)) - { - Directory.CreateDirectory(crashLogFile); - } - - // 注册非UI线程未处理异常处理程序 - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - // 注册控制台Ctrl+C等终止信号处理 - Console.CancelKeyPress += Console_CancelKeyPress; - - // 注册系统会话结束事件(关机、注销等) - SystemEvents.SessionEnding += SystemEvents_SessionEnding; - - // 注册进程退出处理程序 - AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - - // 尝试注册Windows关闭消息监听 - SetConsoleCtrlHandler(ConsoleCtrlHandler, true); - - // 如果系统支持,添加Windows Management Instrumentation监听器 - try - { - // 使用反射动态加载和调用WMI - TrySetupWmiMonitoring(); - } - catch (Exception wmiEx) - { - LogHelper.WriteLogToFile($"设置WMI进程监控失败: {wmiEx.Message}", LogHelper.LogType.Warning); - } - - crashListenersInitialized = true; - LogHelper.WriteLogToFile("已初始化崩溃监听器"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化崩溃监听器失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 新增:动态加载WMI监控(避免直接引用System.Management) - private void TrySetupWmiMonitoring() - { - try - { - // 检查System.Management程序集是否可用 - var assemblyName = "System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; - var assembly = Assembly.Load(assemblyName); - if (assembly == null) - { - LogHelper.WriteLogToFile("未找到System.Management程序集,跳过WMI监控", LogHelper.LogType.Warning); - return; - } - - // 使用反射创建WMI查询 - var watcherType = assembly.GetType("System.Management.ManagementEventWatcher"); - if (watcherType == null) - { - LogHelper.WriteLogToFile("未找到ManagementEventWatcher类型,跳过WMI监控", LogHelper.LogType.Warning); - return; - } - - // 构建WMI查询字符串 - string queryString = $"SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = {currentProcessId}"; - - // 创建ManagementEventWatcher实例 - object watcher = Activator.CreateInstance(watcherType, queryString); - - // 获取EventArrived事件信息 - var eventInfo = watcherType.GetEvent("EventArrived"); - if (eventInfo == null) - { - LogHelper.WriteLogToFile("未找到EventArrived事件,跳过WMI监控", LogHelper.LogType.Warning); - return; - } - - // 创建委托并订阅事件 - Type delegateType = eventInfo.EventHandlerType; - var handler = Delegate.CreateDelegate(delegateType, this, GetType().GetMethod("WmiEventHandler", BindingFlags.NonPublic | BindingFlags.Instance)); - eventInfo.AddEventHandler(watcher, handler); - - // 启动监听 - var startMethod = watcherType.GetMethod("Start"); - startMethod.Invoke(watcher, null); - - LogHelper.WriteLogToFile("已成功启动WMI进程监控"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"动态加载WMI监控失败: {ex.Message}", LogHelper.LogType.Warning); - } - } - - // WMI事件处理方法(通过反射调用) - private void WmiEventHandler(object sender, EventArgs e) - { - try - { - // 尝试从事件参数中提取信息 - dynamic eventArgs = e; - dynamic newEvent = eventArgs.NewEvent; - if (newEvent != null) - { - dynamic targetInstance = newEvent["TargetInstance"]; - if (targetInstance != null) - { - string processName = targetInstance["Name"]?.ToString() ?? "未知进程"; - WriteCrashLog($"WMI检测到进程{processName}(ID:{currentProcessId})已终止"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理WMI事件时出错: {ex.Message}", LogHelper.LogType.Warning); - } - } - - // 新增:Windows控制台控制处理程序 - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add); - - private delegate bool ConsoleCtrlDelegate(int ctrlType); - - private static bool ConsoleCtrlHandler(int ctrlType) - { - string eventType = "未知控制类型"; - - // 使用传统switch语句替代switch表达式 - switch (ctrlType) - { - case 0: - eventType = "CTRL_C_EVENT"; - break; - case 1: - eventType = "CTRL_BREAK_EVENT"; - break; - case 2: - eventType = "CTRL_CLOSE_EVENT"; - break; - case 5: - eventType = "CTRL_LOGOFF_EVENT"; - break; - case 6: - eventType = "CTRL_SHUTDOWN_EVENT"; - break; - default: - eventType = $"未知控制类型({ctrlType})"; - break; - } - - WriteCrashLog($"接收到系统控制信号: {eventType}"); - - // 返回true表示已处理该事件 - return false; - } - - // 新增:系统会话结束事件处理 - private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) - { - string reason = e.Reason == SessionEndReasons.Logoff ? "用户注销" : "系统关机"; - WriteCrashLog($"系统会话即将结束: {reason}"); - } - - // 新增:控制台取消事件处理 - private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) - { - WriteCrashLog($"接收到控制台中断信号: {e.SpecialKey}"); - e.Cancel = true; // 取消默认处理 - } - - // 新增:处理非UI线程的未处理异常 - private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - try - { - var exception = e.ExceptionObject as Exception; - string errorMessage = exception?.ToString() ?? "未知异常"; - lastErrorMessage = errorMessage; - - WriteCrashLog($"捕获到未处理的异常: {errorMessage}"); - - if (e.IsTerminating) - { - WriteCrashLog("应用程序即将终止"); - } - } - catch (Exception ex) - { - // 尝试在最后时刻记录错误 - try - { - File.AppendAllText( - Path.Combine(crashLogFile, $"critical_error_{DateTime.Now:yyyyMMdd_HHmmss}.log"), - $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 记录未处理异常时发生错误: {ex.Message}\r\n" - ); - } - catch { } - } - } - - // 新增:处理进程退出事件 - private void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - TimeSpan runDuration = DateTime.Now - appStartTime; - WriteCrashLog($"应用程序退出,运行时长: {runDuration}"); - - // 如果有最后错误消息,记录到日志 - if (!string.IsNullOrEmpty(lastErrorMessage)) - { - WriteCrashLog($"最后错误信息: {lastErrorMessage}"); - } - } - - // 新增:记录崩溃日志 - private static void WriteCrashLog(string message) - { - try - { - // 确保目录存在 - if (!Directory.Exists(crashLogFile)) - { - Directory.CreateDirectory(crashLogFile); - } - - string logFileName = Path.Combine(crashLogFile, $"crash_{DateTime.Now:yyyyMMdd}.log"); - - // 收集系统状态信息 - string memoryUsage = (Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024)) + " MB"; - string cpuTime = Process.GetCurrentProcess().TotalProcessorTime.ToString(); - string processUptime = (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString(); - - string statusInfo = $"[内存: {memoryUsage}, CPU时间: {cpuTime}, 运行时长: {processUptime}]"; - - // 写入日志 - File.AppendAllText( - logFileName, - $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [PID:{currentProcessId}] {message}\r\n{statusInfo}\r\n\r\n" - ); - - // 同时记录到主日志 - LogHelper.WriteLogToFile(message, LogHelper.LogType.Error); - } - catch { } - } - - // 增加字段保存崩溃后操作设置 - public static CrashActionType CrashAction = CrashActionType.SilentRestart; - - // 修正:允许静态调用 - public static void SyncCrashActionFromSettings() - { - try - { - // 优先从 Settings.json 直接读取 - var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.json"); - if (File.Exists(settingsPath)) - { - var json = File.ReadAllText(settingsPath); - dynamic obj = JsonConvert.DeserializeObject(json); - int crashAction = 0; - try { crashAction = (int)(obj["startup"]["crashAction"] ?? 0); } catch { } - CrashAction = (CrashActionType)crashAction; - } - // 兜底:从主窗口同步 - else if (Ink_Canvas.MainWindow.Settings != null && Ink_Canvas.MainWindow.Settings.Startup != null) - { - CrashAction = (CrashActionType)Ink_Canvas.MainWindow.Settings.Startup.CrashAction; - } - } - catch { } - } - - private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) - { - Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。"); - LogHelper.NewLog(e.Exception.ToString()); - - // 新增:记录到崩溃日志 - lastErrorMessage = e.Exception.ToString(); - WriteCrashLog($"UI线程未处理异常: {e.Exception}"); - - e.Handled = true; - - SyncCrashActionFromSettings(); // 新增:崩溃时同步最新设置 - - if (CrashAction == CrashActionType.SilentRestart && !IsAppExitByUser) - { - StartupCount.Increment(); - if (StartupCount.GetCount() >= 5) - { - MessageBox.Show("检测到程序已连续重启5次,已停止自动重启。请联系开发者或检查系统环境。", "重启次数过多", MessageBoxButton.OK, MessageBoxImage.Error); - StartupCount.Reset(); - Environment.Exit(1); - } - try - { - string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath); - } - catch { } - Environment.Exit(1); - } - // CrashActionType.NoAction 时不做处理 - } - - private TaskbarIcon _taskbar; - - void App_Startup(object sender, StartupEventArgs e) - { - /*if (!StoreHelper.IsStoreApp) */RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; - - LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version)); - - // 在应用启动时自动释放IACore相关DLL - try - { - Helpers.IACoreDllExtractor.ExtractIACoreDlls(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 记录应用启动(设备标识符) - DeviceIdentifier.RecordAppLaunch(); - LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}"); - LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}"); - LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}"); - - bool ret; - mutex = new Mutex(true, "InkCanvasForClass CE", out ret); - - if (!ret && !e.Args.Contains("-m")) //-m multiple - { - LogHelper.NewLog("Detected existing instance"); - MessageBox.Show("已有一个程序实例正在运行"); - LogHelper.NewLog("Ink Canvas automatically closed"); - IsAppExitByUser = true; // 多开时标记为用户主动退出 - // 写入退出信号,确保看门狗不会重启 - try { - StartupCount.Reset(); - File.WriteAllText(watchdogExitSignalFile, "exit"); - if (watchdogProcess != null && !watchdogProcess.HasExited) - { - watchdogProcess.Kill(); - } - } catch { } - Environment.Exit(0); - } - - _taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon"); - - StartArgs = e.Args; - - // 新增:Office注册表检测 - try - { - LogHelper.WriteLogToFile("开始Office注册表检测"); - - // 检查Office安装 - if (!IsOfficeInstalled()) - { - LogHelper.WriteLogToFile("未检测到Office安装", LogHelper.LogType.Warning); - return; - } - - // 尝试获取所有可能的Office版本路径 - var officeVersions = GetOfficeVersions(); - if (officeVersions.Count == 0) - { - LogHelper.WriteLogToFile("未找到任何Office版本", LogHelper.LogType.Warning); - return; - } - - foreach (var version in officeVersions) - { - string regPath = $"Software\\Microsoft\\Office\\{version}\\Common\\Security"; - LogHelper.WriteLogToFile($"正在处理Office版本 {version}, 注册表路径: {regPath}"); - - try - { - using (RegistryKey baseKey = Registry.CurrentUser.OpenSubKey(regPath)) - { - if (baseKey == null) - { - LogHelper.WriteLogToFile($"注册表路径不存在: {regPath}", LogHelper.LogType.Warning); - // 尝试创建路径 - try - { - using (RegistryKey createKey = Registry.CurrentUser.CreateSubKey(regPath, true)) - { - if (createKey != null) - { - createKey.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile($"创建并设置注册表路径: {regPath}"); - } - } - } - catch (Exception createEx) - { - LogHelper.WriteLogToFile($"创建注册表路径失败: {createEx.Message}", LogHelper.LogType.Error); - } - continue; - } - - // 备份路径更改为软件根目录下的saves/RegistryBackups文件夹 - string backupPath = Path.Combine(RootPath, "saves", "RegistryBackups"); - LogHelper.WriteLogToFile($"备份路径: {backupPath}"); - - if (!Directory.Exists(backupPath)) - { - Directory.CreateDirectory(backupPath); - LogHelper.WriteLogToFile($"创建备份目录: {backupPath}"); - } - - string backupFile = Path.Combine(backupPath, $"SecurityBackup_{version}_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{regPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份注册表值: {valueName} = {value}"); - } - } - - using (RegistryKey key = Registry.CurrentUser.CreateSubKey(regPath, true)) - { - // 仅在值不存在或不等于1时更新 - object currentValue = key.GetValue("DisableProtectedView"); - if (currentValue == null || (int)currentValue != 1) - { - key.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile($"Office {version} 注册表值已设置: DisableProtectedView = 1"); - } - else - { - LogHelper.WriteLogToFile($"Office {version} 注册表值已存在且无需更改: DisableProtectedView = 1"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理Office版本 {version} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 处理Office 365的特殊路径 - TryModifyOffice365Registry(); - } - catch (SecurityException secEx) - { - LogHelper.WriteLogToFile($"安全异常: {secEx.Message}", LogHelper.LogType.Error); - ShowPermissionError(); - } - catch (UnauthorizedAccessException authEx) - { - LogHelper.WriteLogToFile($"访问被拒绝: {authEx.Message}", LogHelper.LogType.Error); - ShowPermissionError(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"未知错误: {ex.GetType().FullName} - {ex.Message}", LogHelper.LogType.Error); - LogHelper.WriteLogToFile(ex.StackTrace); - } - } - - private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) - { - try - { - if (SystemInformation.MouseWheelScrollLines == -1) - e.Handled = false; - else - try - { - ScrollViewerEx SenderScrollViewer = (ScrollViewerEx)sender; - SenderScrollViewer.ScrollToVerticalOffset(SenderScrollViewer.VerticalOffset - e.Delta * 10 * SystemInformation.MouseWheelScrollLines / (double)120); - e.Handled = true; - } - catch { } - } - catch { } - } - - // 新增:用于设置崩溃后操作类型 - public enum CrashActionType - { - SilentRestart, - NoAction - } - - // 心跳相关 - private static Timer heartbeatTimer; - private static DateTime lastHeartbeat = DateTime.Now; - private static Timer watchdogTimer; - - private void StartHeartbeatMonitor() - { - // 主线程定时更新心跳 - heartbeatTimer = new Timer(_ => lastHeartbeat = DateTime.Now, null, 0, 1000); - // 辅助线程检测心跳超时 - watchdogTimer = new Timer(_ => - { - if ((DateTime.Now - lastHeartbeat).TotalSeconds > 10) - { - LogHelper.NewLog("检测到主线程无响应,自动重启。"); - SyncCrashActionFromSettings(); // 新增:心跳检测时同步最新设置 - if (CrashAction == CrashActionType.SilentRestart) - { - StartupCount.Increment(); - if (StartupCount.GetCount() >= 5) - { - MessageBox.Show("检测到程序已连续重启5次,已停止自动重启。请联系开发者或检查系统环境。", "重启次数过多", MessageBoxButton.OK, MessageBoxImage.Error); - StartupCount.Reset(); - Environment.Exit(1); - } - try - { - string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath); - } - catch { } - Environment.Exit(1); - } - } - }, null, 0, 3000); - } - - // 看门狗进程 - private void StartWatchdogIfNeeded() - { - // 避免递归启动 - if (Environment.GetCommandLineArgs().Contains("--watchdog")) return; - // 启动看门狗进程 - string exePath = Process.GetCurrentProcess().MainModule.FileName; - var psi = new ProcessStartInfo - { - FileName = exePath, - Arguments = "--watchdog " + Process.GetCurrentProcess().Id + " \"" + watchdogExitSignalFile + "\"", - CreateNoWindow = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }; - watchdogProcess = Process.Start(psi); - } - - // 看门狗主逻辑(在 Main 函数或 App_Startup 入口前加判断) - public static void RunWatchdogIfNeeded() - { - var args = Environment.GetCommandLineArgs(); - if (args.Length >= 4 && args[1] == "--watchdog") - { - int pid = int.Parse(args[2]); - string exitSignalFile = args[3]; - try - { - var proc = Process.GetProcessById(pid); - while (!proc.HasExited) - { - // 检查退出信号文件 - if (File.Exists(exitSignalFile)) - { - try { File.Delete(exitSignalFile); } catch { } - Environment.Exit(0); - } - Thread.Sleep(2000); - } - // 主进程异常退出,自动重启前判断崩溃后操作 - SyncCrashActionFromSettings(); // 新增:同步设置 - if (CrashAction == CrashActionType.SilentRestart) - { - StartupCount.Increment(); - if (StartupCount.GetCount() >= 5) - { - MessageBox.Show("检测到程序已连续重启5次,已停止自动重启。请联系开发者或检查系统环境。", "重启次数过多", MessageBoxButton.OK, MessageBoxImage.Error); - StartupCount.Reset(); - Environment.Exit(1); - } - string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath); - } - // CrashActionType.NoAction 时不重启,直接退出 - } - catch { } - Environment.Exit(0); - } - } - - private void App_Exit(object sender, ExitEventArgs e) - { - // 仅在软件内主动退出时关闭看门狗,并写入退出信号 - try - { - // 新增:记录应用退出状态 - string exitType = IsAppExitByUser ? "用户主动退出" : "应用程序退出"; - WriteCrashLog($"{exitType},退出代码: {e.ApplicationExitCode}"); - - // 记录应用退出(设备标识符) - try - { - DeviceIdentifier.RecordAppExit(); - LogHelper.WriteLogToFile($"App | 应用运行时长: {(DateTime.Now - appStartTime).TotalMinutes:F1}分钟"); - } - catch (Exception deviceEx) - { - LogHelper.WriteLogToFile($"记录设备标识符退出信息失败: {deviceEx.Message}", LogHelper.LogType.Error); - } - - if (IsAppExitByUser) - { - // 写入退出信号文件,通知看门狗正常退出 - StartupCount.Reset(); - File.WriteAllText(watchdogExitSignalFile, "exit"); - if (watchdogProcess != null && !watchdogProcess.HasExited) - { - watchdogProcess.Kill(); - } - } - } - catch (Exception ex) - { - // 尝试记录最后的错误 - try - { - LogHelper.WriteLogToFile($"退出处理时发生错误: {ex.Message}", LogHelper.LogType.Error); - } - catch { } - } - } - - /// - /// 检查Office是否安装 - /// - private bool IsOfficeInstalled() - { - try - { - // 检查多个可能的注册表路径 - // 1. 检查传统的Office版本 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office")) - { - if (key != null && key.GetSubKeyNames().Any(name => name.Contains(".0"))) - { - LogHelper.WriteLogToFile("检测到传统Office安装"); - return true; - } - } - - // 2. 检查64位注册表中的Office - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Microsoft\\Office")) - { - if (key != null && key.GetSubKeyNames().Any(name => name.Contains(".0"))) - { - LogHelper.WriteLogToFile("检测到64位注册表中的Office安装"); - return true; - } - } - - // 3. 检查Office 365/Click-to-Run安装 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365 Click-to-Run"); - return true; - } - } - - // 4. 检查Office 365部署配置 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\15.0\\ClickToRun")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365 (15.0)"); - return true; - } - } - - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\16.0\\ClickToRun")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365 (16.0)"); - return true; - } - } - - // 5. 检查Office 365零售订阅信息 - using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration")) - { - if (key != null) - { - LogHelper.WriteLogToFile("检测到Office 365配置"); - return true; - } - } - - LogHelper.WriteLogToFile("未检测到任何Office安装", LogHelper.LogType.Warning); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查Office安装时出错: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 显示权限不足的错误提示 - /// - private void ShowPermissionError() - { - const string message = "需要管理员权限才能完成此操作\n请以管理员身份重新启动应用程序"; - LogHelper.WriteLogToFile(message, LogHelper.LogType.Error); - MessageBox.Show(message, "权限错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - - /// - /// 获取所有已安装的Office版本 - /// - private List GetOfficeVersions() - { - var versions = new List(); - try - { - // 检查HKLM - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office")) - { - if (key != null) - { - foreach (var subKeyName in key.GetSubKeyNames()) - { - if (subKeyName.Contains(".0")) - { - versions.Add(subKeyName); - LogHelper.WriteLogToFile($"在HKLM中找到Office版本: {subKeyName}"); - } - } - } - } - - // 检查HKCU - using (var key = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Office")) - { - if (key != null) - { - foreach (var subKeyName in key.GetSubKeyNames()) - { - if (subKeyName.Contains(".0") && !versions.Contains(subKeyName)) - { - versions.Add(subKeyName); - LogHelper.WriteLogToFile($"在HKCU中找到Office版本: {subKeyName}"); - } - } - } - } - - // 检查64位注册表 - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Microsoft\\Office")) - { - if (key != null) - { - foreach (var subKeyName in key.GetSubKeyNames()) - { - if (subKeyName.Contains(".0") && !versions.Contains(subKeyName)) - { - versions.Add(subKeyName); - LogHelper.WriteLogToFile($"在64位注册表中找到Office版本: {subKeyName}"); - } - } - } - } - - // 检查Office 365的特殊路径 - CheckOffice365Versions(versions); - - // 如果没有找到任何版本,添加默认的Office 365版本号 - if (versions.Count == 0 && IsOffice365Installed()) - { - versions.Add("16.0"); - LogHelper.WriteLogToFile("未找到具体版本,添加默认Office 365版本: 16.0"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取Office版本时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 按版本号排序 - versions.Sort((a, b) => - { - try - { - double va = double.Parse(a.Replace(".0", "")); - double vb = double.Parse(b.Replace(".0", "")); - return vb.CompareTo(va); // 降序排列,最新版本在前 - } - catch - { - return 0; - } - }); - - return versions; - } - - /// - /// 检测Office 365是否已安装 - /// - private bool IsOffice365Installed() - { - try - { - // 检查多个Office 365特定路径 - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun")) - { - if (key != null) - return true; - } - - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\15.0\\ClickToRun")) - { - if (key != null) - return true; - } - - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\16.0\\ClickToRun")) - { - if (key != null) - return true; - } - - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration")) - { - if (key != null) - return true; - } - - return false; - } - catch - { - return false; - } - } - - /// - /// 检查Office 365特有的版本信息 - /// - private void CheckOffice365Versions(List versions) - { - try - { - // 检查Click-to-Run版本路径 - using (var key = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Office\\ClickToRun\\Configuration")) - { - if (key != null) - { - var platformVersion = key.GetValue("Platform") as string; - var clickToRunVersion = key.GetValue("VersionToReport") as string; - - if (!string.IsNullOrEmpty(platformVersion)) - { - var majorVersion = platformVersion.Split('.').FirstOrDefault(); - if (!string.IsNullOrEmpty(majorVersion) && !versions.Contains($"{majorVersion}.0")) - { - versions.Add($"{majorVersion}.0"); - LogHelper.WriteLogToFile($"在Office 365配置中找到平台版本: {majorVersion}.0"); - } - } - - if (!string.IsNullOrEmpty(clickToRunVersion)) - { - var majorVersion = clickToRunVersion.Split('.').FirstOrDefault(); - if (!string.IsNullOrEmpty(majorVersion) && !versions.Contains($"{majorVersion}.0")) - { - versions.Add($"{majorVersion}.0"); - LogHelper.WriteLogToFile($"在Office 365配置中找到报告版本: {majorVersion}.0"); - } - } - } - } - - // 检查安装路径来确认版本 - var possibleVersions = new[] { "15.0", "16.0" }; // Office 2013 (15.0) 和 Office 2016/2019/365 (16.0) - foreach (var version in possibleVersions) - { - using (var key = Registry.LocalMachine.OpenSubKey($"Software\\Microsoft\\Office\\{version}\\Common\\InstallRoot")) - { - if (key != null && key.GetValue("Path") != null && !versions.Contains(version)) - { - versions.Add(version); - LogHelper.WriteLogToFile($"在InstallRoot中找到Office版本: {version}"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查Office 365版本时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 尝试修改Office 365的特殊注册表路径 - /// - private void TryModifyOffice365Registry() - { - try - { - // 准备备份目录 - string backupPath = Path.Combine(RootPath, "saves", "RegistryBackups"); - if (!Directory.Exists(backupPath)) - { - Directory.CreateDirectory(backupPath); - LogHelper.WriteLogToFile($"创建Office 365备份目录: {backupPath}"); - } - - // 检查Office 365 Outlook和PowerPoint的特定路径 - string[] apps = { "outlook", "powerpoint" }; - - foreach (var app in apps) - { - // 检查用户级别的注册表 - string regPath = $"Software\\Microsoft\\Office\\16.0\\{app}\\Security"; - LogHelper.WriteLogToFile($"检查Office 365特定应用注册表: {regPath}"); - - try - { - // 先检查是否存在该路径 - using (var baseKey = Registry.CurrentUser.OpenSubKey(regPath)) - { - // 如果路径存在,先备份 - if (baseKey != null) - { - string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_{app}_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建Office 365 {app}备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{regPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份Office 365 {app}注册表值: {valueName} = {value}"); - } - } - } - } - - // 修改或创建注册表项 - using (var key = Registry.CurrentUser.CreateSubKey(regPath, true)) - { - if (key != null) - { - object currentValue = key.GetValue("DisableProtectedView"); - if (currentValue == null || (int)currentValue != 1) - { - key.SetValue("DisableProtectedView", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile($"Office 365 {app} 注册表值已设置: DisableProtectedView = 1"); - } - else - { - LogHelper.WriteLogToFile($"Office 365 {app} 注册表值已存在且无需更改"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改 {app} 注册表时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 尝试通过Office信任中心路径修改 - string trustCenterPath = "Software\\Microsoft\\Office\\16.0\\Common\\Security\\FileValidation"; - LogHelper.WriteLogToFile($"检查信任中心路径: {trustCenterPath}"); - - try - { - // 先检查是否存在该路径 - using (var baseKey = Registry.CurrentUser.OpenSubKey(trustCenterPath)) - { - // 如果路径存在,先备份 - if (baseKey != null) - { - string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_TrustCenter_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建信任中心备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{trustCenterPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份信任中心注册表值: {valueName} = {value}"); - } - } - } - } - - using (var key = Registry.CurrentUser.CreateSubKey(trustCenterPath, true)) - { - if (key != null) - { - key.SetValue("DisableEditFromPV", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile("已禁用受保护视图中的编辑"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改信任中心路径时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 尝试修改EnableEditWhileViewingPolicy - string policyPath = "Software\\Policies\\Microsoft\\Office\\16.0\\Common\\Security"; - try - { - // 先检查是否存在该路径 - using (var baseKey = Registry.CurrentUser.OpenSubKey(policyPath)) - { - // 如果路径存在,先备份 - if (baseKey != null) - { - string backupFile = Path.Combine(backupPath, $"SecurityBackup_365_Policy_{DateTime.Now:yyyyMMddHHmmss}.reg"); - LogHelper.WriteLogToFile($"创建策略备份文件: {backupFile}"); - - // 使用UTF8编码写入注册表文件 - using (StreamWriter sw = new StreamWriter(backupFile, false, Encoding.UTF8)) - { - sw.WriteLine("Windows Registry Editor Version 5.00\n"); - sw.WriteLine(); - sw.WriteLine($"[{Registry.CurrentUser.Name}\\{policyPath}]"); - - foreach (string valueName in baseKey.GetValueNames()) - { - object value = baseKey.GetValue(valueName); - sw.WriteLine($"\"{valueName}\"=dword:{((int)value):x8}"); - LogHelper.WriteLogToFile($"备份策略注册表值: {valueName} = {value}"); - } - } - } - } - - using (var key = Registry.CurrentUser.CreateSubKey(policyPath, true)) - { - if (key != null) - { - key.SetValue("EnableEditWhileViewingPolicy", 1, RegistryValueKind.DWord); - LogHelper.WriteLogToFile("已启用查看时编辑策略"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改策略路径时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"修改Office 365注册表时发生未知错误: {ex.Message}", LogHelper.LogType.Error); - } - } - } -} diff --git a/Ink Canvas/FodyWeavers.xml b/Ink Canvas/FodyWeavers.xml deleted file mode 100644 index 8c3a747e..00000000 --- a/Ink Canvas/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/Ink Canvas/FodyWeavers.xsd b/Ink Canvas/FodyWeavers.xsd deleted file mode 100644 index f2dbece7..00000000 --- a/Ink Canvas/FodyWeavers.xsd +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - Obsolete, use UnmanagedWinX86Assemblies instead - - - - - A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks. - - - - - Obsolete, use UnmanagedWinX64Assemblies instead. - - - - - A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks. - - - - - A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks. - - - - - The order of preloaded assemblies, delimited with line breaks. - - - - - - This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. - - - - - Controls if .pdbs for reference assemblies are also embedded. - - - - - Controls if runtime assemblies are also embedded. - - - - - Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. - - - - - Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. - - - - - As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. - - - - - The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events. - - - - - Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. - - - - - Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - Obsolete, use UnmanagedWinX86Assemblies instead - - - - - A list of unmanaged X86 (32 bit) assembly names to include, delimited with |. - - - - - Obsolete, use UnmanagedWinX64Assemblies instead - - - - - A list of unmanaged X64 (64 bit) assembly names to include, delimited with |. - - - - - A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |. - - - - - The order of preloaded assemblies, delimited with |. - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs deleted file mode 100644 index c7701754..00000000 --- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs +++ /dev/null @@ -1,562 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Threading; - -namespace Ink_Canvas.Helpers -{ - /// - /// 异步硬件加速的墨迹平滑处理器 - /// - public class AsyncAdvancedBezierSmoothing - { - private readonly SemaphoreSlim _processingSemaphore; - private readonly ConcurrentDictionary _processingTasks; - private readonly Dispatcher _uiDispatcher; - - public AsyncAdvancedBezierSmoothing(Dispatcher uiDispatcher) - { - _uiDispatcher = uiDispatcher; - _processingSemaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount); - _processingTasks = new ConcurrentDictionary(); - } - - public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度 - public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数 - public int InterpolationSteps { get; set; } = 8; // 从4增加到8,提高插值步数 - public bool UseHardwareAcceleration { get; set; } = true; - public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; - - /// - /// 异步平滑笔画 - /// - public async Task SmoothStrokeAsync(Stroke originalStroke, - Action onCompleted = null, - CancellationToken cancellationToken = default) - { - if (originalStroke == null || originalStroke.StylusPoints.Count < 2) - return originalStroke; - - // 取消之前对同一笔画的处理 - if (_processingTasks.TryGetValue(originalStroke, out var existingCts)) - { - existingCts.Cancel(); - _processingTasks.TryRemove(originalStroke, out _); - } - - var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - _processingTasks[originalStroke] = cts; - - try - { - await _processingSemaphore.WaitAsync(cts.Token); - - var smoothedStroke = await Task.Run(() => - ProcessStrokeInternal(originalStroke, cts.Token), cts.Token); - - // 在UI线程上执行回调 - if (onCompleted != null && !cts.Token.IsCancellationRequested) - { - await _uiDispatcher.InvokeAsync(() => onCompleted(originalStroke, smoothedStroke)); - } - - return smoothedStroke; - } - catch (OperationCanceledException) - { - return originalStroke; - } - finally - { - _processingSemaphore.Release(); - _processingTasks.TryRemove(originalStroke, out _); - cts.Dispose(); - } - } - - private Stroke ProcessStrokeInternal(Stroke stroke, CancellationToken cancellationToken) - { - var originalPoints = stroke.StylusPoints.ToArray(); - - // 如果点数太少,直接返回原始笔画 - if (originalPoints.Length < 3) - return stroke; - - cancellationToken.ThrowIfCancellationRequested(); - - // 简化处理:只进行轻度平滑,避免点数爆炸 - var smoothedPoints = ApplyLightSmoothing(originalPoints); - - cancellationToken.ThrowIfCancellationRequested(); - - // 确保点数不会过多 - if (smoothedPoints.Length > originalPoints.Length * 2) - { - // 如果点数增加太多,回退到原始笔画 - return stroke; - } - - // 创建平滑后的笔画 - var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints)) - { - DrawingAttributes = stroke.DrawingAttributes.Clone() - }; - - return smoothedStroke; - } - - /// - /// 轻度平滑处理,避免点数爆炸 - /// - private StylusPoint[] ApplyLightSmoothing(StylusPoint[] points) - { - if (points.Length < 3) return points; - - var result = new List(); - result.Add(points[0]); // 保持第一个点 - - // 简单的3点平均平滑 - for (int i = 1; i < points.Length - 1; i++) - { - var prev = points[i - 1]; - var curr = points[i]; - var next = points[i + 1]; - - // 3点平均 - double x = (prev.X + curr.X + next.X) / 3.0; - double y = (prev.Y + curr.Y + next.Y) / 3.0; - float pressure = (prev.PressureFactor + curr.PressureFactor + next.PressureFactor) / 3.0f; - - result.Add(new StylusPoint(x, y, Math.Max(pressure, 0.1f))); - } - - result.Add(points[points.Length - 1]); // 保持最后一个点 - - return result.ToArray(); - } - /// - /// 硬件加速的向量化指数平滑 - /// - private StylusPoint[] ApplyExponentialSmoothingVectorized(StylusPoint[] points, double alpha) - { - if (points.Length == 0) return points; - - var result = new StylusPoint[points.Length]; - result[0] = points[0]; - - double lastX = points[0].X; - double lastY = points[0].Y; - float lastPressure = points[0].PressureFactor; - double oneMinusAlpha = 1.0 - alpha; - - // 向量化处理,减少分支预测失败 - for (int i = 1; i < points.Length; i++) - { - var p = points[i]; - lastX = alpha * p.X + oneMinusAlpha * lastX; - lastY = alpha * p.Y + oneMinusAlpha * lastY; - lastPressure = (float)(alpha * p.PressureFactor + oneMinusAlpha * lastPressure); - lastPressure = Math.Max(lastPressure, 0.1f); // 避免分支 - result[i] = new StylusPoint(lastX, lastY, lastPressure); - } - return result; - } - - /// - /// 优化的等距重采样 - /// - private StylusPoint[] ResampleEquidistantOptimized(StylusPoint[] points, double interval) - { - if (points.Length == 0) return points; - - var result = new List(points.Length) { points[0] }; - double accumulated = 0; - - for (int i = 1; i < points.Length; i++) - { - var prev = result[result.Count - 1]; - var curr = points[i]; - double dx = curr.X - prev.X; - double dy = curr.Y - prev.Y; - double dist = Math.Sqrt(dx * dx + dy * dy); - - if (dist + accumulated >= interval) - { - double t = (interval - accumulated) / dist; - double x = prev.X + t * dx; - double y = prev.Y + t * dy; - float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t); - pressure = Math.Max(pressure, 0.1f); - - result.Add(new StylusPoint(x, y, pressure)); - accumulated = 0; - i--; // 重新处理当前点 - } - else - { - accumulated += dist; - } - } - return result.ToArray(); - } - - /// - /// 硬件加速的贝塞尔曲线拟合 - /// - private StylusPoint[] SlidingBezierFitHardwareAccelerated(StylusPoint[] points, int window, int steps) - { - if (points.Length < window) return points; - - var result = new List(points.Length * steps / window); - - // 使用并行处理加速计算 - var segments = new List(); - - Parallel.For(0, points.Length - window + 1, i => - { - var segmentPoints = new StylusPoint[steps]; - var p0 = points[i]; - var p1 = points[i + 1]; - var p2 = points[i + 2]; - var p3 = points[i + 3]; - - for (int j = 0; j < steps; j++) - { - double t = (double)j / steps; - segmentPoints[j] = CubicBezierOptimized(p0, p1, p2, p3, t); - } - - lock (segments) - { - segments.Add(segmentPoints); - } - }); - - // 合并结果 - foreach (var segment in segments) - { - result.AddRange(segment); - } - - result.Add(points[points.Length - 1]); - return result.ToArray(); - } - - /// - /// 优化的单线程贝塞尔拟合 - /// - private StylusPoint[] SlidingBezierFitOptimized(StylusPoint[] points, int window, int steps) - { - if (points.Length < window) return points; - - var result = new List(points.Length * steps / window); - - for (int i = 0; i <= points.Length - window; i++) - { - var p0 = points[i]; - var p1 = points[i + 1]; - var p2 = points[i + 2]; - var p3 = points[i + 3]; - - for (int j = 0; j < steps; j++) - { - double t = (double)j / steps; - result.Add(CubicBezierOptimized(p0, p1, p2, p3, t)); - } - } - - result.Add(points[points.Length - 1]); - return result.ToArray(); - } - - /// - /// 优化的三次贝塞尔曲线计算 - /// - private StylusPoint CubicBezierOptimized(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t) - { - double u = 1 - t; - double tt = t * t; - double uu = u * u; - double uuu = uu * u; - double ttt = tt * t; - - // 预计算系数 - double c0 = uuu; - double c1 = 3 * uu * t; - double c2 = 3 * u * tt; - double c3 = ttt; - - double x = c0 * p0.X + c1 * p1.X + c2 * p2.X + c3 * p3.X; - double y = c0 * p0.Y + c1 * p1.Y + c2 * p2.Y + c3 * p3.Y; - float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t); - pressure = Math.Max(pressure, 0.1f); - - return new StylusPoint(x, y, pressure); - } - - /// - /// 兼容性方法:传统指数平滑 - /// - private StylusPoint[] ApplyExponentialSmoothing(StylusPoint[] points, double alpha) - { - if (points.Length == 0) return points; - - var result = new StylusPoint[points.Length]; - result[0] = points[0]; - - double lastX = points[0].X; - double lastY = points[0].Y; - float lastPressure = points[0].PressureFactor; - - for (int i = 1; i < points.Length; i++) - { - var p = points[i]; - lastX = alpha * p.X + (1 - alpha) * lastX; - lastY = alpha * p.Y + (1 - alpha) * lastY; - lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure); - lastPressure = Math.Max(lastPressure, 0.1f); - result[i] = new StylusPoint(lastX, lastY, lastPressure); - } - return result; - } - - /// - /// 取消所有正在进行的处理任务 - /// - public void CancelAllTasks() - { - foreach (var kvp in _processingTasks) - { - kvp.Value.Cancel(); - } - _processingTasks.Clear(); - } - - /// - /// 释放资源 - /// - public void Dispose() - { - CancelAllTasks(); - _processingSemaphore?.Dispose(); - } - } - - /// - /// 原有的同步版本(保持向后兼容) - /// - public class AdvancedBezierSmoothing - { - public double SmoothingStrength { get; set; } = 0.3; - public double ResampleInterval { get; set; } = 3.0; - public int InterpolationSteps { get; set; } = 8; - - public Stroke SmoothStroke(Stroke stroke) - { - if (stroke == null || stroke.StylusPoints.Count < 3) - return stroke; - - var originalPoints = stroke.StylusPoints.ToList(); - - // 简化处理:只进行轻度平滑 - var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑 - - // 检查点数是否合理 - if (smoothedPoints.Count > originalPoints.Count * 1.5) - { - return stroke; // 如果点数增加太多,返回原始笔画 - } - - var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints)) - { - DrawingAttributes = stroke.DrawingAttributes.Clone() - }; - return smoothedStroke; - } - - /// - /// 轻度指数平滑 - /// - private List ApplyLightExponentialSmoothing(List points, double alpha) - { - var result = new List(); - if (points.Count == 0) return result; - - result.Add(points[0]); - - for (int i = 1; i < points.Count; i++) - { - var prev = result[result.Count - 1]; - var curr = points[i]; - - double x = alpha * curr.X + (1 - alpha) * prev.X; - double y = alpha * curr.Y + (1 - alpha) * prev.Y; - float pressure = (float)(alpha * curr.PressureFactor + (1 - alpha) * prev.PressureFactor); - pressure = Math.Max(pressure, 0.1f); - - result.Add(new StylusPoint(x, y, pressure)); - } - return result; - } - - private List ApplyExponentialSmoothing(List points, double alpha) - { - var result = new List(); - if (points.Count == 0) return result; - result.Add(points[0]); - double lastX = points[0].X; - double lastY = points[0].Y; - float lastPressure = points[0].PressureFactor; - for (int i = 1; i < points.Count; i++) - { - var p = points[i]; - lastX = alpha * p.X + (1 - alpha) * lastX; - lastY = alpha * p.Y + (1 - alpha) * lastY; - lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure); - if (lastPressure < 0.1f) lastPressure = 0.1f; - result.Add(new StylusPoint(lastX, lastY, lastPressure)); - } - return result; - } - - private List ResampleEquidistant(List points, double interval = 2.0) - { - var result = new List(); - if (points.Count == 0) return result; - result.Add(points[0]); - double accumulated = 0; - for (int i = 1; i < points.Count; i++) - { - var prev = result.Last(); - var curr = points[i]; - double dx = curr.X - prev.X; - double dy = curr.Y - prev.Y; - double dist = Math.Sqrt(dx * dx + dy * dy); - if (dist + accumulated >= interval) - { - double t = (interval - accumulated) / dist; - double x = prev.X + t * dx; - double y = prev.Y + t * dy; - float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t); - if (pressure < 0.1f) pressure = 0.1f; - var newPoint = new StylusPoint(x, y, pressure); - result.Add(newPoint); - accumulated = 0; - i--; // 重新处理当前点 - } - else - { - accumulated += dist; - } - } - return result; - } - - private List SlidingBezierFit(List points, int window = 4, int steps = 48) // 从24增加到48 - { - var result = new List(); - if (points.Count < window) return points; - for (int i = 0; i <= points.Count - window; i++) - { - var p0 = points[i]; - var p1 = points[i + 1]; - var p2 = points[i + 2]; - var p3 = points[i + 3]; - for (int j = 0; j < steps; j++) - { - double t = (double)j / steps; - var pt = CubicBezier(p0, p1, p2, p3, t); - result.Add(pt); - } - } - // 保证最后一个点被包含 - result.Add(points.Last()); - return result; - } - - private StylusPoint CubicBezier(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t) - { - double u = 1 - t; - double tt = t * t; - double uu = u * u; - double uuu = uu * u; - double ttt = tt * t; - double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X; - double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y; - float pressure = (float)(p1.PressureFactor * (1 - t) + p2.PressureFactor * t); - if (pressure < 0.1f) pressure = 0.1f; - return new StylusPoint(x, y, pressure); - } - - private List SlidingWindowSmooth(List points, int window = 5) - { - var result = new List(); - int half = window / 2; - for (int i = 0; i < points.Count; i++) - { - double sumX = 0, sumY = 0, sumP = 0; - int count = 0; - for (int j = Math.Max(0, i - half); j <= Math.Min(points.Count - 1, i + half); j++) - { - sumX += points[j].X; - sumY += points[j].Y; - sumP += points[j].PressureFactor; - count++; - } - result.Add(new StylusPoint(sumX / count, sumY / count, (float)(sumP / count))); - } - return result; - } - } - - /// - /// 性能监控器 - /// - public class InkSmoothingPerformanceMonitor - { - private readonly Queue _processingTimes = new Queue(); - private readonly object _lock = new object(); - private const int MaxSamples = 100; - - public void RecordProcessingTime(TimeSpan time) - { - lock (_lock) - { - _processingTimes.Enqueue(time); - if (_processingTimes.Count > MaxSamples) - _processingTimes.Dequeue(); - } - } - - public double GetAverageProcessingTimeMs() - { - lock (_lock) - { - return _processingTimes.Count > 0 ? - _processingTimes.Average(t => t.TotalMilliseconds) : 0; - } - } - - public double GetMaxProcessingTimeMs() - { - lock (_lock) - { - return _processingTimes.Count > 0 ? - _processingTimes.Max(t => t.TotalMilliseconds) : 0; - } - } - - public int GetSampleCount() - { - lock (_lock) - { - return _processingTimes.Count; - } - } - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/AutoUpdateHelper.cs deleted file mode 100644 index e0a45e5a..00000000 --- a/Ink Canvas/Helpers/AutoUpdateHelper.cs +++ /dev/null @@ -1,1631 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Ink_Canvas.Helpers -{ - internal class AutoUpdateHelper - { - // 定义超时时间为10秒 - private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10); - private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate"); - private static string statusFilePath; - - // 线路组结构体(包含版本、下载、日志地址) - public class UpdateLineGroup - { - public string GroupName { get; set; } // 组名 - public string VersionUrl { get; set; } // 版本检测地址 - public string DownloadUrlFormat { get; set; } // 下载地址格式(带{0}占位符) - public string LogUrl { get; set; } // 更新日志地址 - } - - // 通道-线路组映射 - public static readonly Dictionary> ChannelLineGroups = new Dictionary> - { - { UpdateChannel.Release, new List - { - new UpdateLineGroup - { - GroupName = "GitHub主线", - VersionUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", - DownloadUrlFormat = "https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", - LogUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md" - }, - new UpdateLineGroup - { - GroupName = "bgithub备用", - VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", - DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", - LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md" - }, - new UpdateLineGroup - { - GroupName = "kkgithub线路", - VersionUrl = "https://kkgithub.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", - DownloadUrlFormat = "https://kkgithub.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", - LogUrl = "https://kkgithub.com/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md" - }, - new UpdateLineGroup - { - GroupName = "智教联盟", - DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community/InkCanvasForClass.CE.{0}.zip", - }, - new UpdateLineGroup - { - GroupName = "inkeys", - DownloadUrlFormat = "https://iccce.inkeys.top/Release/InkCanvasForClass.CE.{0}.zip", - } - } - }, - { UpdateChannel.Beta, new List - { - new UpdateLineGroup - { - GroupName = "GitHub主线", - VersionUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", - DownloadUrlFormat = "https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", - LogUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md" - }, - new UpdateLineGroup - { - GroupName = "bgithub备用", - VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", - DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", - LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md" - }, - new UpdateLineGroup - { - GroupName = "kkgithub线路", - VersionUrl = "https://kkgithub.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", - DownloadUrlFormat = "https://kkgithub.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", - LogUrl = "https://kkgithub.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md" - }, - new UpdateLineGroup - { - GroupName = "智教联盟", - DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community-beta/InkCanvasForClass.CE.{0}.zip", - }, - new UpdateLineGroup - { - GroupName = "inkeys", - DownloadUrlFormat = "https://iccce.inkeys.top/Beta/InkCanvasForClass.CE.{0}.zip", - } - } - } - }; - - // 区块任务结构体(移到类体内) - private class BlockTask - { - public int Index; - public long Start; - public long End; - public int RetryCount; - } - - // 检测URL延迟 - private static async Task GetUrlDelay(string url) - { - try - { - // 检测是否为Windows 7 - var osVersion = Environment.OSVersion; - bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1; - - if (isWindows7) - { - // Windows 7使用特殊配置 - using (var handler = new HttpClientHandler()) - { - // 配置HttpClientHandler以支持Windows 7 - 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(); - if (resp.IsSuccessStatusCode) - return sw.ElapsedMilliseconds; - } - } - } - else - { - // 其他Windows版本使用标准配置 - 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(); - if (resp.IsSuccessStatusCode) - return sw.ElapsedMilliseconds; - } - } - } - catch { } - return -1; - } - - // 检测线路组延迟,返回最快组(保持向后兼容) - private static async Task GetFastestLineGroup(UpdateChannel channel) - { - var availableGroups = await GetAvailableLineGroupsOrdered(channel); - return availableGroups.Count > 0 ? availableGroups[0] : null; - } - - // 获取所有可用线路组,按延迟排序 - public static async Task> GetAvailableLineGroupsOrdered(UpdateChannel channel) - { - var groups = ChannelLineGroups[channel]; - var availableGroups = new List<(UpdateLineGroup group, long delay)>(); - - LogHelper.WriteLogToFile($"AutoUpdate | 开始检测通道 {channel} 下所有线路组延迟..."); - - foreach (var group in groups) - { - // 跳过"智教联盟"和"inkeys"线路组,不参与延迟检测和排序 - if (group.GroupName == "智教联盟" || group.GroupName == "inkeys") - { - LogHelper.WriteLogToFile($"AutoUpdate | 跳过{group.GroupName}线路组延迟检测"); - continue; - } - LogHelper.WriteLogToFile($"AutoUpdate | 检测线路组: {group.GroupName} ({group.VersionUrl})"); - var delay = await GetUrlDelay(group.VersionUrl); - if (delay >= 0) - { - LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 延迟: {delay}ms"); - availableGroups.Add((group, delay)); - } - else - { - LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 不可用", LogHelper.LogType.Warning); - } - } - - // 按延迟排序,延迟最小的排在前面 - var orderedGroups = availableGroups - .OrderBy(x => x.delay) - .Select(x => x.group) - .ToList(); - - // 将"智教联盟"线路组插入到最前面(如果存在) - var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟"); - if (zhiJiaoGroup != null) - { - orderedGroups.Insert(0, zhiJiaoGroup); - LogHelper.WriteLogToFile("AutoUpdate | 智教联盟线路组已插入到首位"); - } - - // 将"inkeys"线路组插入到第二位(如果存在) - var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys"); - if (inkeysGroup != null) - { - orderedGroups.Insert(1, inkeysGroup); - LogHelper.WriteLogToFile("AutoUpdate | inkeys线路组已插入到第二位"); - } - - if (orderedGroups.Count > 0) - { - LogHelper.WriteLogToFile($"AutoUpdate | 找到 {orderedGroups.Count} 个可用线路组,按延迟排序:"); - for (int i = 0; i < orderedGroups.Count; i++) - { - LogHelper.WriteLogToFile($"AutoUpdate | {i + 1}. {orderedGroups[i].GroupName}"); - } - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error); - } - - return orderedGroups; - } - - // 获取远程版本号 - private static async Task GetRemoteVersion(string fileUrl) - { - // 检测是否为Windows 7 - var osVersion = Environment.OSVersion; - bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1; - - if (isWindows7) - { - // Windows 7使用特殊配置 - using (var handler = new HttpClientHandler()) - { - try - { - // 配置HttpClientHandler以支持Windows 7 - handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; - - using (HttpClient client = new HttpClient(handler)) - { - client.Timeout = RequestTimeout; - LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}"); - - var downloadTask = client.GetAsync(fileUrl); - var timeoutTask = Task.Delay(RequestTimeout); - - var completedTask = await Task.WhenAny(downloadTask, timeoutTask); - if (completedTask == timeoutTask) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error); - return null; - } - - HttpResponseMessage response = await downloadTask; - LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}"); - response.EnsureSuccessStatusCode(); - - string content = await response.Content.ReadAsStringAsync(); - content = content.Trim(); - - // 如果内容包含HTML(可能是GitHub页面而不是原始内容),尝试提取版本号 - if (content.Contains(" 0) - { - int endPos = content.IndexOf("", startPos); - if (endPos > startPos) - { - string tableContent = content.Substring(startPos, endPos - startPos); - var match = Regex.Match(tableContent, @"(\d+\.\d+\.\d+(\.\d+)?)"); - if (match.Success) - { - content = match.Groups[1].Value; - LogHelper.WriteLogToFile($"AutoUpdate | 从HTML提取版本: {content}"); - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | 无法从HTML内容提取版本"); - return null; - } - } - } - } - - LogHelper.WriteLogToFile($"AutoUpdate | 响应内容: {content}"); - return content; - } - } - catch (HttpRequestException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error); - } - catch (TaskCanceledException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error); - } - - return null; - } - } - - // 其他Windows版本使用标准配置 - using (HttpClient client = new HttpClient()) - { - try - { - client.Timeout = RequestTimeout; - LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}"); - - var downloadTask = client.GetAsync(fileUrl); - var timeoutTask = Task.Delay(RequestTimeout); - - var completedTask = await Task.WhenAny(downloadTask, timeoutTask); - if (completedTask == timeoutTask) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error); - return null; - } - - HttpResponseMessage response = await downloadTask; - LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}"); - response.EnsureSuccessStatusCode(); - - string content = await response.Content.ReadAsStringAsync(); - content = content.Trim(); - - // 如果内容包含HTML(可能是GitHub页面而不是原始内容),尝试提取版本号 - if (content.Contains(" 0) - { - int endPos = content.IndexOf("", startPos); - if (endPos > startPos) - { - string tableContent = content.Substring(startPos, endPos - startPos); - var match = Regex.Match(tableContent, @"(\d+\.\d+\.\d+(\.\d+)?)"); - if (match.Success) - { - content = match.Groups[1].Value; - LogHelper.WriteLogToFile($"AutoUpdate | 从HTML提取版本: {content}"); - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | 无法从HTML内容提取版本"); - return null; - } - } - } - } - - LogHelper.WriteLogToFile($"AutoUpdate | 响应内容: {content}"); - return content; - } - catch (HttpRequestException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error); - } - catch (TaskCanceledException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error); - } - - return null; - } - } - - // 通过GitHub API获取指定版本的Release信息 - private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetGithubReleaseByVersion(string targetVersion, UpdateChannel channel) - { - try - { - string apiUrl = channel == UpdateChannel.Beta - ? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases" - : "https://api.github.com/repos/InkCanvasForClass/community/releases"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); - var response = await client.GetStringAsync(apiUrl); - var releases = JArray.Parse(response); - - foreach (var release in releases) - { - string version = release["tag_name"]?.ToString(); - if (version == targetVersion || version == $"v{targetVersion}" || version == $"V{targetVersion}") - { - string releaseNotes = release["body"]?.ToString(); - string downloadUrl = release["assets"]?.First?["browser_download_url"]?.ToString(); - - // 解析发布时间 - DateTime? releaseTime = null; - if (release["published_at"] != null && DateTime.TryParse(release["published_at"].ToString(), out DateTime parsedTime)) - { - releaseTime = parsedTime; - } - - return (version, downloadUrl, releaseNotes, releaseTime); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取版本 {targetVersion} 失败: {ex.Message}", LogHelper.LogType.Warning); - } - return (null, null, null, null); - } - - // 通过GitHub API获取最新Release信息 - private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetLatestGithubRelease(UpdateChannel channel) - { - try - { - string apiUrl = channel == UpdateChannel.Beta - ? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases/latest" - : "https://api.github.com/repos/InkCanvasForClass/community/releases/latest"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); - var response = await client.GetStringAsync(apiUrl); - var json = JObject.Parse(response); - string version = json["tag_name"]?.ToString(); - string releaseNotes = json["body"]?.ToString(); - string downloadUrl = json["assets"]?.First?["browser_download_url"]?.ToString(); - - // 解析发布时间 - DateTime? releaseTime = null; - if (json["published_at"] != null && DateTime.TryParse(json["published_at"].ToString(), out DateTime parsedTime)) - { - releaseTime = parsedTime; - } - - if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl)) - return (version, downloadUrl, releaseNotes, releaseTime); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取失败: {ex.Message}", LogHelper.LogType.Warning); - } - return (null, null, null, null); - } - - // 主要的更新检测方法(优先检测延迟,失败时自动切换线路组) - // 仅检测新版本时用GitHub API,实际下载时只用线路组 - public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false, bool isVersionFix = false) - { - try - { - // 记录更新检查时间 - DeviceIdentifier.RecordUpdateCheck(); - - string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - LogHelper.WriteLogToFile($"AutoUpdate | 本地版本: {localVersion}"); - LogHelper.WriteLogToFile($"AutoUpdate | 设备ID: {DeviceIdentifier.GetDeviceId()}"); - LogHelper.WriteLogToFile($"AutoUpdate | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}"); - LogHelper.WriteLogToFile("AutoUpdate | 优先通过GitHub Releases API检测..."); - - // 1. 优先通过GitHub Releases API获取 - var (apiVersion, _, apiReleaseNotes, apiReleaseTime) = await GetLatestGithubRelease(channel); - if (!string.IsNullOrEmpty(apiVersion)) - { - Version local = new Version(localVersion); - Version remote = new Version(apiVersion.TrimStart('v', 'V')); - if (remote > local || alwaysGetRemote) - { - LogHelper.WriteLogToFile($"AutoUpdate | 通过GitHub Releases API发现新版本: {apiVersion}"); - - // 检查是否应该根据用户优先级推送更新(版本修复功能不受限制) - if (!isVersionFix) - { - DateTime releaseTime = apiReleaseTime ?? DateTime.Now; - - // 尝试获取当前版本的发布时间 - DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel); - - bool shouldPush = DeviceIdentifier.ShouldPushUpdate(apiVersion, releaseTime, true, currentVersionReleaseTime); // 明确标记为自动更新 - if (!shouldPush) - { - var priority = DeviceIdentifier.GetUpdatePriority(); - var daysBetweenVersions = currentVersionReleaseTime.HasValue - ? (releaseTime - currentVersionReleaseTime.Value).TotalDays - : (DateTime.Now - releaseTime).TotalDays; - LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {apiVersion},版本间隔: {daysBetweenVersions:F1} 天"); - var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); - return (null, group, apiReleaseNotes); // 返回null表示不推送 - } - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | 版本修复模式,跳过分级策略检查"); - } - - LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {apiVersion}"); - // 只返回版本号和日志,不返回直链 - var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); - return (apiVersion, availableGroup, apiReleaseNotes); - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | 当前版本已是最新 (GitHub Releases API)"); - var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); - return (null, availableGroup, apiReleaseNotes); - } - } - // 2. 回退到原有txt方案 - LogHelper.WriteLogToFile("AutoUpdate | GitHub Releases API获取失败,回退到txt方案..."); - var availableGroups = await GetAvailableLineGroupsOrdered(channel); - if (availableGroups.Count == 0) - { - LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error); - return (null, null, null); - } - foreach (var group in availableGroups) - { - LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用线路组获取版本信息: {group.GroupName}"); - string remoteVersion = await GetRemoteVersion(group.VersionUrl); - if (remoteVersion != null) - { - LogHelper.WriteLogToFile($"AutoUpdate | 成功从线路组 {group.GroupName} 获取远程版本: {remoteVersion}"); - Version local = new Version(localVersion); - Version remote = new Version(remoteVersion); - if (remote > local || alwaysGetRemote) - { - LogHelper.WriteLogToFile($"AutoUpdate | 发现新版本或强制获取: {remoteVersion}"); - - // 检查是否应该根据用户优先级推送更新(版本修复功能不受限制) - if (!isVersionFix) - { - // 尝试获取当前版本的发布时间 - DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel); - - bool shouldPush = DeviceIdentifier.ShouldPushUpdate(remoteVersion, DateTime.Now, true, currentVersionReleaseTime); // 明确标记为自动更新 - if (!shouldPush) - { - var priority = DeviceIdentifier.GetUpdatePriority(); - LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {remoteVersion}"); - return (null, group, null); // 返回null表示不推送 - } - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | 版本修复模式,跳过分级策略检查"); - } - - LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {remoteVersion}"); - return (remoteVersion, group, null); - } - - LogHelper.WriteLogToFile("AutoUpdate | 当前版本已是最新"); - return (null, group, null); - } - - LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 获取版本失败,尝试下一个线路组", LogHelper.LogType.Warning); - } - LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均无法获取版本信息", LogHelper.LogType.Error); - return (null, null, null); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | CheckForUpdates错误: {ex.Message}", LogHelper.LogType.Error); - return (null, null, null); - } - } - - // 使用指定线路组下载新版 - public static async Task DownloadSetupFile(string version, UpdateLineGroup group) - { - return await DownloadSetupFileWithFallback(version, new List { group }); - } - - // 获取智教联盟真实下载地址 - private static async Task GetZhijiaoRealDownloadUrl(string url) - { - try - { - using (var handler = new HttpClientHandler { AllowAutoRedirect = false }) - using (var client = new HttpClient(handler)) - { - client.Timeout = RequestTimeout; - var resp = await client.GetAsync(url); - // 优先取Location头 - if (resp.StatusCode == HttpStatusCode.Found || resp.StatusCode == HttpStatusCode.Redirect || resp.StatusCode == HttpStatusCode.MovedPermanently) - { - if (resp.Headers.Location != null) - { - var realUrl = resp.Headers.Location.ToString(); - if (realUrl.Contains(" ")) realUrl = realUrl.Replace(" ", "%20"); - return realUrl; - } - } - // 有些服务器直接返回真实地址在内容里 - var content = await resp.Content.ReadAsStringAsync(); - if (Uri.IsWellFormedUriString(content.Trim(), UriKind.Absolute)) - { - var realUrl = content.Trim(); - if (realUrl.Contains(" ")) realUrl = realUrl.Replace(" ", "%20"); - return realUrl; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 获取智教联盟真实下载地址失败: {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - // 使用多线路组下载新版(支持自动切换) - public static async Task DownloadSetupFileWithFallback(string version, List groups, Action progressCallback = null) - { - try - { - statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{version}Status.txt"); - - if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true") - { - LogHelper.WriteLogToFile("AutoUpdate | 安装包已下载"); - progressCallback?.Invoke(100, "已下载完成"); - return true; - } - - // 确保更新目录存在 - if (!Directory.Exists(updatesFolderPath)) - { - Directory.CreateDirectory(updatesFolderPath); - LogHelper.WriteLogToFile($"AutoUpdate | 创建更新目录: {updatesFolderPath}"); - } - - string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip"); - LogHelper.WriteLogToFile($"AutoUpdate | 目标文件路径: {zipFilePath}"); - - SaveDownloadStatus(false); - - // 优先尝试“智教联盟”线路组 - var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟"); - var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys"); - if (zhiJiaoGroup != null || inkeysGroup != null) - { - var priorityGroups = new List(); - if (zhiJiaoGroup != null) - { - priorityGroups.Add(zhiJiaoGroup); - LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试智教联盟线路组"); - } - if (inkeysGroup != null) - { - priorityGroups.Add(inkeysGroup); - LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试inkeys线路组"); - } - groups = priorityGroups.Concat(groups.Where(g => g.GroupName != "智教联盟" && g.GroupName != "inkeys")).ToList(); - } - - // 依次尝试每个线路组 - foreach (var group in groups) - { - string url = string.Format(group.DownloadUrlFormat, version); - // 智教联盟需要先获取真实下载地址 - if (group.GroupName == "智教联盟") - { - LogHelper.WriteLogToFile($"AutoUpdate | 获取智教联盟真实下载地址: {url}"); - var realUrl = await GetZhijiaoRealDownloadUrl(url); - if (string.IsNullOrEmpty(realUrl)) - { - LogHelper.WriteLogToFile("AutoUpdate | 智教联盟真实下载地址获取失败,跳过", LogHelper.LogType.Warning); - progressCallback?.Invoke(0, "智教联盟真实下载地址获取失败,跳过"); - continue; - } - url = realUrl; - LogHelper.WriteLogToFile($"AutoUpdate | 智教联盟真实下载地址: {url}"); - } - // inkeys线路组直接使用下载地址,无需特殊处理 - else if (group.GroupName == "inkeys") - { - LogHelper.WriteLogToFile($"AutoUpdate | 使用inkeys线路组下载地址: {url}"); - } - LogHelper.WriteLogToFile($"AutoUpdate | 尝试从线路组 {group.GroupName} 下载: {url}"); - - bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback); - - if (downloadSuccess) - { - SaveDownloadStatus(true); - LogHelper.WriteLogToFile($"AutoUpdate | 从线路组 {group.GroupName} 下载成功"); - progressCallback?.Invoke(100, "下载完成"); - return true; - } - - LogHelper.WriteLogToFile($"AutoUpdate | 线路组 {group.GroupName} 下载失败,尝试下一个线路组", LogHelper.LogType.Warning); - } - - LogHelper.WriteLogToFile("AutoUpdate | 所有线路组下载均失败", LogHelper.LogType.Error); - progressCallback?.Invoke(0, "所有线路组下载均失败"); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error); - if (ex.InnerException != null) - { - LogHelper.WriteLogToFile($"AutoUpdate | 内部异常: {ex.InnerException.Message}", LogHelper.LogType.Error); - } - - SaveDownloadStatus(false); - progressCallback?.Invoke(0, $"下载异常: {ex.Message}"); - return false; - } - } - - // 下载文件的具体实现 - public static async Task DownloadFile(string fileUrl, string destinationPath, Action progressCallback = null) - { - LogHelper.WriteLogToFile($"AutoUpdate | 正在尝试多线程下载: {fileUrl}"); - int maxRetry = 3; - // 降低并发数,减少网络压力 - int[] threadOptions = { 32, 16, 8, 4, 1 }; - - // 检查服务器是否支持Range分块下载 - bool supportRange = false; - long totalSize = -1; - try - { - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); - var req = new HttpRequestMessage(HttpMethod.Head, fileUrl); - req.Headers.Range = new RangeHeaderValue(0, 0); - var resp = await client.SendAsync(req); - if (resp.StatusCode == HttpStatusCode.PartialContent) - { - supportRange = true; - if (resp.Content.Headers.ContentRange != null && resp.Content.Headers.ContentRange.Length.HasValue) - { - totalSize = resp.Content.Headers.ContentRange.Length.Value; - } - else if (resp.Content.Headers.ContentLength.HasValue) - { - totalSize = resp.Content.Headers.ContentLength.Value; - } - } - else if (resp.StatusCode == HttpStatusCode.OK) - { - supportRange = false; - if (resp.Content.Headers.ContentLength.HasValue) - { - totalSize = resp.Content.Headers.ContentLength.Value; - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 检查Range支持时异常: {ex.Message}", LogHelper.LogType.Warning); - } - - if (!supportRange) - { - LogHelper.WriteLogToFile("AutoUpdate | 服务器不支持分块下载,自动降级为单线程下载"); - progressCallback?.Invoke(0, "服务器不支持分块下载,自动降级为单线程下载"); - return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback); - } - - foreach (int threadCount in threadOptions) - { - LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用 {threadCount} 线程下载"); - progressCallback?.Invoke(0, $"尝试使用 {threadCount} 线程下载"); - - if (totalSize <= 0) - { - totalSize = await GetContentLength(fileUrl); - } - if (totalSize <= 0) - { - progressCallback?.Invoke(0, "无法获取文件大小,取消下载"); - return false; - } - - // 根据文件大小动态调整分块大小,避免分块过小 - int minBlockSize = 32 * 1024; // 最小32KB - int blockSize = Math.Max(minBlockSize, (int)Math.Ceiling((double)totalSize / threadCount)); - int blockCount = (int)Math.Ceiling((double)totalSize / blockSize); - - LogHelper.WriteLogToFile($"AutoUpdate | 文件大小: {totalSize}, 分块数: {blockCount}, 分块大小: {blockSize}"); - - var blockQueue = new ConcurrentQueue(); - var finishedBlocks = new ConcurrentDictionary(); - long[] blockDownloaded = new long[blockCount]; - - for (int i = 0; i < blockCount; i++) - { - long start = i * blockSize; - long end = Math.Min(start + blockSize - 1, totalSize - 1); - blockQueue.Enqueue(new BlockTask { Index = i, Start = start, End = end, RetryCount = 0 }); - } - - CancellationTokenSource cts = new CancellationTokenSource(); - var tasks = new List(); - - for (int t = 0; t < threadCount; t++) - { - tasks.Add(Task.Run(async () => - { - while (blockQueue.TryDequeue(out var block)) - { - bool success = false; - string tempPath = destinationPath + $".part{block.Index}"; - - for (int retry = block.RetryCount; retry < maxRetry && !success; retry++) - { - try - { - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); - var req = new HttpRequestMessage(HttpMethod.Get, fileUrl); - req.Headers.Range = new RangeHeaderValue(block.Start, block.End); - - // 增加连接超时设置 - client.Timeout = TimeSpan.FromSeconds(30); - - var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token); - var lastReadTime = DateTime.UtcNow; - bool dataReceived = false; - - using (var resp = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, downloadCts.Token)) - { - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 响应状态: {resp.StatusCode}"); - resp.EnsureSuccessStatusCode(); - using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - var stream = await resp.Content.ReadAsStreamAsync(); - byte[] buffer = new byte[8192]; - int read; - long blockDownloadedBytes = 0; - - while (true) - { - var readTask = stream.ReadAsync(buffer, 0, buffer.Length, downloadCts.Token); - var timeoutTask = Task.Delay(20000, downloadCts.Token); // 增加到20秒超时 - var completed = await Task.WhenAny(readTask, timeoutTask); - if (completed == timeoutTask) - { - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 20秒无数据,线程超时重试", LogHelper.LogType.Warning); - progressCallback?.Invoke(0, $"分块{block.Index} 20秒无数据,线程超时重试"); - downloadCts.Cancel(); - break; - } - read = await readTask; - if (read <= 0) break; - await fs.WriteAsync(buffer, 0, read, downloadCts.Token); - blockDownloadedBytes += read; - blockDownloaded[block.Index] = blockDownloadedBytes; - lastReadTime = DateTime.UtcNow; - dataReceived = true; - - // 合并所有块进度 - long totalDownloaded = blockDownloaded.Sum(); - double percent = (double)totalDownloaded / totalSize * 100; - progressCallback?.Invoke(percent, $"多线程下载中({threadCount}线程): {percent:F1}%"); - } - } - } - - if (!dataReceived) - { - throw new IOException("分块下载超时无数据"); - } - - // 验证分块大小是否正确 - var fileInfo = new FileInfo(tempPath); - long expectedSize = block.End - block.Start + 1; - if (fileInfo.Length != expectedSize) - { - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}大小不匹配,期望:{expectedSize},实际:{fileInfo.Length}", LogHelper.LogType.Warning); - throw new IOException($"分块{block.Index}大小不匹配"); - } - } - success = true; - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功"); - } - catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException) - { - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning); - progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}"); - - // 清理可能损坏的分块文件 - if (File.Exists(tempPath)) - { - try { File.Delete(tempPath); } catch { } - } - - // 增加重试间隔,避免频繁重试 - await Task.Delay(2000 * (retry + 1)); - } - } - if (success) - { - finishedBlocks[block.Index] = true; - } - else if (block.RetryCount + 1 < maxRetry) - { - // 失败但未超最大重试,重新入队 - block.RetryCount++; - blockQueue.Enqueue(block); - } - else - { - // 超过最大重试,取消所有任务 - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}超过最大重试次数,取消下载", LogHelper.LogType.Error); - cts.Cancel(); - break; - } - } - })); - } - - await Task.WhenAll(tasks); - - if (cts.IsCancellationRequested || finishedBlocks.Count != blockCount) - { - LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}", LogHelper.LogType.Warning); - progressCallback?.Invoke(0, $"{threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}"); - - // 清理分块文件 - for (int i = 0; i < blockCount; i++) - { - string tempPath = destinationPath + $".part{i}"; - if (File.Exists(tempPath)) File.Delete(tempPath); - } - - if (threadCount == threadOptions.Last()) - { - // 已经是最后一次尝试,降级为单线程 - LogHelper.WriteLogToFile("AutoUpdate | 所有多线程尝试失败,降级为单线程下载"); - progressCallback?.Invoke(0, "所有多线程尝试失败,降级为单线程下载"); - return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback); - } - - LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程"); - progressCallback?.Invoke(0, $"{threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程"); - continue; - } - - // 合并所有块 - try - { - using (var output = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - for (int i = 0; i < blockCount; i++) - { - string tempPath = destinationPath + $".part{i}"; - if (!File.Exists(tempPath)) - { - throw new FileNotFoundException($"分块文件不存在: {tempPath}"); - } - - using (var input = new FileStream(tempPath, FileMode.Open, FileAccess.Read)) - { - await input.CopyToAsync(output); - } - File.Delete(tempPath); - } - } - - progressCallback?.Invoke(100, $"多线程下载完成({threadCount}线程)"); - LogHelper.WriteLogToFile($"AutoUpdate | 多线程下载完成({threadCount}线程)"); - - // 文件大小校验 - FileInfo fileInfo = new FileInfo(destinationPath); - if (fileInfo.Length != totalSize) - { - LogHelper.WriteLogToFile($"AutoUpdate | 文件大小校验失败,本地:{fileInfo.Length},服务器:{totalSize}", LogHelper.LogType.Error); - File.Delete(destinationPath); - progressCallback?.Invoke(0, "文件大小校验失败,已删除损坏文件"); - return false; - } - - // ZIP文件完整性校验 - if (destinationPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) - { - try - { - ZipFile.OpenRead(destinationPath).Dispose(); - } - catch - { - LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压测试失败,文件可能已损坏", LogHelper.LogType.Error); - File.Delete(destinationPath); - progressCallback?.Invoke(0, "ZIP文件解压测试失败,已删除损坏文件"); - return false; - } - } - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 合并分块文件时出错: {ex.Message}", LogHelper.LogType.Error); - File.Delete(destinationPath); - progressCallback?.Invoke(0, $"合并分块文件时出错: {ex.Message}"); - return false; - } - } - return false; - } - - // 单线程下载方法 - private static async Task DownloadSingleThread(string fileUrl, string destinationPath, long totalSize, Action progressCallback = null) - { - try - { - LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}"); - progressCallback?.Invoke(0, "开始单线程下载"); - - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); - client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间 - - using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead)) - { - resp.EnsureSuccessStatusCode(); - using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - var stream = await resp.Content.ReadAsStreamAsync(); - byte[] buffer = new byte[8192]; - int read; - long downloaded = 0; - var lastProgressUpdate = DateTime.UtcNow; - - while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - await fs.WriteAsync(buffer, 0, read); - downloaded += read; - - // 限制进度更新频率,避免UI卡顿 - if (DateTime.UtcNow - lastProgressUpdate > TimeSpan.FromMilliseconds(500)) - { - if (totalSize > 0) - { - double percent = (double)downloaded / totalSize * 100; - progressCallback?.Invoke(percent, $"单线程下载中: {percent:F1}%"); - } - lastProgressUpdate = DateTime.UtcNow; - } - } - } - } - } - - progressCallback?.Invoke(100, "单线程下载完成"); - LogHelper.WriteLogToFile("AutoUpdate | 单线程下载完成"); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error); - progressCallback?.Invoke(0, $"单线程下载失败: {ex.Message}"); - return false; - } - } - - // 获取文件总大小 - private static async Task GetContentLength(string fileUrl) - { - try - { - using (var client = new HttpClient()) - { - var req = new HttpRequestMessage(HttpMethod.Head, fileUrl); - var resp = await client.SendAsync(req); - if (resp.IsSuccessStatusCode && resp.Content.Headers.ContentLength.HasValue) - return resp.Content.Headers.ContentLength.Value; - } - } - catch { } - return -1; - } - - // 保存下载状态 - private static void SaveDownloadStatus(bool isSuccess) - { - try - { - if (statusFilePath == null) return; - - string directory = Path.GetDirectoryName(statusFilePath); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - File.WriteAllText(statusFilePath, isSuccess.ToString()); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 保存下载状态时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 安装新版本应用 - public static void InstallNewVersionApp(string version, bool isInSilence) - { - try - { - // 在更新前备份设置文件 - try - { - if (MainWindow.Settings.Advanced.IsAutoBackupBeforeUpdate) - { - string backupDir = Path.Combine(App.RootPath, "Backups"); - if (!Directory.Exists(backupDir)) - { - Directory.CreateDirectory(backupDir); - LogHelper.WriteLogToFile($"创建备份目录: {backupDir}"); - } - - string backupFileName = $"Settings_BeforeUpdate_v{version}_{DateTime.Now:yyyyMMdd_HHmmss}.json"; - string backupPath = Path.Combine(backupDir, backupFileName); - - string settingsJson = JsonConvert.SerializeObject(MainWindow.Settings, Formatting.Indented); - File.WriteAllText(backupPath, settingsJson); - - LogHelper.WriteLogToFile($"更新前自动备份设置成功: {backupPath}"); - } - else - { - LogHelper.WriteLogToFile("更新前自动备份功能已禁用,跳过备份"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新前自动备份设置时出错: {ex.Message}", LogHelper.LogType.Error); - } - - string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip"); - LogHelper.WriteLogToFile($"AutoUpdate | 检查ZIP文件: {zipFilePath}"); - - if (!File.Exists(zipFilePath)) - { - LogHelper.WriteLogToFile($"AutoUpdate | ZIP文件未找到: {zipFilePath}", LogHelper.LogType.Error); - return; - } - - FileInfo fileInfo = new FileInfo(zipFilePath); - if (fileInfo.Length == 0) - { - LogHelper.WriteLogToFile("AutoUpdate | ZIP文件为空,无法继续", LogHelper.LogType.Error); - return; - } - LogHelper.WriteLogToFile($"AutoUpdate | ZIP文件大小: {fileInfo.Length} 字节"); - - string currentAppDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - int currentProcessId = Process.GetCurrentProcess().Id; - string appPath = Assembly.GetExecutingAssembly().Location; - - LogHelper.WriteLogToFile($"AutoUpdate | 当前应用程序目录: {currentAppDir}"); - LogHelper.WriteLogToFile($"AutoUpdate | 当前进程ID: {currentProcessId}"); - LogHelper.WriteLogToFile($"AutoUpdate | 静默更新模式: {isInSilence}"); - - string batchFilePath = Path.Combine(Path.GetTempPath(), "UpdateICC_" + Guid.NewGuid().ToString().Substring(0, 8) + ".bat"); - LogHelper.WriteLogToFile($"AutoUpdate | 创建更新批处理文件: {batchFilePath}"); - - StringBuilder batchContent = new StringBuilder(); - batchContent.AppendLine("@echo off"); - - batchContent.AppendLine("echo Set objShell = CreateObject(\"WScript.Shell\") > \"%temp%\\hideme.vbs\""); - batchContent.AppendLine("echo objShell.Run \"cmd /c \"\"\" ^& WScript.Arguments(0) ^& \"\"\"\", 0, True >> \"%temp%\\hideme.vbs\""); - batchContent.AppendLine("echo Wscript.Sleep 100 >> \"%temp%\\hideme.vbs\""); - - string updateBatPath = Path.Combine(Path.GetTempPath(), "ICCUpdate_" + Guid.NewGuid().ToString().Substring(0, 8) + ".bat"); - batchContent.AppendLine($"echo @echo off > \"{updateBatPath}\""); - batchContent.AppendLine($"echo set PROC_ID={currentProcessId} >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo :CHECK_PROCESS >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo tasklist /fi \"PID eq %PROC_ID%\" ^| find \"%PROC_ID%\" ^> nul >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if %%ERRORLEVEL%% == 0 ( >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo timeout /t 1 /nobreak ^> nul >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo goto CHECK_PROCESS >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo ) >> \"{updateBatPath}\""); - - batchContent.AppendLine($"echo echo Application closed, starting update process... >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo timeout /t 2 /nobreak ^> nul >> \"{updateBatPath}\""); - - string extractPath = Path.Combine(updatesFolderPath, $"Extract_{version}"); - batchContent.AppendLine($"echo echo Extracting update files... >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if exist \"{extractPath}\" rd /s /q \"{extractPath}\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo mkdir \"{extractPath}\" >> \"{updateBatPath}\""); - - batchContent.AppendLine($"echo powershell -command \"Expand-Archive -Path '{zipFilePath.Replace("'", "''")}' -DestinationPath '{extractPath.Replace("'", "''")}' -Force\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if %%ERRORLEVEL%% neq 0 ( >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo goto ERROR_EXIT >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo ) >> \"{updateBatPath}\""); - - batchContent.AppendLine($"echo echo Copying updated files to application directory... >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo xcopy /s /y /e \"{extractPath}\\*\" \"{currentAppDir}\\\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if %%ERRORLEVEL%% neq 0 ( >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo goto ERROR_EXIT >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo ) >> \"{updateBatPath}\""); - - batchContent.AppendLine($"echo echo Cleaning up temporary files... >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if exist \"{extractPath}\" rd /s /q \"{extractPath}\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if exist \"{zipFilePath}\" del /f /q \"{zipFilePath}\" >> \"{updateBatPath}\""); - - batchContent.AppendLine($"echo echo Update completed successfully! >> \"{updateBatPath}\""); - - if (isInSilence) - { - batchContent.AppendLine($"echo echo 自动启动应用程序... >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo start \"\" \"{appPath}\" >> \"{updateBatPath}\""); - } - else - { - batchContent.AppendLine($"echo taskkill /F /IM \"InkCanvasForClass.exe\" >nul 2>nul >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo :: 检查应用程序是否已经在运行 >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo tasklist /FI \"IMAGENAME eq InkCanvasForClass.exe\" | find /i \"InkCanvasForClass.exe\" > nul >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo if %%ERRORLEVEL%% neq 0 ( >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo echo 启动应用程序... >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo start \"\" \"{appPath}\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo ) else ( >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo echo 应用程序已经在运行,不再重复启动 >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo ) >> \"{updateBatPath}\""); - } - - batchContent.AppendLine($"echo exit /b 0 >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo goto EXIT >> \"{updateBatPath}\""); - - if (isInSilence) - { - batchContent.AppendLine($"echo :ERROR_EXIT >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo echo Update failed! >> \"%temp%\\icc_update_error.log\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo exit /b 1 >> \"{updateBatPath}\""); - } - else - { - batchContent.AppendLine($"echo :ERROR_EXIT >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo start \"\" cmd /c \"echo Update failed! ^& pause\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo exit /b 1 >> \"{updateBatPath}\""); - } - - batchContent.AppendLine($"echo :EXIT >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo del \"{updateBatPath}\" >> \"{updateBatPath}\""); - batchContent.AppendLine($"echo exit >> \"{updateBatPath}\""); - - batchContent.AppendLine($"wscript \"%temp%\\hideme.vbs\" \"{updateBatPath}\""); - batchContent.AppendLine("del \"%temp%\\hideme.vbs\""); - batchContent.AppendLine("exit"); - - File.WriteAllText(batchFilePath, batchContent.ToString()); - LogHelper.WriteLogToFile("AutoUpdate | 创建更新批处理文件完成"); - - Process.Start(new ProcessStartInfo - { - FileName = batchFilePath, - CreateNoWindow = true, - UseShellExecute = true, - WindowStyle = ProcessWindowStyle.Hidden - }); - - LogHelper.WriteLogToFile("AutoUpdate | 启动更新批处理进程(隐藏窗口)"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 准备更新安装时出错: {ex.Message}", LogHelper.LogType.Error); - if (ex.InnerException != null) - { - LogHelper.WriteLogToFile($"AutoUpdate | 内部异常: {ex.InnerException.Message}", LogHelper.LogType.Error); - } - } - } - - // 获取远程内容的通用方法 - public static async Task GetRemoteContent(string fileUrl) - { - // 检测是否为Windows 7 - var osVersion = Environment.OSVersion; - bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1; - - if (isWindows7) - { - // Windows 7使用特殊配置 - using (var handler = new HttpClientHandler()) - { - try - { - // 配置HttpClientHandler以支持Windows 7 - handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; - - using (HttpClient client = new HttpClient(handler)) - { - client.Timeout = RequestTimeout; - LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}"); - var downloadTask = client.GetAsync(fileUrl); - var timeoutTask = Task.Delay(RequestTimeout); - var completedTask = await Task.WhenAny(downloadTask, timeoutTask); - if (completedTask == timeoutTask) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error); - return null; - } - HttpResponseMessage response = await downloadTask; - LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}"); - response.EnsureSuccessStatusCode(); - string content = await response.Content.ReadAsStringAsync(); - return content; - } - } - catch (HttpRequestException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error); - } - catch (TaskCanceledException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - } - - // 其他Windows版本使用标准配置 - using (HttpClient client = new HttpClient()) - { - try - { - client.Timeout = RequestTimeout; - LogHelper.WriteLogToFile($"AutoUpdate | 发送HTTP请求到: {fileUrl}"); - var downloadTask = client.GetAsync(fileUrl); - var timeoutTask = Task.Delay(RequestTimeout); - var completedTask = await Task.WhenAny(downloadTask, timeoutTask); - if (completedTask == timeoutTask) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时 ({RequestTimeout.TotalSeconds}秒)", LogHelper.LogType.Error); - return null; - } - HttpResponseMessage response = await downloadTask; - LogHelper.WriteLogToFile($"AutoUpdate | HTTP响应状态: {response.StatusCode}"); - response.EnsureSuccessStatusCode(); - string content = await response.Content.ReadAsStringAsync(); - return content; - } - catch (HttpRequestException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | HTTP请求错误: {ex.Message}", LogHelper.LogType.Error); - } - catch (TaskCanceledException ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 请求超时: {ex.Message}", LogHelper.LogType.Error); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 错误: {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - } - - // 使用指定线路组获取更新日志 - public static async Task GetUpdateLogWithLineGroup(UpdateLineGroup group) - { - return await GetRemoteContent(group.LogUrl); - } - - // 获取更新日志(自动选择最快线路组) - public static async Task GetUpdateLog(UpdateChannel channel = UpdateChannel.Release) - { - var group = await GetFastestLineGroup(channel); - if (group == null) return "# 无法获取更新日志\n\n所有线路均不可用。"; - return await GetUpdateLogWithLineGroup(group); - } - - // 删除更新文件夹 - public static void DeleteUpdatesFolder() - { - try - { - if (Directory.Exists(updatesFolderPath)) - { - foreach (string file in Directory.GetFiles(updatesFolderPath, "*", SearchOption.AllDirectories)) - { - try { File.Delete(file); } catch { } - } - foreach (string dir in Directory.GetDirectories(updatesFolderPath)) - { - try { Directory.Delete(dir, true); } catch { } - } - try { Directory.Delete(updatesFolderPath, true); } catch { } - } - } - catch { } - } - - // 版本修复方法,强制下载并安装指定通道的最新版本 - public static async Task FixVersion(UpdateChannel channel = UpdateChannel.Release) - { - try - { - LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}"); - - // 获取远程版本号(自动选择最快线路组,始终下载远程版本,版本修复模式) - var (remoteVersion, group, _) = await CheckForUpdates(channel, true, true); - if (string.IsNullOrEmpty(remoteVersion) || group == null) - { - LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error); - return false; - } - - LogHelper.WriteLogToFile($"AutoUpdate | 修复版本远程版本: {remoteVersion}"); - - // 无论版本是否为最新,都下载远程版本 - bool downloadResult = await DownloadSetupFile(remoteVersion, group); - if (!downloadResult) - { - LogHelper.WriteLogToFile("AutoUpdate | 修复版本时下载更新失败", LogHelper.LogType.Error); - return false; - } - - // 执行安装,非静默模式 - InstallNewVersionApp(remoteVersion, false); - App.IsAppExitByUser = true; - Application.Current.Dispatcher.Invoke(() => { - Application.Current.Shutdown(); - }); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | FixVersion错误: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - // 获取所有GitHub历史版本(Release) - public static async Task> GetAllGithubReleases(UpdateChannel channel = UpdateChannel.Release) - { - var result = new List<(string, string, string)>(); - try - { - string apiUrl = channel == UpdateChannel.Beta - ? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases" - : "https://api.github.com/repos/InkCanvasForClass/community/releases"; - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); - var response = await client.GetStringAsync(apiUrl); - var arr = JArray.Parse(response); - foreach (var item in arr) - { - string version = item["tag_name"]?.ToString(); - string releaseNotes = item["body"]?.ToString(); - string downloadUrl = item["assets"]?.First?["browser_download_url"]?.ToString(); - if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl)) - result.Add((version, downloadUrl, releaseNotes)); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 获取历史版本失败: {ex.Message}", LogHelper.LogType.Error); - } - return result; - } - - // 测试Windows 7 TLS连接的方法 - public static async Task TestWindows7TlsConnection() - { - try - { - // 检测是否为Windows 7 - var osVersion = Environment.OSVersion; - bool isWindows7 = osVersion.Version.Major == 6 && osVersion.Version.Minor == 1; - - if (!isWindows7) - { - LogHelper.WriteLogToFile("AutoUpdate | 当前系统不是Windows 7,跳过TLS连接测试"); - return true; // 非Windows 7系统直接返回成功 - } - - LogHelper.WriteLogToFile("AutoUpdate | 开始测试Windows 7 TLS连接..."); - - // 测试GitHub连接 - var testUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt"; - - using (var handler = new HttpClientHandler()) - { - handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true; - - using (var client = new HttpClient(handler)) - { - client.Timeout = TimeSpan.FromSeconds(10); - var response = await client.GetAsync(testUrl); - - if (response.IsSuccessStatusCode) - { - LogHelper.WriteLogToFile("AutoUpdate | Windows 7 TLS连接测试成功"); - return true; - } - - LogHelper.WriteLogToFile($"AutoUpdate | Windows 7 TLS连接测试失败,状态码: {response.StatusCode}", LogHelper.LogType.Error); - return false; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | Windows 7 TLS连接测试异常: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 获取指定版本的发布时间 - /// - /// 版本号 - /// 更新通道 - /// 版本发布时间,如果获取失败则返回null - public static async Task GetVersionReleaseTime(string version, UpdateChannel channel = UpdateChannel.Release) - { - try - { - var (_, _, _, releaseTime) = await GetGithubReleaseByVersion(version, channel); - return releaseTime; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 获取版本 {version} 发布时间失败: {ex.Message}", LogHelper.LogType.Warning); - return null; - } - } - - /// - /// 启动手动指定版本的多线路多线程下载并自动安装(用于历史版本回滚等场景) - /// - public static async Task StartManualDownloadAndInstall(string version, UpdateChannel channel, Action progressCallback = null) - { - try - { - // 先检测并排序所有可用线路组 - var groups = await GetAvailableLineGroupsOrdered(channel); - bool downloadSuccess = await DownloadSetupFileWithFallback(version, groups, progressCallback); - if (!downloadSuccess) - { - LogHelper.WriteLogToFile($"AutoUpdate | 手动下载版本{version}失败"); - return false; - } - LogHelper.WriteLogToFile($"AutoUpdate | 手动安装版本: {version}"); - InstallNewVersionApp(version, false); - App.IsAppExitByUser = true; - Application.Current.Dispatcher.Invoke(() => { - Application.Current.Shutdown(); - }); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 手动下载或安装异常: {ex.Message}", LogHelper.LogType.Error); - progressCallback?.Invoke(0, $"下载异常: {ex.Message}"); - return false; - } - } - } - - internal class AutoUpdateWithSilenceTimeComboBox - { - public static ObservableCollection Hours { get; set; } = new ObservableCollection(); - public static ObservableCollection Minutes { get; set; } = new ObservableCollection(); - - public static void InitializeAutoUpdateWithSilenceTimeComboBoxOptions(ComboBox startTimeComboBox, ComboBox endTimeComboBox) - { - for (int hour = 0; hour <= 23; ++hour) - { - Hours.Add(hour.ToString("00")); - } - for (int minute = 0; minute <= 59; minute += 20) - { - Minutes.Add(minute.ToString("00")); - } - startTimeComboBox.ItemsSource = Hours.SelectMany(h => Minutes.Select(m => $"{h}:{m}")); - endTimeComboBox.ItemsSource = Hours.SelectMany(h => Minutes.Select(m => $"{h}:{m}")); - } - - public static bool CheckIsInSilencePeriod(string startTime, string endTime) - { - if (startTime == endTime) return true; - DateTime currentTime = DateTime.Now; - - DateTime StartTime = DateTime.ParseExact(startTime, "HH:mm", null); - DateTime EndTime = DateTime.ParseExact(endTime, "HH:mm", null); - if (StartTime <= EndTime) - { // 单日时间段 - return currentTime >= StartTime && currentTime <= EndTime; - } // 跨越两天的时间段 - return currentTime >= StartTime || currentTime <= EndTime; - } - } -} - diff --git a/Ink Canvas/Helpers/AvoidFullScreenHelper.cs b/Ink Canvas/Helpers/AvoidFullScreenHelper.cs deleted file mode 100644 index 88a6cdc2..00000000 --- a/Ink Canvas/Helpers/AvoidFullScreenHelper.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Forms; -using System.Windows.Interop; - -namespace Ink_Canvas.Helpers -{ - /// - /// 防止窗口进入全屏状态的辅助类 - /// - public static class AvoidFullScreenHelper - { - private static readonly DependencyProperty IsAvoidFullScreenEnabledProperty = - DependencyProperty.RegisterAttached( - "IsAvoidFullScreenEnabled", - typeof(bool), - typeof(AvoidFullScreenHelper)); - - private static bool _isBoardMode; - public static void SetBoardMode(bool isBoardMode) - { - _isBoardMode = isBoardMode; - } - - public static void StartAvoidFullScreen(Window window) - { - if (window == null) - throw new ArgumentNullException(nameof(window)); - - if (!(bool)window.GetValue(IsAvoidFullScreenEnabledProperty)) - { - var hwndSource = PresentationSource.FromVisual(window) as HwndSource; - if (hwndSource != null) - { - hwndSource.AddHook(KeepInWorkingAreaHook); - window.SetValue(IsAvoidFullScreenEnabledProperty, true); - } - } - } - - public static void StopAvoidFullScreen(Window window) - { - if (window == null) - throw new ArgumentNullException(nameof(window)); - - if ((bool)window.GetValue(IsAvoidFullScreenEnabledProperty)) - { - var hwndSource = PresentationSource.FromVisual(window) as HwndSource; - if (hwndSource != null) - { - hwndSource.RemoveHook(KeepInWorkingAreaHook); - window.ClearValue(IsAvoidFullScreenEnabledProperty); - } - } - } - - public static bool GetIsAvoidFullScreenEnabled(DependencyObject obj) => (bool)obj.GetValue(IsAvoidFullScreenEnabledProperty); - public static void SetIsAvoidFullScreenEnabled(DependencyObject obj, bool value) => obj.SetValue(IsAvoidFullScreenEnabledProperty, value); - - private static IntPtr KeepInWorkingAreaHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - // 只拦截主画布窗口的全屏(最大化)操作 - var window = HwndSource.FromHwnd(hwnd)?.RootVisual as Window; - if (window == null) return IntPtr.Zero; - // 这里假设主画布窗口类名为MainWindow(如有不同请调整) - if (window.GetType().Name != "MainWindow") return IntPtr.Zero; - - if (_isBoardMode) - { - // 画板模式下允许全屏/最大化,不拦截 - return IntPtr.Zero; - } - const int WM_WINDOWPOSCHANGING = 0x0046; - const int WM_SYSCOMMAND = 0x0112; - const int SC_MAXIMIZE = 0xF030; - if (msg == WM_SYSCOMMAND && wParam.ToInt32() == SC_MAXIMIZE) - { - // 拦截最大化命令,强制还原窗口并调整到工作区 - window.WindowState = WindowState.Normal; - var workingArea = GetWorkingArea(new Rect(window.Left, window.Top, window.Width, window.Height)); - window.Left = workingArea.Left; - window.Top = workingArea.Top; - window.Width = workingArea.Width; - window.Height = workingArea.Height; - handled = true; - return IntPtr.Zero; - } - if (msg != WM_WINDOWPOSCHANGING) - return IntPtr.Zero; - - try - { - var pos = (WindowPosition)Marshal.PtrToStructure(lParam, typeof(WindowPosition)); - if ((pos.Flags & (WindowPositionFlags.SWP_NOMOVE | WindowPositionFlags.SWP_NOSIZE)) != 0) - return IntPtr.Zero; - // 只处理主画布窗口 - // 计算目标矩形 - var targetRect = new Rect( - (pos.Flags & WindowPositionFlags.SWP_NOMOVE) == 0 ? pos.X : window.Left, - (pos.Flags & WindowPositionFlags.SWP_NOMOVE) == 0 ? pos.Y : window.Top, - (pos.Flags & WindowPositionFlags.SWP_NOSIZE) == 0 ? pos.Width : window.Width, - (pos.Flags & WindowPositionFlags.SWP_NOSIZE) == 0 ? pos.Height : window.Height); - var workingArea = GetWorkingArea(targetRect); - var adjustedRect = AdjustRectToWorkingArea(targetRect, workingArea); - pos.X = (int)adjustedRect.Left; - pos.Y = (int)adjustedRect.Top; - pos.Width = (int)adjustedRect.Width; - pos.Height = (int)adjustedRect.Height; - pos.Flags &= ~(WindowPositionFlags.SWP_NOSIZE | WindowPositionFlags.SWP_NOMOVE | WindowPositionFlags.SWP_NOREDRAW); - pos.Flags |= WindowPositionFlags.SWP_NOCOPYBITS; - Marshal.StructureToPtr(pos, lParam, false); - } - catch (Exception ex) - { - Console.WriteLine($"窗口位置调整失败: {ex.Message}"); - } - return IntPtr.Zero; - } - - private static Rect GetWorkingArea(Rect windowRect) - { - // 获取所有显示器 - var screens = Screen.AllScreens; - - // 确定窗口主要位于哪个显示器上 - Screen targetScreen = null; - double maxIntersection = 0; - - foreach (var screen in screens) - { - var screenRect = new Rect( - screen.WorkingArea.X, - screen.WorkingArea.Y, - screen.WorkingArea.Width, - screen.WorkingArea.Height); - - var intersection = Rect.Intersect(windowRect, screenRect); - if (intersection.Width * intersection.Height > maxIntersection) - { - maxIntersection = intersection.Width * intersection.Height; - targetScreen = screen; - } - } - - // 如果没找到,使用主显示器 - if (targetScreen == null) - targetScreen = Screen.PrimaryScreen; - - return new Rect( - targetScreen.WorkingArea.X, - targetScreen.WorkingArea.Y, - targetScreen.WorkingArea.Width, - targetScreen.WorkingArea.Height); - } - - private static Rect AdjustRectToWorkingArea(Rect windowRect, Rect workingArea) - { - // 调整尺寸以适应工作区域 - if (windowRect.Width > workingArea.Width) - windowRect.Width = workingArea.Width; - - if (windowRect.Height > workingArea.Height) - windowRect.Height = workingArea.Height; - - // 调整位置以确保窗口完全在工作区域内 - if (windowRect.Left < workingArea.Left) - windowRect.X = workingArea.Left; - else if (windowRect.Right > workingArea.Right) - windowRect.X = workingArea.Right - windowRect.Width; - - if (windowRect.Top < workingArea.Top) - windowRect.Y = workingArea.Top; - else if (windowRect.Bottom > workingArea.Bottom) - windowRect.Y = workingArea.Bottom - windowRect.Height; - - return windowRect; - } - } - - // 使用WPF原生类型替代Win32结构 - [StructLayout(LayoutKind.Sequential)] - internal struct WindowPosition - { - public IntPtr Hwnd; - public IntPtr HwndInsertAfter; - public int X; - public int Y; - public int Width; - public int Height; - public WindowPositionFlags Flags; - } - - [Flags] - internal enum WindowPositionFlags : uint - { - SWP_NOSIZE = 0x0001, - SWP_NOMOVE = 0x0002, - SWP_NOZORDER = 0x0004, - SWP_NOREDRAW = 0x0008, - SWP_NOACTIVATE = 0x0010, - SWP_FRAMECHANGED = 0x0020, - SWP_SHOWWINDOW = 0x0040, - SWP_HIDEWINDOW = 0x0080, - SWP_NOCOPYBITS = 0x0100, - SWP_NOOWNERZORDER = 0x0200, - SWP_NOSENDCHANGING = 0x0400, - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Converters.cs b/Ink Canvas/Helpers/Converters.cs deleted file mode 100644 index ac5f7612..00000000 --- a/Ink Canvas/Helpers/Converters.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace Ink_Canvas.Converter -{ - public class BooleanToVisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if ((bool)value) - { - return Visibility.Visible; - } - - return Visibility.Collapsed; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if ((bool)value) - { - return Visibility.Visible; - } - - return Visibility.Collapsed; - } - } - public class VisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - Visibility visibility = (Visibility)value; - if (visibility == Visibility.Visible) - { - return Visibility.Collapsed; - } - - return Visibility.Visible; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - Visibility visibility = (Visibility)value; - if (visibility == Visibility.Visible) - { - return Visibility.Collapsed; - } - - return Visibility.Visible; - } - } - - public class IntNumberToString : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if ((double)value == 0) - { - return "无限制"; - } - - return ((double)value) + "人"; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if ((double)value == 0) - { - return "无限制"; - } - - return ((double)value) + "人"; - } - } - - public class IntNumberToString2 : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if ((double)value == 0) - { - return "自动截图"; - } - - return ((double)value) + "条"; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if ((double)value == 0) - { - return "自动截图"; - } - - return ((double)value) + "条"; - } - } - - public class IsEnabledToOpacityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - bool isChecked = (bool)value; - if (isChecked) - { - return 1d; - } - - return 0.35; - } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } - } -} diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs deleted file mode 100644 index 51912ee6..00000000 --- a/Ink Canvas/Helpers/DeviceIdentifier.cs +++ /dev/null @@ -1,2749 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using Microsoft.Win32; -using Newtonsoft.Json; - -namespace Ink_Canvas.Helpers -{ - /// - /// 设备标识符和使用频率监控类 - /// - internal static class DeviceIdentifier - { - // 多重备份路径策略 - private static readonly string DeviceIdFilePath = Path.Combine(App.RootPath, "device_id.dat"); - private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.json"); - - // 使用频率数据的多重隐藏备份路径 - private static readonly string BackupDeviceIdPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "ICC", ".sys", "device.dat"); - private static readonly string BackupUsageStatsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "ICC", ".sys", "usage.dat"); - - // 使用频率数据的额外隐藏备份位置 - private static readonly string SecondaryUsageBackupPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "Microsoft", "Windows", ".icc", "usage_backup.tmp"); - private static readonly string TertiaryUsageBackupPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), - "ICC", ".cache", "usage_cache.dat"); - private static readonly string QuaternaryUsageBackupPath = Path.Combine(Path.GetTempPath(), - ".icc_temp", "usage_temp.dat"); - - // 数据完整性验证密钥 - private static readonly string DataIntegrityKey = "ICC_DEVICE_INTEGRITY_2024"; - - private static readonly string DeviceId; - private static readonly object fileLock = new object(); - - static DeviceIdentifier() - { - // 在静态构造函数中初始化设备ID - DeviceId = GetOrCreateDeviceId(); - - // 执行数据完整性检查和自动修复 - try - { - PerformDataIntegrityCheck(); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 初始化时数据完整性检查失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 获取或创建设备ID - /// - /// 25字符的唯一设备标识符 - public static string GetDeviceId() - { - return DeviceId; - } - - /// - /// 获取或创建设备ID(内部方法)- 支持多重备份恢复 - /// - private static string GetOrCreateDeviceId() - { - lock (fileLock) - { - try - { - // 1. 尝试从主文件读取设备ID - string deviceId = LoadDeviceIdFromFile(DeviceIdFilePath); - if (!string.IsNullOrEmpty(deviceId)) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从主文件读取设备ID: {deviceId}"); - // 确保备份同步 - SaveDeviceIdToAllLocations(deviceId); - return deviceId; - } - - // 2. 尝试从备份文件恢复 - deviceId = LoadDeviceIdFromFile(BackupDeviceIdPath); - if (!string.IsNullOrEmpty(deviceId)) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从备份文件恢复设备ID: {deviceId}"); - SaveDeviceIdToAllLocations(deviceId); - return deviceId; - } - - // 3. 尝试从注册表恢复 - deviceId = LoadDeviceIdFromRegistry(); - if (!string.IsNullOrEmpty(deviceId)) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表恢复设备ID: {deviceId}"); - SaveDeviceIdToAllLocations(deviceId); - return deviceId; - } - - // 4. 生成新的设备ID - string newDeviceId = GenerateDeviceId(); - LogHelper.WriteLogToFile($"DeviceIdentifier | 生成新设备ID: {newDeviceId}"); - - // 5. 保存到所有位置 - SaveDeviceIdToAllLocations(newDeviceId); - - return newDeviceId; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 获取设备ID时出错: {ex.Message}", LogHelper.LogType.Error); - // 返回一个基于时间戳的备用ID - return GenerateFallbackDeviceId(); - } - } - } - - /// - /// 生成25字符的唯一设备ID - /// - private static string GenerateDeviceId() - { - try - { - // 收集硬件信息 - var hardwareInfo = new StringBuilder(); - - // 使用反射获取硬件信息,避免直接引用System.Management - try - { - // 尝试加载System.Management程序集 - var assembly = Assembly.Load("System.Management"); - if (assembly != null) - { - // CPU信息 - try - { - var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); - var searcher = Activator.CreateInstance(searcherType, "SELECT ProcessorId FROM Win32_Processor"); - var getMethod = searcherType.GetMethod("Get"); - var enumerator = getMethod.Invoke(searcher, null); - - var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); - var currentProperty = enumerator.GetType().GetProperty("Current"); - - if ((bool)moveNextMethod.Invoke(enumerator, null)) - { - var obj = currentProperty.GetValue(enumerator); - var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); - var processorId = indexer.GetValue(obj, new object[] { "ProcessorId" }); - hardwareInfo.Append(processorId?.ToString() ?? ""); - } - - var disposeMethod = searcher.GetType().GetMethod("Dispose"); - disposeMethod?.Invoke(searcher, null); - } - catch { } - - // 主板序列号 - try - { - var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); - var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BaseBoard"); - var getMethod = searcherType.GetMethod("Get"); - var enumerator = getMethod.Invoke(searcher, null); - - var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); - var currentProperty = enumerator.GetType().GetProperty("Current"); - - if ((bool)moveNextMethod.Invoke(enumerator, null)) - { - var obj = currentProperty.GetValue(enumerator); - var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); - var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" }); - hardwareInfo.Append(serialNumber?.ToString() ?? ""); - } - - var disposeMethod = searcher.GetType().GetMethod("Dispose"); - disposeMethod?.Invoke(searcher, null); - } - catch { } - - // BIOS序列号 - try - { - var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); - var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BIOS"); - var getMethod = searcherType.GetMethod("Get"); - var enumerator = getMethod.Invoke(searcher, null); - - var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); - var currentProperty = enumerator.GetType().GetProperty("Current"); - - if ((bool)moveNextMethod.Invoke(enumerator, null)) - { - var obj = currentProperty.GetValue(enumerator); - var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); - var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" }); - hardwareInfo.Append(serialNumber?.ToString() ?? ""); - } - - var disposeMethod = searcher.GetType().GetMethod("Dispose"); - disposeMethod?.Invoke(searcher, null); - } - catch { } - - // 主硬盘序列号 - try - { - var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); - var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType='Fixed hard disk media'"); - var getMethod = searcherType.GetMethod("Get"); - var enumerator = getMethod.Invoke(searcher, null); - - var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); - var currentProperty = enumerator.GetType().GetProperty("Current"); - - if ((bool)moveNextMethod.Invoke(enumerator, null)) - { - var obj = currentProperty.GetValue(enumerator); - var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); - var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" }); - hardwareInfo.Append(serialNumber?.ToString() ?? ""); - } - - var disposeMethod = searcher.GetType().GetMethod("Dispose"); - disposeMethod?.Invoke(searcher, null); - } - catch { } - } - } - catch { } - - // 如果硬件信息不足,添加系统信息 - if (hardwareInfo.Length < 10) - { - hardwareInfo.Append(Environment.MachineName); - hardwareInfo.Append(Environment.UserName); - hardwareInfo.Append(Environment.OSVersion); - } - - // 生成哈希 - using (var sha256 = SHA256.Create()) - { - byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hardwareInfo.ToString())); - string hashString = BitConverter.ToString(hashBytes).Replace("-", ""); - - // 取前25个字符,确保唯一性 - string deviceId = hashString.Substring(0, 25); - - // 添加校验位(第25位) - int checksum = 0; - for (int i = 0; i < 24; i++) - { - checksum += Convert.ToInt32(deviceId[i]); - } - checksum %= 36; // 0-9, A-Z - char checksumChar = checksum < 10 ? (char)(checksum + '0') : (char)(checksum - 10 + 'A'); - - return deviceId.Substring(0, 24) + checksumChar; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 生成设备ID时出错: {ex.Message}", LogHelper.LogType.Error); - return GenerateFallbackDeviceId(); - } - } - - /// - /// 生成备用设备ID(基于时间戳) - /// - private static string GenerateFallbackDeviceId() - { - try - { - string timestamp = DateTime.Now.Ticks.ToString("X"); - string random = Guid.NewGuid().ToString("N").Substring(0, 8); - string combined = timestamp + random; - - using (var sha256 = SHA256.Create()) - { - byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined)); - string hashString = BitConverter.ToString(hashBytes).Replace("-", ""); - return hashString.Substring(0, 25); - } - } - catch - { - // 最后的备用方案 - return "ICC" + DateTime.Now.ToString("yyyyMMddHHmmss") + "000000000"; - } - } - - /// - /// 验证设备ID格式 - /// - private static bool IsValidDeviceId(string deviceId) - { - if (string.IsNullOrEmpty(deviceId) || deviceId.Length != 25) - return false; - - // 验证字符集(只允许数字和大写字母) - if (!deviceId.All(c => char.IsLetterOrDigit(c) && (char.IsDigit(c) || char.IsUpper(c)))) - return false; - - // 验证校验位 - try - { - int checksum = 0; - for (int i = 0; i < 24; i++) - { - checksum += Convert.ToInt32(deviceId[i]); - } - checksum %= 36; - char expectedChecksum = checksum < 10 ? (char)(checksum + '0') : (char)(checksum - 10 + 'A'); - return deviceId[24] == expectedChecksum; - } - catch - { - return false; - } - } - - /// - /// 从文件加载设备ID - /// - private static string LoadDeviceIdFromFile(string filePath) - { - try - { - if (File.Exists(filePath)) - { - string content = File.ReadAllText(filePath).Trim(); - if (IsValidDeviceId(content)) - { - return content; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载设备ID失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 从注册表加载设备ID - /// - private static string LoadDeviceIdFromRegistry() - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(@"Software\ICC\DeviceInfo")) - { - if (key != null) - { - var value = key.GetValue("DeviceId") as string; - if (IsValidDeviceId(value)) - { - return value; - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表加载设备ID失败: {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 保存设备ID到所有位置 - /// - private static void SaveDeviceIdToAllLocations(string deviceId) - { - // 保存到主文件 - SaveDeviceIdToFile(DeviceIdFilePath, deviceId); - - // 保存到备份文件 - SaveDeviceIdToFile(BackupDeviceIdPath, deviceId); - - // 保存到注册表 - SaveDeviceIdToRegistry(deviceId); - } - - /// - /// 保存设备ID到文件 - /// - private static void SaveDeviceIdToFile(string filePath, string deviceId) - { - try - { - // 确保目录存在 - var directory = Path.GetDirectoryName(filePath); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - // 设置隐藏属性 - if (filePath.Contains(".sys")) - { - var dirInfo = new DirectoryInfo(directory); - dirInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } - } - - File.WriteAllText(filePath, deviceId); - - // 设置文件属性为隐藏和系统文件 - if (filePath.Contains(".sys")) - { - var fileInfo = new FileInfo(filePath); - fileInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 设备ID已保存到: {filePath}"); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 保存设备ID到注册表 - /// - private static void SaveDeviceIdToRegistry(string deviceId) - { - try - { - using (var key = Registry.CurrentUser.CreateSubKey(@"Software\ICC\DeviceInfo")) - { - key?.SetValue("DeviceId", deviceId); - key?.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - } - // LogHelper.WriteLogToFile("DeviceIdentifier | 设备ID已保存到注册表"); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到注册表失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 使用频率统计数据结构(优化至秒级精度) - /// - private class UsageStats - { - [JsonProperty("deviceId")] - public string DeviceId { get; set; } - - [JsonProperty("lastLaunchTime")] - public DateTime LastLaunchTime { get; set; } - - [JsonProperty("launchCount")] - public int LaunchCount { get; set; } - - // 新的秒级精度字段 - [JsonProperty("totalUsageSeconds")] - public long TotalUsageSeconds { get; set; } - - [JsonProperty("averageSessionSeconds")] - public double AverageSessionSeconds { get; set; } - - // 保留旧字段以保持向后兼容性(已弃用) - [JsonProperty("totalUsageMinutes")] - [Obsolete("已弃用,请使用 TotalUsageSeconds")] - public long TotalUsageMinutes { get; set; } - - [JsonProperty("averageSessionMinutes")] - [Obsolete("已弃用,请使用 AverageSessionSeconds")] - public double AverageSessionMinutes { get; set; } - - [JsonProperty("lastUpdateCheck")] - public DateTime LastUpdateCheck { get; set; } - - [JsonProperty("updatePriority")] - public UpdatePriority UpdatePriority { get; set; } - - [JsonProperty("usageFrequency")] - public UsageFrequency UsageFrequency { get; set; } - - [JsonProperty("dataHash")] - public string DataHash { get; set; } - - [JsonProperty("lastModified")] - public DateTime LastModified { get; set; } - - // 每周统计数据(秒级精度) - [JsonProperty("weeklyLaunchCount")] - public int WeeklyLaunchCount { get; set; } - - [JsonProperty("weeklyUsageSeconds")] - public long WeeklyUsageSeconds { get; set; } - - [JsonProperty("weekStartDate")] - public DateTime WeekStartDate { get; set; } - - [JsonProperty("lastWeekLaunchCount")] - public int LastWeekLaunchCount { get; set; } - - [JsonProperty("lastWeekUsageSeconds")] - public long LastWeekUsageSeconds { get; set; } - - // 保留旧字段以保持向后兼容性(已弃用) - [JsonProperty("weeklyUsageMinutes")] - [Obsolete("已弃用,请使用 WeeklyUsageSeconds")] - public long WeeklyUsageMinutes { get; set; } - - [JsonProperty("lastWeekUsageMinutes")] - [Obsolete("已弃用,请使用 LastWeekUsageSeconds")] - public long LastWeekUsageMinutes { get; set; } - - /// - /// 数据迁移:从分钟精度迁移到秒级精度 - /// - public void MigrateToSecondsPrecision() - { - try - { - // 如果新字段为空但旧字段有数据,进行迁移 - if (TotalUsageSeconds == 0 && TotalUsageMinutes > 0) - { - TotalUsageSeconds = TotalUsageMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移总使用时长: {TotalUsageMinutes}分钟 -> {TotalUsageSeconds}秒"); - } - - if (AverageSessionSeconds == 0 && AverageSessionMinutes > 0) - { - AverageSessionSeconds = AverageSessionMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移平均会话时长: {AverageSessionMinutes}分钟 -> {AverageSessionSeconds}秒"); - } - - if (WeeklyUsageSeconds == 0 && WeeklyUsageMinutes > 0) - { - WeeklyUsageSeconds = WeeklyUsageMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移每周使用时长: {WeeklyUsageMinutes}分钟 -> {WeeklyUsageSeconds}秒"); - } - - if (LastWeekUsageSeconds == 0 && LastWeekUsageMinutes > 0) - { - LastWeekUsageSeconds = LastWeekUsageMinutes * 60; - LogHelper.WriteLogToFile($"DeviceIdentifier | 迁移上周使用时长: {LastWeekUsageMinutes}分钟 -> {LastWeekUsageSeconds}秒"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 数据迁移失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 检查并重置每周统计数据(秒级精度) - /// - public void CheckAndResetWeeklyStats() - { - var now = DateTime.Now; - var currentWeekStart = GetWeekStartDate(now); - - // 如果是新的一周,重置统计 - if (WeekStartDate == DateTime.MinValue || currentWeekStart > WeekStartDate) - { - // 保存上周数据 - LastWeekLaunchCount = WeeklyLaunchCount; - LastWeekUsageSeconds = WeeklyUsageSeconds; - - // 同时更新旧字段以保持兼容性 - LastWeekUsageMinutes = LastWeekUsageSeconds / 60; - - // 重置本周数据 - WeeklyLaunchCount = 0; - WeeklyUsageSeconds = 0; - WeeklyUsageMinutes = 0; - WeekStartDate = currentWeekStart; - - LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {FormatDuration(LastWeekUsageSeconds)}"); - } - } - - /// - /// 获取指定日期所在周的开始日期(周一) - /// - public DateTime GetWeekStartDate(DateTime date) - { - var dayOfWeek = (int)date.DayOfWeek; - var daysToSubtract = dayOfWeek == 0 ? 6 : dayOfWeek - 1; // 周日=0,需要减6天到周一 - return date.Date.AddDays(-daysToSubtract); - } - - /// - /// 记录本周的启动 - /// - public void RecordWeeklyLaunch() - { - CheckAndResetWeeklyStats(); - WeeklyLaunchCount++; - } - - /// - /// 记录本周的使用时长(秒级精度) - /// - public void RecordWeeklyUsage(long seconds) - { - CheckAndResetWeeklyStats(); - WeeklyUsageSeconds += seconds; - // 同时更新旧字段以保持兼容性 - WeeklyUsageMinutes = WeeklyUsageSeconds / 60; - } - - - - /// - /// 计算数据哈希值用于完整性验证(秒级精度) - /// - public void UpdateDataHash() - { - // 使用秒级精度数据计算哈希 - var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}"; - using (var sha256 = SHA256.Create()) - { - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); - DataHash = Convert.ToBase64String(hashBytes); - } - LastModified = DateTime.Now; - } - - /// - /// 验证数据完整性(秒级精度) - /// - public bool VerifyDataIntegrity() - { - try - { - // 首先尝试使用秒级精度验证 - var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageSeconds}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageSeconds}|{DataIntegrityKey}"; - using (var sha256 = SHA256.Create()) - { - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); - var expectedHash = Convert.ToBase64String(hashBytes); - if (DataHash == expectedHash) - { - return true; - } - } - - // 如果秒级精度验证失败,尝试使用旧的分钟精度验证(向后兼容) - var oldDataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; - using (var sha256 = SHA256.Create()) - { - var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(oldDataString)); - var expectedHash = Convert.ToBase64String(hashBytes); - return DataHash == expectedHash; - } - } - catch - { - return false; - } - } - } - - /// - /// 格式化时长显示(秒级精度) - /// - /// 总秒数 - /// 格式化的时长字符串 - public static string FormatDuration(long totalSeconds) - { - if (totalSeconds < 60) - { - return $"{totalSeconds}秒"; - } - - if (totalSeconds < 3600) - { - var minutes = totalSeconds / 60; - var seconds = totalSeconds % 60; - return seconds > 0 ? $"{minutes}分{seconds}秒" : $"{minutes}分钟"; - } - else - { - var hours = totalSeconds / 3600; - var minutes = (totalSeconds % 3600) / 60; - var seconds = totalSeconds % 60; - - var result = $"{hours}小时"; - if (minutes > 0) result += $"{minutes}分"; - if (seconds > 0) result += $"{seconds}秒"; - - return result; - } - } - - /// - /// 更新推送优先级枚举 - /// - public enum UpdatePriority - { - High = 1, // 高优先级:立即推送更新 - Medium = 2, // 中优先级:延迟1-3天推送 - Low = 3 // 低优先级:延迟3-14天推送 - } - - /// - /// 用户使用频率分类枚举 - /// - public enum UsageFrequency - { - High = 1, // 高频用户:综合评分≥80分(活跃度高、使用时长长、启动频繁) - Medium = 2, // 中频用户:综合评分40-79分(中等活跃度和使用强度) - Low = 3 // 低频用户:综合评分<40分(活跃度低、使用时长短、启动较少) - } - - /// - /// 记录应用启动 - /// - public static void RecordAppLaunch() - { - try - { - lock (fileLock) - { - var stats = LoadUsageStats(); - stats.LastLaunchTime = DateTime.Now; - stats.LaunchCount++; - stats.DeviceId = DeviceId; - - // 记录每周启动次数 - stats.RecordWeeklyLaunch(); - - // 计算使用频率 - CalculateUsageFrequency(stats); - - // 更新数据完整性哈希 - stats.UpdateDataHash(); - - SaveUsageStats(stats); - - LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用启动 - 设备ID: {DeviceId}, 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用启动失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 记录应用退出(计算使用时长 - 秒级精度) - /// - public static void RecordAppExit() - { - try - { - lock (fileLock) - { - var stats = LoadUsageStats(); - - // 执行数据迁移(如果需要) - stats.MigrateToSecondsPrecision(); - - // 计算本次会话时长(秒级精度) - long sessionSeconds = 0; - if (stats.LastLaunchTime != DateTime.MinValue) - { - var sessionDuration = DateTime.Now - stats.LastLaunchTime; - sessionSeconds = (long)sessionDuration.TotalSeconds; - - // 更新秒级精度数据 - stats.TotalUsageSeconds += sessionSeconds; - - // 同时更新旧字段以保持兼容性 - stats.TotalUsageMinutes = stats.TotalUsageSeconds / 60; - - // 记录每周使用时长(秒级精度) - stats.RecordWeeklyUsage(sessionSeconds); - - // 更新平均会话时长(秒级精度) - if (stats.LaunchCount > 0) - { - stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; - // 同时更新旧字段以保持兼容性 - stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; - } - } - - // 重新计算使用频率 - CalculateUsageFrequency(stats); - - // 更新数据完整性哈希 - stats.UpdateDataHash(); - - SaveUsageStats(stats); - - LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {FormatDuration(sessionSeconds)}, " + - $"总时长: {FormatDuration(stats.TotalUsageSeconds)}, " + - $"本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 计算使用频率和更新优先级(基于真实的每周统计数据) - /// 通过多维度评分系统确定用户类型:高频(≥80分)、中频(40-79分)、低频(<40分) - /// - private static void CalculateUsageFrequency(UsageStats stats) - { - try - { - // 确保每周统计数据是最新的 - stats.CheckAndResetWeeklyStats(); - - // 计算最近活跃度 - var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; - - // 使用真实的每周数据(秒级精度) - var currentWeekLaunches = stats.WeeklyLaunchCount; - var currentWeekSeconds = stats.WeeklyUsageSeconds; - - // 如果秒级数据为空但分钟数据存在,进行转换 - if (currentWeekSeconds == 0 && stats.WeeklyUsageMinutes > 0) - { - currentWeekSeconds = stats.WeeklyUsageMinutes * 60; - } - - // 如果本周数据不足,参考上周数据 - var weeklyLaunches = currentWeekLaunches > 0 ? currentWeekLaunches : stats.LastWeekLaunchCount; - var weeklySeconds = currentWeekSeconds > 0 ? currentWeekSeconds : stats.LastWeekUsageSeconds; - - // 如果秒级数据仍为空,使用分钟数据转换 - if (weeklySeconds == 0 && stats.LastWeekUsageMinutes > 0) - { - weeklySeconds = stats.LastWeekUsageMinutes * 60; - } - - // 综合评分系统(0-100分) - var frequencyScore = CalculateFrequencyScoreWithWeeklyData(stats, daysSinceLastUse, weeklyLaunches, weeklySeconds); - - // 根据综合评分确定频率分类和更新优先级 - if (frequencyScore >= 80) - { - stats.UsageFrequency = UsageFrequency.High; // 高频用户:立即推送更新 - stats.UpdatePriority = UpdatePriority.High; - } - else if (frequencyScore >= 40) - { - stats.UsageFrequency = UsageFrequency.Medium; // 中频用户:延迟1-3天推送 - stats.UpdatePriority = UpdatePriority.Medium; - } - else - { - stats.UsageFrequency = UsageFrequency.Low; // 低频用户:延迟3-14天推送 - stats.UpdatePriority = UpdatePriority.Low; - } - - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率计算 - 评分: {frequencyScore}, 频率: {stats.UsageFrequency}, " + - $"优先级: {stats.UpdatePriority}, 本周启动: {currentWeekLaunches}次, 本周时长: {FormatDuration(currentWeekSeconds)}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 计算使用频率失败: {ex.Message}", LogHelper.LogType.Error); - // 默认设置为中等频率和优先级 - stats.UsageFrequency = UsageFrequency.Medium; - stats.UpdatePriority = UpdatePriority.Medium; - } - } - - /// - /// 基于每周真实数据计算综合频率评分(0-100分,秒级精度) - /// 评分标准:≥80分=高频用户,40-79分=中频用户,<40分=低频用户 - /// - /// 使用统计数据 - /// 距离最后使用的天数 - /// 每周启动次数 - /// 每周使用时长(秒) - /// 综合评分(0-100分) - private static int CalculateFrequencyScoreWithWeeklyData(UsageStats stats, double daysSinceLastUse, - long weeklyLaunches, long weeklySeconds) - { - var score = 0; - - // 最近活跃度评分(40分)- 反映用户当前的活跃程度 - if (daysSinceLastUse <= 1) score += 40; // 1天内使用:非常活跃 - else if (daysSinceLastUse <= 3) score += 35; // 3天内使用:很活跃 - else if (daysSinceLastUse <= 7) score += 25; // 1周内使用:较活跃 - else if (daysSinceLastUse <= 14) score += 15; // 2周内使用:一般活跃 - else if (daysSinceLastUse <= 30) score += 5; // 1月内使用:不太活跃 - - // 每周使用频率评分(30分)- 基于真实的每周启动次数 - if (weeklyLaunches >= 10) score += 30; // 10次以上:高频使用 - else if (weeklyLaunches >= 5) score += 20; // 5-9次:中高频使用 - else if (weeklyLaunches >= 3) score += 15; // 3-4次:中频使用 - else if (weeklyLaunches >= 1) score += 10; // 1-2次:低频使用 - - // 每周使用时长评分(20分)- 基于真实的每周使用时长(秒级精度) - if (weeklySeconds >= 36000) score += 20; // 10小时以上:重度使用 - else if (weeklySeconds >= 18000) score += 15; // 5-10小时:中重度使用 - else if (weeklySeconds >= 7200) score += 10; // 2-5小时:中度使用 - else if (weeklySeconds >= 3600) score += 5; // 1-2小时:轻度使用 - - // 历史使用深度评分(10分)- 反映用户的长期使用习惯(秒级精度) - var totalSeconds = stats.TotalUsageSeconds > 0 ? stats.TotalUsageSeconds : stats.TotalUsageMinutes * 60; - if (totalSeconds >= 180000) score += 10; // 50小时以上:资深用户 - else if (totalSeconds >= 72000) score += 7; // 20-50小时:中等用户 - else if (totalSeconds >= 18000) score += 4; // 5-20小时:新手用户 - - return Math.Min(100, score); - } - - - - /// - /// 获取当前更新优先级 - /// - public static UpdatePriority GetUpdatePriority() - { - try - { - var stats = LoadUsageStats(); - return stats.UpdatePriority; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 获取更新优先级失败: {ex.Message}", LogHelper.LogType.Error); - return UpdatePriority.Medium; // 默认中等优先级 - } - } - - /// - /// 获取使用频率 - /// - public static UsageFrequency GetUsageFrequency() - { - try - { - var stats = LoadUsageStats(); - return stats.UsageFrequency; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用频率失败: {ex.Message}", LogHelper.LogType.Error); - return UsageFrequency.Medium; // 默认中等频率 - } - } - - /// - /// 获取使用统计信息(秒级精度) - /// - public static (int launchCount, long totalSeconds, double avgSessionSeconds, UpdatePriority priority) GetUsageStats() - { - try - { - var stats = LoadUsageStats(); - return (stats.LaunchCount, stats.TotalUsageSeconds, stats.AverageSessionSeconds, stats.UpdatePriority); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用统计失败: {ex.Message}", LogHelper.LogType.Error); - return (0, 0, 0, UpdatePriority.Medium); - } - } - - /// - /// 获取使用统计信息(兼容性方法 - 分钟精度) - /// - [Obsolete("请使用 GetUsageStats() 获取秒级精度数据")] - public static (int launchCount, long totalMinutes, double avgSessionMinutes, UpdatePriority priority) GetUsageStatsInMinutes() - { - try - { - var stats = LoadUsageStats(); - var totalMinutes = stats.TotalUsageSeconds / 60; - var avgMinutes = stats.AverageSessionSeconds / 60; - return (stats.LaunchCount, totalMinutes, avgMinutes, stats.UpdatePriority); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用统计失败: {ex.Message}", LogHelper.LogType.Error); - return (0, 0, 0, UpdatePriority.Medium); - } - } - - /// - /// 加载使用统计 - 支持多重备份恢复和智能反篡改 - /// - private static UsageStats LoadUsageStats() - { - try - { - // 智能恢复:收集所有可用的数据源,选择最可信的 - var allDataSources = CollectAllUsageDataSources(); - - // 如果找到有效数据,返回最可信的 - if (allDataSources.Count > 0) - { - var bestData = SelectMostTrustedData(allDataSources); - if (bestData != null) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用最可信数据源恢复使用统计: {bestData.Source}"); - - // 执行数据迁移(如果需要) - bestData.Stats.MigrateToSecondsPrecision(); - - // 确保备份同步 - SaveUsageStatsToAllLocations(bestData.Stats); - return bestData.Stats; - } - } - - // LogHelper.WriteLogToFile("DeviceIdentifier | 所有数据源都不可用,检查是否有部分可恢复数据", LogHelper.LogType.Warning); - - // 如果没有完全可信的数据,尝试从部分损坏的数据中恢复 - var partiallyRecoveredData = AttemptPartialDataRecovery(allDataSources); - if (partiallyRecoveredData != null) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 从部分损坏数据中恢复使用统计"); - - // 执行数据迁移(如果需要) - partiallyRecoveredData.MigrateToSecondsPrecision(); - - SaveUsageStatsToAllLocations(partiallyRecoveredData); - return partiallyRecoveredData; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error); - } - - // 返回新的统计对象(秒级精度) - var newStats = new UsageStats - { - DeviceId = DeviceId, - LastLaunchTime = DateTime.Now, - LaunchCount = 0, - TotalUsageSeconds = 0, - AverageSessionSeconds = 0, - TotalUsageMinutes = 0, // 保持兼容性 - AverageSessionMinutes = 0, // 保持兼容性 - LastUpdateCheck = DateTime.MinValue, - UpdatePriority = UpdatePriority.Medium, - UsageFrequency = UsageFrequency.Medium - }; - - // 更新数据完整性哈希 - newStats.UpdateDataHash(); - - // 保存新统计到所有位置 - SaveUsageStatsToAllLocations(newStats); - return newStats; - } - - /// - /// 保存使用统计 - 多重备份 - /// - private static void SaveUsageStats(UsageStats stats) - { - SaveUsageStatsToAllLocations(stats); - } - - /// - /// 数据源信息结构 - /// - private class DataSourceInfo - { - public UsageStats Stats { get; set; } - public string Source { get; set; } - public bool IsIntegrityValid { get; set; } - public DateTime LastModified { get; set; } - public int TrustScore { get; set; } - } - - /// - /// 从文件加载使用统计(带完整性验证,但不丢弃篡改数据) - /// - private static UsageStats LoadUsageStatsFromFile(string filePath) - { - try - { - if (File.Exists(filePath)) - { - string json = File.ReadAllText(filePath); - var stats = JsonConvert.DeserializeObject(json); - if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) - { - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - if (stats.VerifyDataIntegrity()) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证通过: {filePath}"); - return stats; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证失败,可能被篡改: {filePath}", LogHelper.LogType.Warning); - return null; // 数据被篡改,不使用 - } - - // 旧版本数据,没有哈希值,更新哈希后返回 - // LogHelper.WriteLogToFile($"DeviceIdentifier | 检测到旧版本数据,正在更新完整性哈希: {filePath}"); - stats.UpdateDataHash(); - return stats; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载使用统计失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 从文件加载使用统计(包括被篡改的数据,用于恢复分析) - /// - private static DataSourceInfo LoadUsageStatsFromFileWithInfo(string filePath, string sourceName) - { - try - { - if (File.Exists(filePath)) - { - string json = File.ReadAllText(filePath); - var stats = JsonConvert.DeserializeObject(json); - if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) - { - var fileInfo = new FileInfo(filePath); - var dataSource = new DataSourceInfo - { - Stats = stats, - Source = sourceName, - LastModified = stats.LastModified != DateTime.MinValue ? stats.LastModified : fileInfo.LastWriteTime, - IsIntegrityValid = false, - TrustScore = 0 - }; - - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); - dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 20; // 完整数据100分,篡改数据20分 - } - else - { - // 旧版本数据,中等信任度 - dataSource.IsIntegrityValid = true; - dataSource.TrustScore = 60; - stats.UpdateDataHash(); - } - - return dataSource; - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载数据源信息失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 收集所有可用的使用统计数据源 - /// - private static List CollectAllUsageDataSources() - { - var dataSources = new List(); - - try - { - // 1. 收集文件数据源 - var fileSources = new[] - { - new { Path = UsageStatsFilePath, Name = "主文件" }, - new { Path = BackupUsageStatsPath, Name = "第一备份" }, - new { Path = SecondaryUsageBackupPath, Name = "第二备份" }, - new { Path = TertiaryUsageBackupPath, Name = "第三备份" }, - new { Path = QuaternaryUsageBackupPath, Name = "第四备份" } - }; - - foreach (var source in fileSources) - { - var dataSource = LoadUsageStatsFromFileWithInfo(source.Path, source.Name); - if (dataSource != null) - { - dataSources.Add(dataSource); - } - } - - // 2. 收集注册表数据源 - var registrySource = LoadUsageStatsFromRegistryWithInfo(@"Software\ICC\DeviceInfo", "主注册表"); - if (registrySource != null) - { - dataSources.Add(registrySource); - } - - // 3. 收集备用注册表数据源 - var backupRegistryPaths = new[] - { - new { Path = @"Software\Microsoft\Windows\CurrentVersion\ICC", Name = "备用注册表1" }, - new { Path = @"Software\Classes\.icc\UsageData", Name = "备用注册表2" }, - new { Path = @"Software\ICC\Config\Usage", Name = "备用注册表3" } - }; - - foreach (var regPath in backupRegistryPaths) - { - var regSource = LoadUsageStatsFromBackupRegistryWithInfo(regPath.Path, regPath.Name); - if (regSource != null) - { - dataSources.Add(regSource); - } - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 收集到 {dataSources.Count} 个数据源"); - return dataSources; - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 收集数据源失败: {ex.Message}", LogHelper.LogType.Error); - return dataSources; - } - } - - /// - /// 选择最可信的数据源 - /// - private static DataSourceInfo SelectMostTrustedData(List dataSources) - { - try - { - // 首先尝试找到完整性验证通过的数据 - var validSources = dataSources.Where(d => d.IsIntegrityValid).ToList(); - - if (validSources.Count > 0) - { - // 在有效数据中选择最新的 - var bestValid = validSources.OrderByDescending(d => d.LastModified).First(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 选择完整性验证通过的最新数据: {bestValid.Source}"); - return bestValid; - } - - // 如果没有完整性验证通过的数据,选择信任度最高的 - var bestByTrust = dataSources.OrderByDescending(d => d.TrustScore).ThenByDescending(d => d.LastModified).FirstOrDefault(); - if (bestByTrust != null) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 选择信任度最高的数据: {bestByTrust.Source} (信任度: {bestByTrust.TrustScore})", LogHelper.LogType.Warning); - return bestByTrust; - } - - return null; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 选择最可信数据失败: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 尝试从部分损坏的数据中恢复 - /// - private static UsageStats AttemptPartialDataRecovery(List dataSources) - { - try - { - if (dataSources.Count == 0) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 没有可用数据源进行部分恢复"); - return null; - } - - // 从所有数据源中提取可信的字段 - var recoveredStats = new UsageStats - { - DeviceId = DeviceId, - LastLaunchTime = DateTime.Now, - LaunchCount = 0, - TotalUsageMinutes = 0, - AverageSessionMinutes = 0, - LastUpdateCheck = DateTime.MinValue, - UpdatePriority = UpdatePriority.Medium, - UsageFrequency = UsageFrequency.Medium - }; - - // 使用多数投票或最大值策略恢复关键数据(秒级精度) - var launchCounts = dataSources.Where(d => d.Stats.LaunchCount > 0).Select(d => d.Stats.LaunchCount).ToList(); - var usageSeconds = dataSources.Where(d => d.Stats.TotalUsageSeconds > 0).Select(d => d.Stats.TotalUsageSeconds).ToList(); - var usageMinutes = dataSources.Where(d => d.Stats.TotalUsageMinutes > 0).Select(d => d.Stats.TotalUsageMinutes).ToList(); - - if (launchCounts.Count > 0) - { - recoveredStats.LaunchCount = (int)launchCounts.Average(); // 使用平均值 - } - - // 优先使用秒级数据,如果没有则使用分钟数据转换 - if (usageSeconds.Count > 0) - { - recoveredStats.TotalUsageSeconds = (long)usageSeconds.Average(); // 使用平均值 - recoveredStats.TotalUsageMinutes = recoveredStats.TotalUsageSeconds / 60; // 兼容性 - } - else if (usageMinutes.Count > 0) - { - recoveredStats.TotalUsageMinutes = (long)usageMinutes.Average(); // 使用平均值 - recoveredStats.TotalUsageSeconds = recoveredStats.TotalUsageMinutes * 60; // 转换为秒 - } - - // 重新计算平均会话时长(秒级精度) - if (recoveredStats.LaunchCount > 0) - { - recoveredStats.AverageSessionSeconds = (double)recoveredStats.TotalUsageSeconds / recoveredStats.LaunchCount; - recoveredStats.AverageSessionMinutes = recoveredStats.AverageSessionSeconds / 60; // 兼容性 - } - - // 重新计算使用频率 - CalculateUsageFrequency(recoveredStats); - - // 更新数据完整性哈希 - recoveredStats.UpdateDataHash(); - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {FormatDuration(recoveredStats.TotalUsageSeconds)}"); - return recoveredStats; - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复失败: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 从注册表加载使用统计(带数据源信息) - /// - private static DataSourceInfo LoadUsageStatsFromRegistryWithInfo(string registryPath, string sourceName) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(registryPath)) - { - if (key != null) - { - var deviceId = key.GetValue("DeviceId") as string; - var launchCount = key.GetValue("LaunchCount"); - - // 秒级精度数据 - var totalSeconds = key.GetValue("TotalUsageSeconds"); - var avgSessionSeconds = key.GetValue("AverageSessionSeconds"); - - // 兼容性:分钟精度数据 - var totalMinutes = key.GetValue("TotalUsageMinutes"); - var avgSessionMinutes = key.GetValue("AverageSessionMinutes"); - - var lastLaunch = key.GetValue("LastLaunchTime") as string; - var priority = key.GetValue("UpdatePriority"); - var frequency = key.GetValue("UsageFrequency"); - var dataHash = key.GetValue("DataHash") as string; - var lastUpdate = key.GetValue("LastUpdate") as string; - - // 每周统计数据(秒级精度) - var weeklyLaunchCount = key.GetValue("WeeklyLaunchCount"); - var weeklyUsageSeconds = key.GetValue("WeeklyUsageSeconds"); - var lastWeekUsageSeconds = key.GetValue("LastWeekUsageSeconds"); - - // 兼容性:分钟精度数据 - var weeklyUsageMinutes = key.GetValue("WeeklyUsageMinutes"); - var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes"); - - var weekStartDate = key.GetValue("WeekStartDate") as string; - var lastWeekLaunchCount = key.GetValue("LastWeekLaunchCount"); - - if (!string.IsNullOrEmpty(deviceId) && launchCount != null) - { - var stats = new UsageStats - { - DeviceId = deviceId, - LaunchCount = Convert.ToInt32(launchCount), - - // 秒级精度数据 - TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0, - AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0, - - // 兼容性:分钟精度数据 - TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, - AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0, - - LastLaunchTime = DateTime.TryParse(lastLaunch, out var dt) ? dt : DateTime.Now, - UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, - UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, - DataHash = dataHash, - LastUpdateCheck = DateTime.MinValue, - - // 每周统计数据(秒级精度) - WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, - WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0, - LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0, - - // 兼容性:分钟精度数据 - WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, - LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0, - - WeekStartDate = DateTime.TryParse(weekStartDate, out var wsd) ? wsd : DateTime.MinValue, - LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0 - }; - - // 执行数据迁移(如果需要) - stats.MigrateToSecondsPrecision(); - - // 重新计算平均会话时长 - if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0) - { - stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; - stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; - } - - var dataSource = new DataSourceInfo - { - Stats = stats, - Source = sourceName, - LastModified = DateTime.TryParse(lastUpdate, out var updateTime) ? updateTime : DateTime.Now, - IsIntegrityValid = false, - TrustScore = 80 // 注册表数据信任度较高 - }; - - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); - dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 30; - } - - return dataSource; - } - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表加载数据源信息失败 ({registryPath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 从备用注册表位置加载使用统计(带数据源信息) - /// - private static DataSourceInfo LoadUsageStatsFromBackupRegistryWithInfo(string registryPath, string sourceName) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(registryPath)) - { - if (key != null) - { - var launchCount = key.GetValue("LC"); - - // 秒级精度数据 - var totalSeconds = key.GetValue("TUS"); - var avgSessionSeconds = key.GetValue("ASS"); - - // 兼容性:分钟精度数据 - var totalMinutes = key.GetValue("TUM"); - var avgSessionMinutes = key.GetValue("ASM"); - - var lastLaunchBinary = key.GetValue("LLT"); - var priority = key.GetValue("UP"); - var frequency = key.GetValue("UF"); - var dataHash = key.GetValue("DH") as string; - var lastUpdateBinary = key.GetValue("LU"); - - // 每周统计数据(秒级精度) - var weeklyLaunchCount = key.GetValue("WLC"); - var weeklyUsageSeconds = key.GetValue("WUS"); - var lastWeekUsageSeconds = key.GetValue("LWUS"); - - // 兼容性:分钟精度数据 - var weeklyUsageMinutes = key.GetValue("WUM"); - var lastWeekUsageMinutes = key.GetValue("LWUM"); - - var weekStartDateBinary = key.GetValue("WSD"); - var lastWeekLaunchCount = key.GetValue("LWLC"); - - if (launchCount != null && (totalSeconds != null || totalMinutes != null)) - { - var stats = new UsageStats - { - DeviceId = DeviceId, - LaunchCount = Convert.ToInt32(launchCount), - - // 秒级精度数据 - TotalUsageSeconds = totalSeconds != null ? Convert.ToInt64(totalSeconds) : 0, - AverageSessionSeconds = avgSessionSeconds != null ? Convert.ToDouble(avgSessionSeconds) : 0, - - // 兼容性:分钟精度数据 - TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, - AverageSessionMinutes = avgSessionMinutes != null ? Convert.ToDouble(avgSessionMinutes) : 0, - - LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, - UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, - UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, - DataHash = dataHash, - LastUpdateCheck = DateTime.MinValue, - - // 每周统计数据(秒级精度) - WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, - WeeklyUsageSeconds = weeklyUsageSeconds != null ? Convert.ToInt64(weeklyUsageSeconds) : 0, - LastWeekUsageSeconds = lastWeekUsageSeconds != null ? Convert.ToInt64(lastWeekUsageSeconds) : 0, - - // 兼容性:分钟精度数据 - WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, - LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0, - - WeekStartDate = weekStartDateBinary != null ? DateTime.FromBinary(Convert.ToInt64(weekStartDateBinary)) : DateTime.MinValue, - LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0 - }; - - // 执行数据迁移(如果需要) - stats.MigrateToSecondsPrecision(); - - // 重新计算平均会话时长 - if (stats.LaunchCount > 0 && stats.AverageSessionSeconds == 0) - { - stats.AverageSessionSeconds = (double)stats.TotalUsageSeconds / stats.LaunchCount; - stats.AverageSessionMinutes = stats.AverageSessionSeconds / 60; - } - - var dataSource = new DataSourceInfo - { - Stats = stats, - Source = sourceName, - LastModified = lastUpdateBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastUpdateBinary)) : DateTime.Now, - IsIntegrityValid = false, - TrustScore = 75 // 备用注册表数据信任度中等 - }; - - // 验证数据完整性 - if (!string.IsNullOrEmpty(stats.DataHash)) - { - dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); - dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 25; - } - - return dataSource; - } - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从备用注册表加载数据源信息失败 ({registryPath}): {ex.Message}", LogHelper.LogType.Error); - } - return null; - } - - /// - /// 从注册表加载使用统计(保持向后兼容) - /// - private static UsageStats LoadUsageStatsFromRegistry() - { - var dataSource = LoadUsageStatsFromRegistryWithInfo(@"Software\ICC\DeviceInfo", "主注册表"); - return dataSource?.Stats; - } - - /// - /// 从多个注册表位置加载使用统计(强化恢复) - /// - private static UsageStats LoadUsageStatsFromMultipleRegistryLocations() - { - var registryPaths = new[] - { - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) - { - var launchCount = key.GetValue("LC"); - var totalMinutes = key.GetValue("TUM"); - var lastLaunchBinary = key.GetValue("LLT"); - var priority = key.GetValue("UP"); - var frequency = key.GetValue("UF"); - var dataHash = key.GetValue("DH") as string; - - if (launchCount != null && totalMinutes != null) - { - var stats = new UsageStats - { - DeviceId = DeviceId, - LaunchCount = Convert.ToInt32(launchCount), - TotalUsageMinutes = Convert.ToInt64(totalMinutes), - LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, - UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, - UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, - DataHash = dataHash, - AverageSessionMinutes = 0, - LastUpdateCheck = DateTime.MinValue - }; - - // 重新计算平均会话时长 - if (stats.LaunchCount > 0) - { - stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; - } - - // 验证数据完整性(如果有哈希值) - if (!string.IsNullOrEmpty(stats.DataHash)) - { - if (stats.VerifyDataIntegrity()) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置恢复数据并验证完整性通过: {path}"); - return stats; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 注册表位置数据完整性验证失败: {path}", LogHelper.LogType.Warning); - } - else - { - // 没有哈希值的旧数据,更新哈希后返回 - stats.UpdateDataHash(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置恢复旧版本数据: {path}"); - return stats; - } - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置加载失败 ({path}): {ex.Message}", LogHelper.LogType.Error); - } - } - - return null; - } - - /// - /// 保存使用统计到所有位置(强化版本 - 多重隐藏备份) - /// - private static void SaveUsageStatsToAllLocations(UsageStats stats) - { - // 保存到主文件 - SaveUsageStatsToFile(UsageStatsFilePath, stats); - - // 保存到第一备份文件 - SaveUsageStatsToFile(BackupUsageStatsPath, stats); - - // 保存到多个隐藏备份位置(专门针对使用频率数据保护) - SaveUsageStatsToFile(SecondaryUsageBackupPath, stats); - SaveUsageStatsToFile(TertiaryUsageBackupPath, stats); - SaveUsageStatsToFile(QuaternaryUsageBackupPath, stats); - - // 保存到注册表 - SaveUsageStatsToRegistry(stats); - - // 保存到注册表的多个位置 - SaveUsageStatsToMultipleRegistryLocations(stats); - } - - /// - /// 保存使用统计到文件 - /// - private static void SaveUsageStatsToFile(string filePath, UsageStats stats) - { - try - { - // 确保目录存在 - var directory = Path.GetDirectoryName(filePath); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - // 设置隐藏属性 - if (filePath.Contains(".sys")) - { - var dirInfo = new DirectoryInfo(directory); - dirInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } - } - - string json = JsonConvert.SerializeObject(stats, Formatting.Indented); - File.WriteAllText(filePath, json); - - // 设置文件属性为隐藏和系统文件 - if (filePath.Contains(".sys")) - { - var fileInfo = new FileInfo(filePath); - fileInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到: {filePath}"); - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 保存使用统计到注册表 - /// - private static void SaveUsageStatsToRegistry(UsageStats stats) - { - try - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始保存使用统计到主注册表位置"); - - using (var key = Registry.CurrentUser.CreateSubKey(@"Software\ICC\DeviceInfo")) - { - if (key != null) - { - key.SetValue("DeviceId", stats.DeviceId); - key.SetValue("LaunchCount", stats.LaunchCount); - - // 秒级精度数据 - key.SetValue("TotalUsageSeconds", stats.TotalUsageSeconds); - key.SetValue("AverageSessionSeconds", stats.AverageSessionSeconds); - - // 兼容性:分钟精度数据 - key.SetValue("TotalUsageMinutes", stats.TotalUsageMinutes); - key.SetValue("AverageSessionMinutes", stats.AverageSessionMinutes); - - key.SetValue("LastLaunchTime", stats.LastLaunchTime.ToString("yyyy-MM-dd HH:mm:ss")); - key.SetValue("UpdatePriority", (int)stats.UpdatePriority); - key.SetValue("UsageFrequency", (int)stats.UsageFrequency); - key.SetValue("DataHash", stats.DataHash ?? ""); - key.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - - // 每周统计数据(秒级精度) - key.SetValue("WeeklyLaunchCount", stats.WeeklyLaunchCount); - key.SetValue("WeeklyUsageSeconds", stats.WeeklyUsageSeconds); - key.SetValue("LastWeekUsageSeconds", stats.LastWeekUsageSeconds); - - // 兼容性:分钟精度数据 - key.SetValue("WeeklyUsageMinutes", stats.WeeklyUsageMinutes); - key.SetValue("LastWeekUsageMinutes", stats.LastWeekUsageMinutes); - - key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd")); - key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount); - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, " + - // $"总时长: {FormatDuration(stats.TotalUsageSeconds)}, 本周时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); - } - else - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 创建主注册表键失败", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到主注册表失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 保存使用统计到多个注册表位置(强化保护) - /// - private static void SaveUsageStatsToMultipleRegistryLocations(UsageStats stats) - { - var registryPaths = new[] - { - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 开始保存使用统计到{registryPaths.Length}个备用注册表位置"); - var successCount = 0; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.CreateSubKey(path)) - { - if (key != null) - { - // 使用编码的键名来隐藏数据(秒级精度) - key.SetValue("LC", stats.LaunchCount); // LaunchCount - - // 秒级精度数据 - key.SetValue("TUS", stats.TotalUsageSeconds); // TotalUsageSeconds - key.SetValue("ASS", stats.AverageSessionSeconds); // AverageSessionSeconds - - // 兼容性:分钟精度数据 - key.SetValue("TUM", stats.TotalUsageMinutes); // TotalUsageMinutes - key.SetValue("ASM", stats.AverageSessionMinutes); // AverageSessionMinutes - - key.SetValue("LLT", stats.LastLaunchTime.ToBinary()); // LastLaunchTime - key.SetValue("UP", (int)stats.UpdatePriority); // UpdatePriority - key.SetValue("UF", (int)stats.UsageFrequency); // UsageFrequency - key.SetValue("DH", stats.DataHash ?? ""); // DataHash - key.SetValue("LU", DateTime.Now.ToBinary()); // LastUpdate - - // 每周统计数据(秒级精度) - key.SetValue("WLC", stats.WeeklyLaunchCount); // WeeklyLaunchCount - key.SetValue("WUS", stats.WeeklyUsageSeconds); // WeeklyUsageSeconds - key.SetValue("LWUS", stats.LastWeekUsageSeconds); // LastWeekUsageSeconds - - // 兼容性:分钟精度数据 - key.SetValue("WUM", stats.WeeklyUsageMinutes); // WeeklyUsageMinutes - key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes - - key.SetValue("WSD", stats.WeekStartDate.ToBinary()); // WeekStartDate - key.SetValue("LWLC", stats.LastWeekLaunchCount); // LastWeekLaunchCount - - successCount++; - // LogHelper.WriteLogToFile($"DeviceIdentifier | 成功保存到备用注册表位置: {path}"); - } - else - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 创建备用注册表键失败: {path}", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 保存到备用注册表位置失败 ({path}): {ex.Message}", LogHelper.LogType.Error); - } - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 备用注册表保存完成: {successCount}/{registryPaths.Length} 成功"); - } - - /// - /// 记录更新检查时间(同时执行数据保护检查) - /// - public static void RecordUpdateCheck() - { - try - { - lock (fileLock) - { - var stats = LoadUsageStats(); - stats.LastUpdateCheck = DateTime.Now; - stats.UpdateDataHash(); - SaveUsageStats(stats); - - // 定期执行数据保护检查(每10次更新检查执行一次) - if (stats.LaunchCount % 10 == 0) - { - PerformUsageDataProtectionCheck(); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 记录更新检查失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 执行使用频率数据保护检查和自动修复 - /// - public static bool PerformUsageDataProtectionCheck() - { - try - { - lock (fileLock) - { - LogHelper.WriteLogToFile("DeviceIdentifier | 开始使用频率数据保护检查"); - - var issues = new List(); - var repaired = new List(); - var backupPaths = new[] - { - new { Path = UsageStatsFilePath, Name = "主文件" }, - new { Path = BackupUsageStatsPath, Name = "第一备份" }, - new { Path = SecondaryUsageBackupPath, Name = "第二备份" }, - new { Path = TertiaryUsageBackupPath, Name = "第三备份" }, - new { Path = QuaternaryUsageBackupPath, Name = "第四备份" } - }; - - // 检查所有备份文件 - UsageStats validStats = null; - var missingFiles = new List(); - - foreach (var backup in backupPaths) - { - if (!File.Exists(backup.Path)) - { - issues.Add($"{backup.Name}丢失"); - missingFiles.Add(backup.Path); - } - else - { - var stats = LoadUsageStatsFromFile(backup.Path); - if (stats != null && stats.VerifyDataIntegrity() && validStats == null) - { - validStats = stats; - } - } - } - - // 如果找到有效数据,修复丢失的文件 - if (validStats != null && missingFiles.Count > 0) - { - foreach (var missingFile in missingFiles) - { - SaveUsageStatsToFile(missingFile, validStats); - repaired.Add($"恢复文件: {Path.GetFileName(missingFile)}"); - } - } - - // 检查注册表备份 - var registryPaths = new[] - { - @"Software\ICC\DeviceInfo", - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - var missingRegistryBackups = 0; - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key == null) missingRegistryBackups++; - } - } - catch - { - missingRegistryBackups++; - } - } - - if (missingRegistryBackups > 0) - { - issues.Add($"{missingRegistryBackups}个注册表备份丢失"); - if (validStats != null) - { - SaveUsageStatsToMultipleRegistryLocations(validStats); - repaired.Add("重建注册表备份"); - } - } - - // 如果没有找到任何有效数据,尝试从注册表恢复 - if (validStats == null) - { - validStats = LoadUsageStatsFromRegistry(); - if (validStats == null) - { - validStats = LoadUsageStatsFromMultipleRegistryLocations(); - } - - if (validStats != null) - { - SaveUsageStatsToAllLocations(validStats); - repaired.Add("从注册表完全恢复数据"); - } - else - { - // 最后手段:强制重建 - ForceRebuildUsageDataBackups(); - repaired.Add("强制重建所有备份"); - } - } - - // 记录检查结果 - if (issues.Count > 0) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查发现问题: {string.Join(", ", issues)}", LogHelper.LogType.Warning); - } - - if (repaired.Count > 0) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护修复: {string.Join(", ", repaired)}"); - } - - var protectionScore = CalculateUsageDataProtectionScore(); - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查完成 - 问题: {issues.Count}, 修复: {repaired.Count}, 保护强度: {protectionScore}/100"); - - return protectionScore >= 80; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 根据优先级决定是否应该推送更新(仅适用于自动更新,版本修复功能不受影响) - /// - /// 更新版本号 - /// 新版本发布时间 - /// 是否为自动更新检查(默认true,false表示版本修复) - /// 当前版本发布时间 - /// 是否应该推送更新 - public static bool ShouldPushUpdate(string updateVersion, DateTime releaseTime, bool isAutoUpdate = true, DateTime? currentVersionReleaseTime = null) - { - try - { - // 判断更新类型(基于版本号) - var updateType = DetermineUpdateType(updateVersion); - - // 如果不是自动更新(即版本修复),则应用不同的策略 - if (!isAutoUpdate) - { - // 版本修复:立即允许,不受分级策略影响 - LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复 - 版本: {updateVersion}, 类型: {updateType}, 结果: 允许"); - return true; - } - - var priority = GetUpdatePriority(); - var frequency = GetUsageFrequency(); - var stats = LoadUsageStats(); - - // 计算版本间的时间差 - double daysBetweenVersions; - if (currentVersionReleaseTime.HasValue) - { - // 使用当前版本发布时间与新版本发布时间的差异 - daysBetweenVersions = (releaseTime - currentVersionReleaseTime.Value).TotalDays; - } - else - { - // 如果没有当前版本发布时间,回退到使用新版本发布时间到现在的天数 - daysBetweenVersions = (DateTime.Now - releaseTime).TotalDays; - } - - // 计算最近活跃度(最后一次使用距今的天数) - var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; - - // 综合判断逻辑(仅适用于自动更新) - var shouldPush = ShouldPushUpdateComprehensive(priority, frequency, daysBetweenVersions, daysSinceLastUse, stats, updateType); - - LogHelper.WriteLogToFile($"DeviceIdentifier | 自动更新推送判断 - 版本: {updateVersion}, 类型: {updateType}, " + - $"优先级: {priority}, 频率: {frequency}, 版本间隔: {daysBetweenVersions:F1}天, " + - $"最后使用: {daysSinceLastUse:F1}天前, 结果: {shouldPush}"); - - return shouldPush; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 判断是否推送更新失败: {ex.Message}", LogHelper.LogType.Error); - return true; // 出错时默认推送 - } - } - - /// - /// 更新类型枚举 - /// - private enum UpdateType - { - Major, // 主版本更新 (x.0.0) - Minor, // 次版本更新 (x.y.0) - Patch, // 补丁更新 (x.y.z) - Hotfix, // 热修复更新 - Unknown // 未知类型 - } - - /// - /// 根据版本号判断更新类型 - /// - private static UpdateType DetermineUpdateType(string version) - { - if (string.IsNullOrEmpty(version)) return UpdateType.Unknown; - - try - { - // 移除可能的前缀(如 "v") - var cleanVersion = version.TrimStart('v', 'V'); - - // 检查是否包含热修复标识 - if (cleanVersion.ToLower().Contains("hotfix") || cleanVersion.ToLower().Contains("fix")) - { - return UpdateType.Hotfix; - } - - // 解析版本号 - var parts = cleanVersion.Split('.'); - if (parts.Length >= 3) - { - if (int.TryParse(parts[1], out int minor) && int.TryParse(parts[2], out int patch)) - { - if (minor == 0 && patch == 0) return UpdateType.Major; - if (patch == 0) return UpdateType.Minor; - return UpdateType.Patch; - } - } - - return UpdateType.Unknown; - } - catch - { - return UpdateType.Unknown; - } - } - - /// - /// 综合时间和使用频率的自动更新推送判断逻辑(不影响版本修复) - /// - /// 用户更新优先级 - /// 用户使用频率 - /// 当前版本与新版本之间的天数差异 - /// 距离最后使用的天数 - /// 使用统计数据 - /// 更新类型 - /// 是否应该推送更新 - private static bool ShouldPushUpdateComprehensive(UpdatePriority priority, UsageFrequency frequency, - double daysBetweenVersions, double daysSinceLastUse, UsageStats stats, UpdateType updateType) - { - // 考虑用户的总体使用模式 - var isHeavyUser = stats.TotalUsageMinutes > 3000; // 超过50小时的重度用户 - var isFrequentUser = stats.LaunchCount > 100; // 启动超过100次的频繁用户 - - // 根据更新类型调整推送策略 - var urgencyMultiplier = GetUpdateUrgencyMultiplier(updateType); - - // 如果用户长时间未使用(超过30天),降低推送优先级 - if (daysSinceLastUse > 30) - { - // 热修复和重要更新优先推送 - if (updateType == UpdateType.Hotfix) - { - return daysBetweenVersions >= 1; // 热修复版本间隔1天后推送 - } - - // 但如果是重度用户,仍然要适当推送 - var baseDelay = isHeavyUser ? 7 : 14; - return daysBetweenVersions >= (baseDelay / urgencyMultiplier); - } - - // 如果用户最近很活跃(3天内使用过) - if (daysSinceLastUse <= 3) - { - // 热修复立即推送给活跃用户 - if (updateType == UpdateType.Hotfix) - { - return true; - } - - // 结合使用频率和优先级判断 - if (frequency == UsageFrequency.High || isHeavyUser) - { - return daysBetweenVersions >= Math.Max(0, 1 / urgencyMultiplier); // 高频用户优先推送 - } - - switch (priority) - { - case UpdatePriority.High: - return daysBetweenVersions >= Math.Max(0, 1 / urgencyMultiplier); - - case UpdatePriority.Medium: - return daysBetweenVersions >= Math.Max(1, 2 / urgencyMultiplier); - - case UpdatePriority.Low: - return daysBetweenVersions >= Math.Max(2, 3 / urgencyMultiplier); - } - } - - // 中等活跃度用户(3-14天内使用过) - if (daysSinceLastUse <= 14) - { - // 热修复优先推送 - if (updateType == UpdateType.Hotfix) - { - return daysBetweenVersions >= 1; - } - - // 频繁用户优先推送 - if (isFrequentUser && frequency == UsageFrequency.High) - { - return daysBetweenVersions >= Math.Max(1, 2 / urgencyMultiplier); - } - - switch (priority) - { - case UpdatePriority.High: - return daysBetweenVersions >= Math.Max(1, 2 / urgencyMultiplier); - - case UpdatePriority.Medium: - return daysBetweenVersions >= Math.Max(2, 4 / urgencyMultiplier); - - case UpdatePriority.Low: - return daysBetweenVersions >= Math.Max(4, 7 / urgencyMultiplier); - } - } - - // 较不活跃用户(14-30天内使用过) - // 对于低频率用户,进一步延迟推送 - var delayMultiplier = frequency == UsageFrequency.Low ? 2 : 1; - - switch (priority) - { - case UpdatePriority.High: - return daysBetweenVersions >= Math.Max(2, 3 * delayMultiplier / urgencyMultiplier); - - case UpdatePriority.Medium: - return daysBetweenVersions >= Math.Max(4, 7 * delayMultiplier / urgencyMultiplier); - - case UpdatePriority.Low: - return daysBetweenVersions >= Math.Max(7, 14 * delayMultiplier / urgencyMultiplier); - - default: - return daysBetweenVersions >= 7; - } - } - - /// - /// 根据更新类型获取紧急程度倍数(仅用于自动更新分级) - /// - private static double GetUpdateUrgencyMultiplier(UpdateType updateType) - { - switch (updateType) - { - case UpdateType.Hotfix: - return 3.0; // 热修复最紧急,3倍速度推送 - case UpdateType.Major: - return 0.5; // 主版本更新较慢推送 - case UpdateType.Minor: - return 1.0; // 次版本正常推送 - case UpdateType.Patch: - return 1.5; // 补丁更新稍快推送 - case UpdateType.Unknown: - return 1.0; // 未知类型正常推送 - default: - return 1.0; - } - } - - /// - /// 检查是否应该进行版本修复(不受分级策略影响) - /// - /// 当前版本 - /// 可用版本 - /// 是否需要版本修复 - public static bool ShouldPerformVersionFix(string currentVersion, string availableVersion) - { - try - { - // 版本修复功能不受使用频率分级策略影响,始终允许 - LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复检查 - 当前版本: {currentVersion}, 可用版本: {availableVersion}, 结果: 允许"); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复检查失败: {ex.Message}", LogHelper.LogType.Error); - return true; // 出错时默认允许 - } - } - - - /// - /// 获取设备信息摘要(用于调试) - /// - public static string GetDeviceInfoSummary() - { - try - { - var (launchCount, totalSeconds, avgSessionSeconds, priority) = GetUsageStats(); - var frequency = GetUsageFrequency(); - var stats = LoadUsageStats(); - var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; - - return $"设备ID: {DeviceId}\n" + - $"启动次数: {launchCount}\n" + - $"总使用时长: {FormatDuration(totalSeconds)}\n" + - $"平均会话时长: {FormatDuration((long)avgSessionSeconds)}\n" + - $"使用频率: {frequency}\n" + - $"更新优先级: {priority}\n" + - $"最后使用: {daysSinceLastUse:F1}天前\n" + - $"用户类型: {GetUserTypeDescription(stats)}"; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 获取设备信息摘要失败: {ex.Message}", LogHelper.LogType.Error); - return $"设备ID: {DeviceId}\n获取详细信息失败"; - } - } - - /// - /// 获取用户类型描述 - /// - private static string GetUserTypeDescription(UsageStats stats) - { - var isHeavyUser = stats.TotalUsageMinutes > 3000; - var isFrequentUser = stats.LaunchCount > 100; - var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; - - var descriptions = new List(); - - if (isHeavyUser) descriptions.Add("重度用户"); - if (isFrequentUser) descriptions.Add("频繁用户"); - - if (daysSinceLastUse <= 3) descriptions.Add("高活跃"); - else if (daysSinceLastUse <= 14) descriptions.Add("中活跃"); - else if (daysSinceLastUse <= 30) descriptions.Add("低活跃"); - else descriptions.Add("非活跃"); - - return descriptions.Count > 0 ? string.Join(", ", descriptions) : "普通用户"; - } - - /// - /// 数据自检和修复 - /// - public static bool PerformDataIntegrityCheck() - { - try - { - lock (fileLock) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始数据完整性检查"); - - var issues = new List(); - var repaired = new List(); - - // 检查设备ID文件 - if (!File.Exists(DeviceIdFilePath)) - { - issues.Add("主设备ID文件丢失"); - if (File.Exists(BackupDeviceIdPath)) - { - var backupId = LoadDeviceIdFromFile(BackupDeviceIdPath); - if (!string.IsNullOrEmpty(backupId)) - { - SaveDeviceIdToFile(DeviceIdFilePath, backupId); - repaired.Add("从备份恢复主设备ID文件"); - } - } - } - - // 检查备份设备ID文件 - if (!File.Exists(BackupDeviceIdPath)) - { - issues.Add("备份设备ID文件丢失"); - var mainId = LoadDeviceIdFromFile(DeviceIdFilePath); - if (!string.IsNullOrEmpty(mainId)) - { - SaveDeviceIdToFile(BackupDeviceIdPath, mainId); - repaired.Add("重建备份设备ID文件"); - } - } - - // 检查使用统计文件 - if (!File.Exists(UsageStatsFilePath)) - { - issues.Add("主使用统计文件丢失"); - var backupStatsForRestore = LoadUsageStatsFromFile(BackupUsageStatsPath); - if (backupStatsForRestore != null) - { - SaveUsageStatsToFile(UsageStatsFilePath, backupStatsForRestore); - repaired.Add("从备份恢复主使用统计文件"); - } - } - - // 检查备份使用统计文件 - if (!File.Exists(BackupUsageStatsPath)) - { - issues.Add("备份使用统计文件丢失"); - var mainStatsForBackup = LoadUsageStatsFromFile(UsageStatsFilePath); - if (mainStatsForBackup != null) - { - SaveUsageStatsToFile(BackupUsageStatsPath, mainStatsForBackup); - repaired.Add("重建备份使用统计文件"); - } - } - - // 验证数据一致性 - var mainStats = LoadUsageStatsFromFile(UsageStatsFilePath); - var backupStats = LoadUsageStatsFromFile(BackupUsageStatsPath); - - if (mainStats != null && backupStats != null) - { - if (mainStats.LaunchCount != backupStats.LaunchCount || - mainStats.TotalUsageMinutes != backupStats.TotalUsageMinutes) - { - issues.Add("主备份数据不一致"); - // 使用最新的数据 - var newerStats = mainStats.LastModified > backupStats.LastModified ? mainStats : backupStats; - SaveUsageStatsToAllLocations(newerStats); - repaired.Add("同步主备份数据"); - } - } - - // 记录检查结果 - if (issues.Count > 0) - { - LogHelper.WriteLogToFile($"DeviceIdentifier | 发现问题: {string.Join(", ", issues)}", LogHelper.LogType.Warning); - } - - if (repaired.Count > 0) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 已修复: {string.Join(", ", repaired)}"); - } - - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性检查完成 - 问题: {issues.Count}, 修复: {repaired.Count}"); - return issues.Count == 0 || repaired.Count > 0; - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性检查失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 获取使用频率数据保护状态摘要(强化版本) - /// - public static string GetUsageDataProtectionSummary() - { - try - { - var summary = new StringBuilder(); - summary.AppendLine("使用频率数据保护状态摘要:"); - - // 检查主要文件 - summary.AppendLine($"主使用统计文件: {(File.Exists(UsageStatsFilePath) ? "✓" : "✗")}"); - summary.AppendLine($"第一备份文件: {(File.Exists(BackupUsageStatsPath) ? "✓" : "✗")}"); - - // 检查多重隐藏备份 - summary.AppendLine($"第二备份文件: {(File.Exists(SecondaryUsageBackupPath) ? "✓" : "✗")}"); - summary.AppendLine($"第三备份文件: {(File.Exists(TertiaryUsageBackupPath) ? "✓" : "✗")}"); - summary.AppendLine($"第四备份文件: {(File.Exists(QuaternaryUsageBackupPath) ? "✓" : "✗")}"); - - // 检查注册表备份 - var registryBackups = 0; - var registryPaths = new[] - { - @"Software\ICC\DeviceInfo", - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) registryBackups++; - } - } - catch { } - } - - summary.AppendLine($"注册表备份位置: {registryBackups}/4 ✓"); - - // 检查数据完整性和可恢复性 - var stats = LoadUsageStats(); - if (stats != null) - { - summary.AppendLine($"数据完整性: {(stats.VerifyDataIntegrity() ? "✓" : "✗")}"); - summary.AppendLine($"总启动次数: {stats.LaunchCount}"); - summary.AppendLine($"总使用时长: {FormatDuration(stats.TotalUsageSeconds)}"); - summary.AppendLine($"本周启动次数: {stats.WeeklyLaunchCount}"); - summary.AppendLine($"本周使用时长: {FormatDuration(stats.WeeklyUsageSeconds)}"); - summary.AppendLine($"上周启动次数: {stats.LastWeekLaunchCount}"); - summary.AppendLine($"上周使用时长: {FormatDuration(stats.LastWeekUsageSeconds)}"); - summary.AppendLine($"本周开始日期: {(stats.WeekStartDate != DateTime.MinValue ? stats.WeekStartDate.ToString("yyyy-MM-dd") : "未设置")}"); - summary.AppendLine($"使用频率: {stats.UsageFrequency}"); - summary.AppendLine($"更新优先级: {stats.UpdatePriority}"); - summary.AppendLine($"最后修改: {stats.LastModified:yyyy-MM-dd HH:mm:ss}"); - } - - // 计算保护强度评分 - var protectionScore = CalculateUsageDataProtectionScore(); - summary.AppendLine($"保护强度评分: {protectionScore}/100"); - - return summary.ToString(); - } - catch (Exception ex) - { - return $"获取使用频率数据保护状态失败: {ex.Message}"; - } - } - - /// - /// 计算使用频率数据保护强度评分 - /// - private static int CalculateUsageDataProtectionScore() - { - var score = 0; - - try - { - // 文件备份评分(50分) - if (File.Exists(UsageStatsFilePath)) score += 15; - if (File.Exists(BackupUsageStatsPath)) score += 10; - if (File.Exists(SecondaryUsageBackupPath)) score += 8; - if (File.Exists(TertiaryUsageBackupPath)) score += 8; - if (File.Exists(QuaternaryUsageBackupPath)) score += 9; - - // 注册表备份评分(30分) - var registryPaths = new[] - { - @"Software\ICC\DeviceInfo", - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) score += 7; - } - } - catch { } - } - - // 数据完整性评分(20分) - var stats = LoadUsageStats(); - if (stats != null) - { - if (!string.IsNullOrEmpty(stats.DataHash)) score += 10; - if (stats.VerifyDataIntegrity()) score += 10; - } - } - catch { } - - return Math.Min(100, score); - } - - /// - /// 强制重建所有使用频率数据备份 - /// - public static bool ForceRebuildUsageDataBackups() - { - try - { - lock (fileLock) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始强制重建使用频率数据备份"); - - var stats = LoadUsageStats(); - if (stats == null) - { - // 如果无法加载任何数据,创建基础数据 - stats = new UsageStats - { - DeviceId = DeviceId, - LastLaunchTime = DateTime.Now, - LaunchCount = 1, - TotalUsageMinutes = 0, - AverageSessionMinutes = 0, - LastUpdateCheck = DateTime.MinValue, - UpdatePriority = UpdatePriority.Medium, - UsageFrequency = UsageFrequency.Medium - }; - stats.UpdateDataHash(); - LogHelper.WriteLogToFile("DeviceIdentifier | 创建新的基础使用数据"); - } - - // 强制保存到所有位置 - SaveUsageStatsToAllLocations(stats); - - // 验证重建结果 - var protectionScore = CalculateUsageDataProtectionScore(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据备份重建完成,保护强度: {protectionScore}/100"); - - return protectionScore >= 80; // 80分以上认为重建成功 - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 强制重建使用频率数据备份失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 获取数据保护状态摘要(保持向后兼容) - /// - public static string GetDataProtectionSummary() - { - return GetUsageDataProtectionSummary(); - } - - - - - - /// - /// 强制执行一次完整的数据保存操作(包括注册表) - /// - public static bool ForceCompleteDataSave() - { - try - { - lock (fileLock) - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始强制完整数据保存"); - - // 保存设备ID到所有位置 - SaveDeviceIdToAllLocations(DeviceId); - - // 加载并保存使用统计到所有位置 - var stats = LoadUsageStats(); - if (stats != null) - { - stats.UpdateDataHash(); - SaveUsageStatsToAllLocations(stats); - - // 验证注册表保存是否成功 - var verificationResult = VerifyRegistryData(); - // LogHelper.WriteLogToFile($"DeviceIdentifier | 注册表数据验证结果: {verificationResult}"); - - // LogHelper.WriteLogToFile("DeviceIdentifier | 强制完整数据保存完成"); - return true; - } - - // LogHelper.WriteLogToFile("DeviceIdentifier | 强制完整数据保存失败: 无法加载使用统计", LogHelper.LogType.Error); - return false; - } - } - catch (Exception ex) - { - // LogHelper.WriteLogToFile($"DeviceIdentifier | 强制完整数据保存失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 验证注册表中的数据是否存在 - /// - public static string VerifyRegistryData() - { - var results = new StringBuilder(); - results.AppendLine("注册表数据验证结果:"); - - try - { - // 验证主注册表位置 - try - { - using (var key = Registry.CurrentUser.OpenSubKey(@"Software\ICC\DeviceInfo")) - { - if (key != null) - { - var deviceId = key.GetValue("DeviceId") as string; - var launchCount = key.GetValue("LaunchCount"); - var totalMinutes = key.GetValue("TotalUsageMinutes"); - var lastUpdate = key.GetValue("LastUpdate") as string; - - results.AppendLine("✓ 主注册表位置存在"); - results.AppendLine($" 设备ID: {deviceId ?? "未找到"}"); - results.AppendLine($" 启动次数: {launchCount ?? "未找到"}"); - results.AppendLine($" 使用时长: {totalMinutes ?? "未找到"}分钟"); - results.AppendLine($" 最后更新: {lastUpdate ?? "未找到"}"); - } - else - { - results.AppendLine("✗ 主注册表位置不存在"); - } - } - } - catch (Exception ex) - { - results.AppendLine($"✗ 主注册表位置访问失败: {ex.Message}"); - } - - // 验证备用注册表位置 - var registryPaths = new[] - { - @"Software\Microsoft\Windows\CurrentVersion\ICC", - @"Software\Classes\.icc\UsageData", - @"Software\ICC\Config\Usage" - }; - - foreach (var path in registryPaths) - { - try - { - using (var key = Registry.CurrentUser.OpenSubKey(path)) - { - if (key != null) - { - var launchCount = key.GetValue("LC"); - var totalMinutes = key.GetValue("TUM"); - var lastUpdate = key.GetValue("LU"); - - results.AppendLine($"✓ 备用注册表位置存在: {path}"); - results.AppendLine($" 启动次数: {launchCount ?? "未找到"}"); - results.AppendLine($" 使用时长: {totalMinutes ?? "未找到"}"); - results.AppendLine($" 最后更新: {(lastUpdate != null ? DateTime.FromBinary(Convert.ToInt64(lastUpdate)).ToString("yyyy-MM-dd HH:mm:ss") : "未找到")}"); - } - else - { - results.AppendLine($"✗ 备用注册表位置不存在: {path}"); - } - } - } - catch (Exception ex) - { - results.AppendLine($"✗ 备用注册表位置访问失败 ({path}): {ex.Message}"); - } - } - - return results.ToString(); - } - catch (Exception ex) - { - return $"注册表数据验证失败: {ex.Message}"; - } - } - - /// - /// 立即执行一次数据保存并验证注册表写入 - /// - public static string SaveAndVerifyRegistryData() - { - try - { - // LogHelper.WriteLogToFile("DeviceIdentifier | 开始保存并验证注册表数据"); - - // 强制保存数据 - var saveSuccess = ForceCompleteDataSave(); - - // 验证注册表数据 - var verificationResult = VerifyRegistryData(); - - var result = $"保存操作: {(saveSuccess ? "成功" : "失败")}\n\n{verificationResult}"; - - // LogHelper.WriteLogToFile("DeviceIdentifier | 保存并验证注册表数据完成"); - return result; - } - catch (Exception ex) - { - var errorMsg = $"保存并验证注册表数据失败: {ex.Message}"; - LogHelper.WriteLogToFile($"DeviceIdentifier | {errorMsg}", LogHelper.LogType.Error); - return errorMsg; - } - } - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs deleted file mode 100644 index 9ac6f811..00000000 --- a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; - -namespace Ink_Canvas.Helpers -{ - /// - /// 硬件加速的墨迹处理器,利用WPF的GPU渲染能力 - /// - public class HardwareAcceleratedInkProcessor - { - private readonly RenderTargetBitmap _renderTarget; - private readonly DrawingVisual _drawingVisual; - private readonly DrawingContext _drawingContext; - private bool _isInitialized; - - public HardwareAcceleratedInkProcessor(int width = 1920, int height = 1080) - { - // 创建硬件加速的渲染目标 - _renderTarget = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); - _drawingVisual = new DrawingVisual(); - - // 启用硬件加速 - RenderOptions.SetBitmapScalingMode(_drawingVisual, BitmapScalingMode.HighQuality); - RenderOptions.SetEdgeMode(_drawingVisual, EdgeMode.Aliased); - - _isInitialized = true; - } - - /// - /// 使用GPU加速的贝塞尔曲线平滑 - /// - public async Task SmoothStrokeWithGPU(Stroke originalStroke) - { - if (!_isInitialized || originalStroke == null || originalStroke.StylusPoints.Count < 2) - return originalStroke; - - return await Task.Run(() => - { - try - { - // 使用PathGeometry进行硬件加速的曲线拟合 - var pathGeometry = CreateSmoothPathGeometry(originalStroke.StylusPoints); - - // 将PathGeometry转换回StylusPoint集合 - var smoothedPoints = ConvertPathGeometryToStylusPoints(pathGeometry, originalStroke.StylusPoints); - - return new Stroke(new StylusPointCollection(smoothedPoints)) - { - DrawingAttributes = originalStroke.DrawingAttributes.Clone() - }; - } - catch - { - return originalStroke; - } - }); - } - - /// - /// 创建平滑的路径几何体 - /// - private PathGeometry CreateSmoothPathGeometry(StylusPointCollection points) - { - var pathGeometry = new PathGeometry(); - var pathFigure = new PathFigure(); - - if (points.Count < 2) return pathGeometry; - - pathFigure.StartPoint = new Point(points[0].X, points[0].Y); - - // 使用贝塞尔曲线段创建平滑路径,增加插点密度 - for (int i = 0; i < points.Count - 1; i += 2) // 从i+=3改为i+=2,增加插点密度 - { - var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint; - var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1; - var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2; - - var bezierSegment = new BezierSegment(p1, p2, p3, true); - pathFigure.Segments.Add(bezierSegment); - } - - pathGeometry.Figures.Add(pathFigure); - return pathGeometry; - } - - /// - /// 将PathGeometry转换为StylusPoint集合 - /// - private List ConvertPathGeometryToStylusPoints(PathGeometry pathGeometry, StylusPointCollection originalPoints) - { - var result = new List(); - var flattened = pathGeometry.GetFlattenedPathGeometry(); - - foreach (var figure in flattened.Figures) - { - result.Add(new StylusPoint(figure.StartPoint.X, figure.StartPoint.Y, 0.5f)); - - foreach (var segment in figure.Segments) - { - if (segment is LineSegment lineSegment) - { - result.Add(new StylusPoint(lineSegment.Point.X, lineSegment.Point.Y, 0.5f)); - } - else if (segment is PolyLineSegment polyLineSegment) - { - foreach (var point in polyLineSegment.Points) - { - result.Add(new StylusPoint(point.X, point.Y, 0.5f)); - } - } - } - } - - // 保持原始压感信息 - InterpolatePressure(result, originalPoints); - - return result; - } - - /// - /// 插值压感信息 - /// - private void InterpolatePressure(List smoothedPoints, StylusPointCollection originalPoints) - { - if (originalPoints.Count == 0 || smoothedPoints.Count == 0) return; - - for (int i = 0; i < smoothedPoints.Count; i++) - { - double ratio = (double)i / (smoothedPoints.Count - 1); - int originalIndex = (int)(ratio * (originalPoints.Count - 1)); - originalIndex = Math.Max(0, Math.Min(originalIndex, originalPoints.Count - 1)); - - var point = smoothedPoints[i]; - float pressure = originalPoints[originalIndex].PressureFactor; - smoothedPoints[i] = new StylusPoint(point.X, point.Y, Math.Max(pressure, 0.1f)); - } - } - - /// - /// 使用GPU加速的并行贝塞尔计算 - /// - public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 32) - { - if (controlPoints.Length < 4) return controlPoints; - - var result = new StylusPoint[segments * (controlPoints.Length / 4)]; - - Parallel.For(0, controlPoints.Length / 4, segmentIndex => - { - var p0 = controlPoints[segmentIndex * 4]; - var p1 = controlPoints[segmentIndex * 4 + 1]; - var p2 = controlPoints[segmentIndex * 4 + 2]; - var p3 = controlPoints[segmentIndex * 4 + 3]; - - for (int i = 0; i < segments; i++) - { - double t = (double)i / (segments - 1); - result[segmentIndex * segments + i] = CubicBezierFast(p0, p1, p2, p3, t); - } - }); - - return result; - } - - /// - /// 优化的三次贝塞尔曲线计算 - /// - private static StylusPoint CubicBezierFast(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t) - { - double u = 1 - t; - double tt = t * t; - double uu = u * u; - double uuu = uu * u; - double ttt = tt * t; - - double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X; - double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y; - float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t); - - return new StylusPoint(x, y, Math.Max(pressure, 0.1f)); - } - - /// - /// 释放GPU资源 - /// - public void Dispose() - { - _drawingContext?.Close(); - _renderTarget?.Clear(); - _isInitialized = false; - } - } - - /// - /// 质量配置枚举 - /// - public enum InkSmoothingQuality - { - HighPerformance = 0, // 高性能低质量 - Balanced = 1, // 平衡 - HighQuality = 2 // 高质量低性能 - } - - /// - /// 墨迹平滑配置 - /// - public class InkSmoothingConfig - { - public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.HighQuality; - public bool UseHardwareAcceleration { get; set; } = true; - public bool UseAsyncProcessing { get; set; } = true; - public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; - public double SmoothingStrength { get; set; } = 0.8; // 高质量模式的平滑强度 - public double ResampleInterval { get; set; } = 0.8; // 高质量模式的重采样间隔 - public int InterpolationSteps { get; set; } = 64; // 高质量模式的插值步数 - - public static InkSmoothingConfig FromSettings() - { - return new InkSmoothingConfig - { - Quality = (InkSmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality, - UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration, - UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing, - MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ? - MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount - }; - } - - public void ApplyQualitySettings() - { - switch (Quality) - { - case InkSmoothingQuality.HighPerformance: - SmoothingStrength = 0.4; - ResampleInterval = 2.0; - InterpolationSteps = 16; - break; - case InkSmoothingQuality.Balanced: - SmoothingStrength = 0.6; - ResampleInterval = 1.2; - InterpolationSteps = 32; - break; - case InkSmoothingQuality.HighQuality: - SmoothingStrength = 0.8; - ResampleInterval = 0.8; - InterpolationSteps = 64; - break; - } - } - } -} diff --git a/Ink Canvas/Helpers/IACoreDllExtractor.cs b/Ink Canvas/Helpers/IACoreDllExtractor.cs deleted file mode 100644 index de717826..00000000 --- a/Ink Canvas/Helpers/IACoreDllExtractor.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Windows; - -namespace Ink_Canvas.Helpers -{ - /// - /// IACore DLL自动释放器 - /// 在应用启动时自动释放IACore相关的DLL文件到应用程序目录 - /// - public static class IACoreDllExtractor - { - private static readonly string[] RequiredDlls = { - "IACore.dll", - "IALoader.dll", - "IAWinFX.dll" - }; - - /// - /// 在应用启动时释放IACore相关DLL - /// - public static void ExtractIACoreDlls() - { - try - { - string appDirectory = AppDomain.CurrentDomain.BaseDirectory; - LogHelper.WriteLogToFile("开始检查并释放IACore相关DLL文件"); - - foreach (string dllName in RequiredDlls) - { - string targetPath = Path.Combine(appDirectory, dllName); - - // 检查文件是否已存在且有效 - if (File.Exists(targetPath) && IsValidDll(targetPath)) - { - LogHelper.WriteLogToFile($"{dllName} 已存在且有效,跳过释放"); - continue; - } - - // 从嵌入资源中释放DLL - if (ExtractDllFromResource(dllName, targetPath)) - { - LogHelper.WriteLogToFile($"成功释放 {dllName} 到 {targetPath}"); - } - else - { - LogHelper.WriteLogToFile($"警告:无法释放 {dllName},可能影响形状识别功能", LogHelper.LogType.Warning); - } - } - - LogHelper.WriteLogToFile("IACore DLL释放检查完成"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 从嵌入资源中提取DLL文件 - /// - private static bool ExtractDllFromResource(string dllName, string targetPath) - { - try - { - Assembly assembly = Assembly.GetExecutingAssembly(); - string resourceName = $"Ink_Canvas.Resources.IACore.{dllName}"; - - using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) - { - if (resourceStream == null) - { - LogHelper.WriteLogToFile($"未找到嵌入资源: {resourceName}", LogHelper.LogType.Warning); - return false; - } - - // 确保目标目录存在 - string targetDirectory = Path.GetDirectoryName(targetPath); - if (!Directory.Exists(targetDirectory)) - { - Directory.CreateDirectory(targetDirectory); - } - - // 写入文件 - using (FileStream fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write)) - { - resourceStream.CopyTo(fileStream); - } - - return true; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"从资源提取 {dllName} 失败: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 检查DLL文件是否有效 - /// - private static bool IsValidDll(string filePath) - { - try - { - if (!File.Exists(filePath)) - return false; - - FileInfo fileInfo = new FileInfo(filePath); - - // 检查文件大小(空文件或过小的文件可能无效) - if (fileInfo.Length < 1024) // 小于1KB可能无效 - return false; - - // 简单检查PE头(DLL文件应该以MZ开头) - using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) - { - byte[] buffer = new byte[2]; - if (fs.Read(buffer, 0, 2) == 2) - { - return buffer[0] == 0x4D && buffer[1] == 0x5A; // "MZ" - } - } - - return false; - } - catch - { - return false; - } - } - - /// - /// 清理释放的DLL文件(可选,在应用退出时调用) - /// - public static void CleanupExtractedDlls() - { - try - { - string appDirectory = AppDomain.CurrentDomain.BaseDirectory; - - foreach (string dllName in RequiredDlls) - { - string filePath = Path.Combine(appDirectory, dllName); - - if (File.Exists(filePath)) - { - try - { - File.Delete(filePath); - LogHelper.WriteLogToFile($"已清理 {dllName}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理 {dllName} 失败: {ex.Message}", LogHelper.LogType.Warning); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } -} diff --git a/Ink Canvas/Helpers/InkSmoothingManager.cs b/Ink Canvas/Helpers/InkSmoothingManager.cs deleted file mode 100644 index c6490e41..00000000 --- a/Ink Canvas/Helpers/InkSmoothingManager.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Ink; -using System.Windows.Media; -using System.Windows.Threading; - -namespace Ink_Canvas.Helpers -{ - /// - /// 统一的墨迹平滑管理器,整合异步处理和硬件加速 - /// - public class InkSmoothingManager : IDisposable - { - private readonly AsyncAdvancedBezierSmoothing _asyncSmoothing; - private readonly HardwareAcceleratedInkProcessor _hardwareProcessor; - private readonly InkSmoothingPerformanceMonitor _performanceMonitor; - private readonly InkSmoothingConfig _config; - private readonly Dispatcher _uiDispatcher; - private bool _disposed; - - public InkSmoothingManager(Dispatcher uiDispatcher) - { - _uiDispatcher = uiDispatcher; - _config = InkSmoothingConfig.FromSettings(); - _config.ApplyQualitySettings(); - - _asyncSmoothing = new AsyncAdvancedBezierSmoothing(uiDispatcher) - { - SmoothingStrength = _config.SmoothingStrength, - ResampleInterval = _config.ResampleInterval, - InterpolationSteps = _config.InterpolationSteps, - UseHardwareAcceleration = _config.UseHardwareAcceleration, - MaxConcurrentTasks = _config.MaxConcurrentTasks - }; - - _hardwareProcessor = new HardwareAcceleratedInkProcessor(); - _performanceMonitor = new InkSmoothingPerformanceMonitor(); - } - - /// - /// 平滑笔画(自动选择最佳方法) - /// - public async Task SmoothStrokeAsync(Stroke originalStroke, - Action onCompleted = null, - CancellationToken cancellationToken = default) - { - if (originalStroke == null || originalStroke.StylusPoints.Count < 2) - return originalStroke; - - var stopwatch = Stopwatch.StartNew(); - Stroke result = originalStroke; - - try - { - if (_config.UseAsyncProcessing) - { - // 使用异步处理 - result = await _asyncSmoothing.SmoothStrokeAsync(originalStroke, onCompleted, cancellationToken); - } - else if (_config.UseHardwareAcceleration) - { - // 使用硬件加速但同步处理 - result = await _hardwareProcessor.SmoothStrokeWithGPU(originalStroke); - onCompleted?.Invoke(originalStroke, result); - } - else - { - // 回退到传统同步处理 - result = await Task.Run(() => - { - var traditionalSmoothing = new AdvancedBezierSmoothing(); - return traditionalSmoothing.SmoothStroke(originalStroke); - }, cancellationToken); - onCompleted?.Invoke(originalStroke, result); - } - } - catch (OperationCanceledException) - { - result = originalStroke; - } - catch (Exception ex) - { - Debug.WriteLine($"墨迹平滑失败: {ex.Message}"); - result = originalStroke; - } - finally - { - stopwatch.Stop(); - _performanceMonitor.RecordProcessingTime(stopwatch.Elapsed); - } - - return result; - } - - /// - /// 同步平滑笔画(用于向后兼容) - /// - public Stroke SmoothStroke(Stroke originalStroke) - { - if (originalStroke == null || originalStroke.StylusPoints.Count < 2) - return originalStroke; - - var stopwatch = Stopwatch.StartNew(); - Stroke result; - - try - { - if (_config.UseHardwareAcceleration) - { - // 使用硬件加速的同步版本 - var task = _hardwareProcessor.SmoothStrokeWithGPU(originalStroke); - task.Wait(5000); // 5秒超时 - result = task.Status == TaskStatus.RanToCompletion ? task.Result : originalStroke; - } - else - { - // 传统同步处理 - var traditionalSmoothing = new AdvancedBezierSmoothing(); - result = traditionalSmoothing.SmoothStroke(originalStroke); - } - } - catch (Exception ex) - { - Debug.WriteLine($"同步墨迹平滑失败: {ex.Message}"); - result = originalStroke; - } - finally - { - stopwatch.Stop(); - _performanceMonitor.RecordProcessingTime(stopwatch.Elapsed); - } - - return result; - } - - /// - /// 更新配置 - /// - public void UpdateConfig() - { - var newConfig = InkSmoothingConfig.FromSettings(); - newConfig.ApplyQualitySettings(); - - _asyncSmoothing.SmoothingStrength = newConfig.SmoothingStrength; - _asyncSmoothing.ResampleInterval = newConfig.ResampleInterval; - _asyncSmoothing.InterpolationSteps = newConfig.InterpolationSteps; - _asyncSmoothing.UseHardwareAcceleration = newConfig.UseHardwareAcceleration; - _asyncSmoothing.MaxConcurrentTasks = newConfig.MaxConcurrentTasks; - } - - /// - /// 获取性能统计信息 - /// - public string GetPerformanceStats() - { - return $"平均处理时间: {_performanceMonitor.GetAverageProcessingTimeMs():F2}ms, " + - $"最大处理时间: {_performanceMonitor.GetMaxProcessingTimeMs():F2}ms, " + - $"样本数: {_performanceMonitor.GetSampleCount()}"; - } - - /// - /// 取消所有正在进行的任务 - /// - public void CancelAllTasks() - { - _asyncSmoothing?.CancelAllTasks(); - } - - /// - /// 检查系统是否支持硬件加速 - /// - public static bool IsHardwareAccelerationSupported() - { - try - { - return RenderCapability.Tier >= 0x00020000; - } - catch - { - return false; - } - } - - /// - /// 获取推荐的配置 - /// - public static InkSmoothingConfig GetRecommendedConfig() - { - var config = new InkSmoothingConfig(); - - // 根据系统性能调整配置 - var processorCount = Environment.ProcessorCount; - var isHardwareAccelerated = IsHardwareAccelerationSupported(); - - if (processorCount >= 4 && isHardwareAccelerated) - { - // 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量 - config.Quality = InkSmoothingQuality.HighQuality; - config.UseHardwareAcceleration = true; - config.UseAsyncProcessing = true; - config.MaxConcurrentTasks = Math.Min(processorCount, 8); - } - else if (processorCount >= 2) - { - // 2核以上使用平衡模式 - config.Quality = InkSmoothingQuality.Balanced; - config.UseHardwareAcceleration = isHardwareAccelerated; - config.UseAsyncProcessing = true; - config.MaxConcurrentTasks = Math.Min(processorCount, 4); - } - else - { - // 单核或性能较低的设备使用高性能模式 - config.Quality = InkSmoothingQuality.HighPerformance; - config.UseHardwareAcceleration = false; - config.UseAsyncProcessing = false; - config.MaxConcurrentTasks = 1; - } - - config.ApplyQualitySettings(); - return config; - } - - /// - /// 应用推荐配置到设置 - /// - public static void ApplyRecommendedSettings() - { - var config = GetRecommendedConfig(); - - MainWindow.Settings.Canvas.InkSmoothingQuality = (int)config.Quality; - MainWindow.Settings.Canvas.UseHardwareAcceleration = config.UseHardwareAcceleration; - MainWindow.Settings.Canvas.UseAsyncInkSmoothing = config.UseAsyncProcessing; - MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = config.MaxConcurrentTasks; - } - - public void Dispose() - { - if (!_disposed) - { - CancelAllTasks(); - _asyncSmoothing?.Dispose(); - _hardwareProcessor?.Dispose(); - _disposed = true; - } - } - } - - /// - /// 墨迹平滑事件参数 - /// - public class InkSmoothingEventArgs : EventArgs - { - public Stroke OriginalStroke { get; set; } - public Stroke SmoothedStroke { get; set; } - public TimeSpan ProcessingTime { get; set; } - public bool WasAsync { get; set; } - public bool UsedHardwareAcceleration { get; set; } - } -} diff --git a/Ink Canvas/Helpers/LogHelper.cs b/Ink Canvas/Helpers/LogHelper.cs deleted file mode 100644 index 29436f64..00000000 --- a/Ink Canvas/Helpers/LogHelper.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; - -namespace Ink_Canvas.Helpers -{ - class LogHelper - { - public static string LogFile = "Log.txt"; - private static string LogsFolder = "Logs"; - private static string AppStartTime = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"); - private static readonly long MaxLogsFolderSizeBytes = 5 * 1024 * 1024; // 5MB - - public static void NewLog(string str) - { - WriteLogToFile(str); - } - - public static void NewLog(Exception ex) - { - if (ex == null) return; - var stackTrace = ex.StackTrace ?? ""; - var msg = $"[Exception] Type: {ex.GetType().FullName}\nMessage: {ex.Message}\nStackTrace: {stackTrace}"; - if (ex.InnerException != null) - { - msg += $"\nInnerException: {ex.InnerException.GetType().FullName} - {ex.InnerException.Message}\n{ex.InnerException.StackTrace}"; - } - WriteLogToFile(msg, LogType.Error); - } - - public static void WriteLogToFile(string str, LogType logType = LogType.Info) - { - // 检查日志是否启用 - if (MainWindow.Settings != null && MainWindow.Settings.Advanced != null && !MainWindow.Settings.Advanced.IsLogEnabled) return; - - string strLogType = logType.ToString(); - try - { - string file; - - // 检查是否启用了日期保存功能 - if (MainWindow.Settings != null && MainWindow.Settings.Advanced != null && MainWindow.Settings.Advanced.IsSaveLogByDate) - { - // 确保Logs文件夹存在 - string logsPath = Path.Combine(App.RootPath, LogsFolder); - if (!Directory.Exists(logsPath)) - { - Directory.CreateDirectory(logsPath); - } - - // 检查Logs文件夹大小,如果超过5MB则清空 - CheckAndCleanLogsFolder(logsPath); - - // 使用软件启动时间作为日志文件名 - file = Path.Combine(logsPath, $"Log_{AppStartTime}.txt"); - } - else - { - file = App.RootPath + LogFile; - } - - if (!Directory.Exists(App.RootPath)) - { - Directory.CreateDirectory(App.RootPath); - } - - var threadId = Thread.CurrentThread.ManagedThreadId; - var callingMethod = new StackTrace(2, true).GetFrame(0); - string callerInfo = ""; - if (callingMethod != null) - { - var method = callingMethod.GetMethod(); - if (method != null) - { - var className = method.DeclaringType != null ? method.DeclaringType.FullName : ""; - callerInfo = $"{className}.{method.Name}"; - } - } - string logLine = string.Format("{0} [T{1}] [{2}] [{3}] {4}", DateTime.Now.ToString("O"), threadId, strLogType, callerInfo, str); - using (StreamWriter sw = new StreamWriter(file, true)) - { - sw.WriteLine(logLine); - } - } - catch { } - } - - private static void CheckAndCleanLogsFolder(string logsPath) - { - try - { - long totalSize = 0; - DirectoryInfo dirInfo = new DirectoryInfo(logsPath); - - // 如果目录不存在,直接返回 - if (!dirInfo.Exists) return; - - // 计算文件夹大小 - foreach (FileInfo file in dirInfo.GetFiles()) - { - totalSize += file.Length; - } - - // 如果超过5MB,清空文件夹 - if (totalSize > MaxLogsFolderSizeBytes) - { - foreach (FileInfo file in dirInfo.GetFiles()) - { - try - { - file.Delete(); - } - catch { } - } - - // 记录清理操作 - string cleanupMessage = $"Logs folder exceeded size limit ({totalSize / 1024.0 / 1024.0:F2} MB > {MaxLogsFolderSizeBytes / 1024.0 / 1024.0:F2} MB). Folder cleaned."; - using (StreamWriter sw = new StreamWriter(Path.Combine(logsPath, $"Log_{AppStartTime}.txt"), true)) - { - sw.WriteLine($"{DateTime.Now:O} [Cleanup] {cleanupMessage}"); - } - } - } - catch { } - } - - internal static void WriteLogToFile(string v, object warning) - { - WriteLogToFile($"[Warning] {v}", LogType.Warning); - } - - public enum LogType - { - Info, - Trace, - Error, - Event, - Warning - } - } -} diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs deleted file mode 100644 index 549ab5ab..00000000 --- a/Ink Canvas/Helpers/PPTInkManager.cs +++ /dev/null @@ -1,397 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using System.Windows.Ink; -using System.Windows.Threading; -using Microsoft.Office.Interop.PowerPoint; - -namespace Ink_Canvas.Helpers -{ - /// - /// PPT墨迹管理器 - 负责PPT中墨迹的保存、加载和同步 - /// - public class PPTInkManager : IDisposable - { - #region Properties - public bool IsAutoSaveEnabled { get; set; } = true; - public string AutoSaveLocation { get; set; } = ""; - public StrokeCollection CurrentStrokes { get; private set; } = new StrokeCollection(); - #endregion - - #region Private Fields - private MemoryStream[] _memoryStreams; - private int _maxSlides = 100; - private string _currentPresentationId = ""; - private readonly object _lockObject = new object(); - private bool _disposed = false; - - // 墨迹锁定机制,防止翻页时的墨迹冲突 - private DateTime _inkLockUntil = DateTime.MinValue; - private int _lockedSlideIndex = -1; - private const int InkLockMilliseconds = 500; - #endregion - - #region Constructor - public PPTInkManager() - { - InitializeMemoryStreams(); - } - - private void InitializeMemoryStreams() - { - _memoryStreams = new MemoryStream[_maxSlides + 2]; - } - #endregion - - #region Public Methods - /// - /// 初始化新的演示文稿 - /// - public void InitializePresentation(Presentation presentation) - { - if (presentation == null) return; - - lock (_lockObject) - { - try - { - // 生成演示文稿唯一标识符 - _currentPresentationId = GeneratePresentationId(presentation); - - // 重新初始化内存流数组 - var slideCount = presentation.Slides.Count; - _memoryStreams = new MemoryStream[slideCount + 2]; - - // 如果启用自动保存,尝试加载已保存的墨迹 - if (IsAutoSaveEnabled && !string.IsNullOrEmpty(AutoSaveLocation)) - { - LoadSavedStrokes(); - } - - LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化演示文稿墨迹管理失败: {ex}", LogHelper.LogType.Error); - } - } - } - - /// - /// 保存当前页面的墨迹 - /// - public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes) - { - if (slideIndex <= 0 || strokes == null) return; - - lock (_lockObject) - { - try - { - // 检查墨迹锁定 - if (!CanWriteInk(slideIndex)) - { - LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning); - return; - } - - if (slideIndex < _memoryStreams.Length) - { - var ms = new MemoryStream(); - strokes.Save(ms); - ms.Position = 0; - - // 释放旧的内存流 - _memoryStreams[slideIndex]?.Dispose(); - _memoryStreams[slideIndex] = ms; - - LogHelper.WriteLogToFile($"已保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - } - - /// - /// 加载指定页面的墨迹 - /// - public StrokeCollection LoadSlideStrokes(int slideIndex) - { - if (slideIndex <= 0) return new StrokeCollection(); - - lock (_lockObject) - { - try - { - if (slideIndex < _memoryStreams.Length && _memoryStreams[slideIndex] != null && _memoryStreams[slideIndex].Length > 0) - { - _memoryStreams[slideIndex].Position = 0; - var strokes = new StrokeCollection(_memoryStreams[slideIndex]); - LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace); - return strokes; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - - return new StrokeCollection(); - } - - /// - /// 切换到指定页面并加载墨迹 - /// - public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null) - { - lock (_lockObject) - { - try - { - // 如果有当前墨迹,先保存 - if (currentStrokes != null && currentStrokes.Count > 0) - { - SaveCurrentSlideStrokes(_lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex, currentStrokes); - } - - // 设置墨迹锁定 - LockInkForSlide(slideIndex); - - // 加载新页面的墨迹 - return LoadSlideStrokes(slideIndex); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"切换到第{slideIndex}页失败: {ex}", LogHelper.LogType.Error); - return new StrokeCollection(); - } - } - } - - /// - /// 保存所有墨迹到文件 - /// - public void SaveAllStrokesToFile(Presentation presentation) - { - if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return; - - lock (_lockObject) - { - try - { - var folderPath = GetPresentationFolderPath(); - if (!Directory.Exists(folderPath)) - { - Directory.CreateDirectory(folderPath); - } - - // 保存位置信息 - try - { - File.WriteAllText(Path.Combine(folderPath, "Position"), _lockedSlideIndex.ToString()); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存位置信息失败: {ex}", LogHelper.LogType.Error); - } - - // 保存所有页面的墨迹 - int savedCount = 0; - for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++) - { - if (_memoryStreams[i] != null) - { - try - { - if (_memoryStreams[i].Length > 8) - { - var srcBuf = new byte[_memoryStreams[i].Length]; - _memoryStreams[i].Position = 0; - var byteLength = _memoryStreams[i].Read(srcBuf, 0, srcBuf.Length); - - var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk"); - File.WriteAllBytes(filePath, srcBuf); - savedCount++; - - LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace); - } - else - { - // 删除空的墨迹文件 - var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk"); - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存第{i}页墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - } - - LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存墨迹到文件失败: {ex}", LogHelper.LogType.Error); - } - } - } - - /// - /// 从文件加载已保存的墨迹 - /// - public void LoadSavedStrokes() - { - if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation)) return; - - lock (_lockObject) - { - try - { - var folderPath = GetPresentationFolderPath(); - if (!Directory.Exists(folderPath)) return; - - var files = new DirectoryInfo(folderPath).GetFiles("*.icstk"); - int loadedCount = 0; - - foreach (var file in files) - { - try - { - if (int.TryParse(Path.GetFileNameWithoutExtension(file.Name), out int slideIndex)) - { - if (slideIndex > 0 && slideIndex < _memoryStreams.Length) - { - var fileBytes = File.ReadAllBytes(file.FullName); - _memoryStreams[slideIndex] = new MemoryStream(fileBytes); - _memoryStreams[slideIndex].Position = 0; - loadedCount++; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载墨迹文件{file.Name}失败: {ex}", LogHelper.LogType.Error); - } - } - - LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - } - - /// - /// 清除所有墨迹 - /// - public void ClearAllStrokes() - { - lock (_lockObject) - { - try - { - for (int i = 0; i < _memoryStreams.Length; i++) - { - _memoryStreams[i]?.Dispose(); - _memoryStreams[i] = null; - } - - CurrentStrokes.Clear(); - LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清除墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - } - - /// - /// 翻页后锁定墨迹写入 - /// - public void LockInkForSlide(int slideIndex) - { - _inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds); - _lockedSlideIndex = slideIndex; - } - - /// - /// 检查是否可以写入墨迹 - /// - public bool CanWriteInk(int currentSlideIndex) - { - if (DateTime.Now < _inkLockUntil) return false; - if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false; - return true; - } - #endregion - - #region Private Methods - private string GeneratePresentationId(Presentation presentation) - { - try - { - var presentationPath = presentation.FullName; - var fileHash = GetFileHash(presentationPath); - return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}"; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"生成演示文稿ID失败: {ex}", LogHelper.LogType.Error); - return $"unknown_{DateTime.Now.Ticks}"; - } - } - - private string GetFileHash(string filePath) - { - try - { - if (string.IsNullOrEmpty(filePath)) return "unknown"; - - using (var md5 = MD5.Create()) - { - byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath)); - return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error); - return "error"; - } - } - - private string GetPresentationFolderPath() - { - return Path.Combine(AutoSaveLocation, "Auto Saved - Presentations", _currentPresentationId); - } - #endregion - - #region Dispose - public void Dispose() - { - if (!_disposed) - { - lock (_lockObject) - { - ClearAllStrokes(); - } - _disposed = true; - } - } - #endregion - } -} diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs deleted file mode 100644 index 59a2f553..00000000 --- a/Ink Canvas/Helpers/PPTManager.cs +++ /dev/null @@ -1,1642 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Timers; -using System.Windows; -using System.Windows.Threading; -using Microsoft.Office.Core; -using Microsoft.Office.Interop.PowerPoint; -using Application = System.Windows.Application; -using Timer = System.Timers.Timer; - -namespace Ink_Canvas.Helpers -{ - /// - /// PPT联动管理器 - 统一管理PPT和WPS的连接、事件处理和进程管理 - /// - public class PPTManager : IDisposable - { - #region Events - public event Action SlideShowBegin; - public event Action SlideShowNextSlide; - public event Action SlideShowEnd; - public event Action PresentationOpen; - public event Action PresentationClose; - public event Action PPTConnectionChanged; - #endregion - - #region Properties - public Microsoft.Office.Interop.PowerPoint.Application PPTApplication { get; private set; } - public Presentation CurrentPresentation { get; private set; } - public Slides CurrentSlides { get; private set; } - public Slide CurrentSlide { get; private set; } - public int SlidesCount { get; private set; } - public bool IsConnected - { - get - { - try - { - if (PPTApplication == null) return false; - if (!Marshal.IsComObject(PPTApplication)) return false; - - // 尝试访问一个简单的属性来验证连接是否有效 - var _ = PPTApplication.Name; - return true; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - // 如果COM对象已失效,返回false - if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706B5) - { - return false; - } - return false; - } - catch - { - return false; - } - } - } - public bool IsInSlideShow - { - get - { - try - { - if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false; - return PPTApplication.SlideShowWindows?.Count > 0; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - return false; - } - catch - { - return false; - } - } - } - public bool IsSupportWPS { get; set; } = false; - #endregion - - #region Private Fields - private Timer _connectionCheckTimer; - private Timer _wpsProcessCheckTimer; - private Process _wpsProcess; - private bool _hasWpsProcessId; - private DateTime _wpsProcessRecordTime = DateTime.MinValue; - private int _wpsProcessCheckCount; - private WpsWindowInfo _lastForegroundWpsWindow; - private DateTime _lastWindowCheckTime = DateTime.MinValue; - private readonly object _lockObject = new object(); - private bool _disposed = false; - #endregion - - #region Constructor & Initialization - public PPTManager() - { - InitializeConnectionTimer(); - } - - private void InitializeConnectionTimer() - { - _connectionCheckTimer = new Timer(500); - _connectionCheckTimer.Elapsed += OnConnectionCheckTimerElapsed; - _connectionCheckTimer.AutoReset = true; - } - - public void StartMonitoring() - { - if (!_disposed) - { - _connectionCheckTimer?.Start(); - LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Trace); - } - } - - public void StopMonitoring() - { - _connectionCheckTimer?.Stop(); - DisconnectFromPPT(); - LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Trace); - } - #endregion - - #region Connection Management - private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e) - { - try - { - CheckAndConnectToPPT(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"PPT连接检查失败: {ex}", LogHelper.LogType.Error); - } - } - - private void CheckAndConnectToPPT() - { - lock (_lockObject) - { - try - { - // 尝试连接到PowerPoint - var pptApp = TryConnectToPowerPoint(); - if (pptApp == null && IsSupportWPS) - { - // 如果PowerPoint连接失败且支持WPS,尝试连接WPS - pptApp = TryConnectToWPS(); - } - - if (pptApp != null && !IsConnected) - { - // 有可用的PPT/WPS应用程序且当前未连接,建立连接 - ConnectToPPT(pptApp); - } - else if (pptApp == null && IsConnected) - { - // 没有可用的PPT/WPS应用程序但当前显示已连接,断开连接 - DisconnectFromPPT(); - } - else if (pptApp == null && PPTApplication != null) - { - // PPT/WPS应用程序不可用但PPTApplication对象仍存在,清理无效连接 - DisconnectFromPPT(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"PPT连接检查异常: {ex}", LogHelper.LogType.Error); - if (PPTApplication != null) - { - DisconnectFromPPT(); - } - } - } - } - - private Microsoft.Office.Interop.PowerPoint.Application TryConnectToPowerPoint() - { - try - { - var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); - - // 验证COM对象是否有效 - if (pptApp != null && Marshal.IsComObject(pptApp)) - { - // 尝试访问一个简单的属性来验证连接 - var _ = pptApp.Name; - return pptApp; - } - return null; - } - catch (COMException ex) - { - var hr = (uint)ex.HResult; - // 忽略常见的PowerPoint连接错误: - // 0x800401E3: 操作无法使用 - // 0x80004005: 未指定错误 - // 0x800706B5: RPC服务器不可用 - // 0x8001010E: 应用程序调用一个已为另一线程整理的接口 - // 0x800401F3: 无效的类字符串(PowerPoint未安装或COM组件未注册) - if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3) - { - LogHelper.WriteLogToFile($"连接PowerPoint失败: {ex}", LogHelper.LogType.Warning); - } - return null; - } - catch (InvalidCastException) - { - // COM对象类型转换失败 - return null; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"连接PowerPoint时发生意外错误: {ex}", LogHelper.LogType.Warning); - return null; - } - } - - private Microsoft.Office.Interop.PowerPoint.Application TryConnectToWPS() - { - try - { - var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application"); - - // 验证COM对象是否有效 - if (wpsApp != null && Marshal.IsComObject(wpsApp)) - { - // 尝试访问一个简单的属性来验证连接 - var _ = wpsApp.Name; - return wpsApp; - } - return null; - } - catch (COMException ex) - { - var hr = (uint)ex.HResult; - // 忽略常见的WPS连接错误: - // 0x800401E3: 操作无法使用 - // 0x80004005: 未指定错误 - // 0x800706B5: RPC服务器不可用 - // 0x8001010E: 应用程序调用一个已为另一线程整理的接口 - // 0x800401F3: 无效的类字符串(WPS未安装或COM组件未注册) - if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3) - { - LogHelper.WriteLogToFile($"连接WPS失败: {ex}", LogHelper.LogType.Warning); - } - return null; - } - catch (InvalidCastException) - { - // COM对象类型转换失败 - return null; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"连接WPS时发生意外错误: {ex}", LogHelper.LogType.Warning); - return null; - } - } - - private void ConnectToPPT(Microsoft.Office.Interop.PowerPoint.Application pptApp) - { - try - { - PPTApplication = pptApp; - - // 在主线程中注册事件,确保COM对象在正确的线程中 - Application.Current?.Dispatcher?.Invoke(() => - { - try - { - PPTApplication.PresentationOpen += OnPresentationOpen; - PPTApplication.PresentationClose += OnPresentationClose; - PPTApplication.SlideShowBegin += OnSlideShowBegin; - PPTApplication.SlideShowNextSlide += OnSlideShowNextSlide; - PPTApplication.SlideShowEnd += OnSlideShowEnd; - - LogHelper.WriteLogToFile("PPT事件注册成功", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"PPT事件注册失败: {ex}", LogHelper.LogType.Error); - throw; // 重新抛出异常,让外层处理 - } - }, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(2)); - - // 获取当前演示文稿信息 - UpdateCurrentPresentationInfo(); - - // 停止连接检查定时器 - _connectionCheckTimer?.Stop(); - - // 触发连接成功事件 - PPTConnectionChanged?.Invoke(true); - - LogHelper.WriteLogToFile("成功连接到PPT应用程序", LogHelper.LogType.Event); - - // 如果已经在放映状态,立即触发放映开始事件 - if (IsInSlideShow) - { - OnSlideShowBegin(PPTApplication.SlideShowWindows[1]); - } - else if (CurrentPresentation != null) - { - OnPresentationOpen(CurrentPresentation); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"连接PPT应用程序失败: {ex}", LogHelper.LogType.Error); - PPTApplication = null; - } - } - - private void DisconnectFromPPT() - { - try - { - if (PPTApplication != null) - { - // 取消事件注册 - 使用更安全的方式 - try - { - // 检查COM对象是否仍然有效 - if (Marshal.IsComObject(PPTApplication)) - { - // 尝试在主线程中取消事件注册 - Application.Current?.Dispatcher?.Invoke(() => - { - try - { - PPTApplication.PresentationOpen -= OnPresentationOpen; - PPTApplication.PresentationClose -= OnPresentationClose; - PPTApplication.SlideShowBegin -= OnSlideShowBegin; - PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide; - PPTApplication.SlideShowEnd -= OnSlideShowEnd; - } - catch (COMException comEx) - { - // COM对象已经被释放或在错误的线程中,忽略这些错误 - var hr = (uint)comEx.HResult; - if (hr != 0x8001010E && hr != 0x80004005 && hr != 0x800706B5) - { - LogHelper.WriteLogToFile($"取消PPT事件注册COM异常: {comEx}", LogHelper.LogType.Warning); - } - } - catch (InvalidCastException) - { - // COM对象类型转换失败,通常是因为对象已经被释放 - LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace); - } - }, DispatcherPriority.Normal, System.Threading.CancellationToken.None, TimeSpan.FromSeconds(1)); - } - } - catch (Exception ex) - { - // 记录但不抛出异常,确保清理过程能够继续 - LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.GetType().Name} - {ex.Message}", LogHelper.LogType.Warning); - } - - // 清理引用 - PPTApplication = null; - CurrentPresentation = null; - CurrentSlides = null; - CurrentSlide = null; - SlidesCount = 0; - - // 重新启动连接检查定时器 - _connectionCheckTimer?.Start(); - - // 触发连接断开事件 - PPTConnectionChanged?.Invoke(false); - - LogHelper.WriteLogToFile("已断开PPT连接", LogHelper.LogType.Event); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"断开PPT连接失败: {ex}", LogHelper.LogType.Error); - } - } - - private void UpdateCurrentPresentationInfo() - { - try - { - if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) - { - // 检查是否有活动的演示文稿 - try - { - var activePresentation = PPTApplication.ActivePresentation; - if (activePresentation != null) - { - CurrentPresentation = activePresentation; - CurrentSlides = CurrentPresentation.Slides; - SlidesCount = CurrentSlides.Count; - - // 获取当前幻灯片 - try - { - if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0) - { - CurrentSlide = PPTApplication.SlideShowWindows[1].View.Slide; - } - else if (PPTApplication.ActiveWindow?.Selection?.SlideRange?.SlideNumber > 0) - { - CurrentSlide = CurrentSlides[PPTApplication.ActiveWindow.Selection.SlideRange.SlideNumber]; - } - else if (SlidesCount > 0) - { - // 如果获取失败,使用第一张幻灯片 - CurrentSlide = CurrentSlides[1]; - } - } - catch (COMException comEx) - { - // COM异常,尝试使用第一张幻灯片 - var hr = (uint)comEx.HResult; - if (hr != 0x8001010E && hr != 0x80004005) - { - LogHelper.WriteLogToFile($"获取当前幻灯片失败: {comEx.Message}", LogHelper.LogType.Warning); - } - - if (SlidesCount > 0) - { - CurrentSlide = CurrentSlides[1]; - } - } - } - else - { - // 没有活动演示文稿,清理状态 - CurrentPresentation = null; - CurrentSlides = null; - CurrentSlide = null; - SlidesCount = 0; - } - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // 常见的COM错误,可能是没有活动演示文稿 - CurrentPresentation = null; - CurrentSlides = null; - CurrentSlide = null; - SlidesCount = 0; - } - else - { - LogHelper.WriteLogToFile($"访问活动演示文稿失败: {comEx}", LogHelper.LogType.Warning); - } - } - } - else - { - // PPT应用程序无效,清理状态 - CurrentPresentation = null; - CurrentSlides = null; - CurrentSlide = null; - SlidesCount = 0; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新演示文稿信息失败: {ex}", LogHelper.LogType.Error); - // 发生异常时清理状态 - CurrentPresentation = null; - CurrentSlides = null; - CurrentSlide = null; - SlidesCount = 0; - } - } - #endregion - - #region Event Handlers - private void OnPresentationOpen(Presentation pres) - { - try - { - UpdateCurrentPresentationInfo(); - PresentationOpen?.Invoke(pres); - LogHelper.WriteLogToFile($"演示文稿已打开: {pres?.Name}", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnPresentationClose(Presentation pres) - { - try - { - PresentationClose?.Invoke(pres); - LogHelper.WriteLogToFile($"演示文稿已关闭: {pres?.Name}", LogHelper.LogType.Event); - - // 重新启动连接检查 - _connectionCheckTimer?.Start(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnSlideShowBegin(SlideShowWindow wn) - { - try - { - UpdateCurrentPresentationInfo(); - SlideShowBegin?.Invoke(wn); - LogHelper.WriteLogToFile("幻灯片放映开始", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnSlideShowNextSlide(SlideShowWindow wn) - { - try - { - UpdateCurrentPresentationInfo(); - SlideShowNextSlide?.Invoke(wn); - LogHelper.WriteLogToFile($"幻灯片切换到第{wn?.View?.CurrentShowPosition}页", LogHelper.LogType.Trace); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnSlideShowEnd(Presentation pres) - { - try - { - // 记录WPS进程用于后续管理 - if (IsSupportWPS && PPTApplication != null) - { - RecordWpsProcessForManagement(); - } - - SlideShowEnd?.Invoke(pres); - LogHelper.WriteLogToFile("幻灯片放映结束", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error); - } - } - #endregion - - #region Public Methods - public bool TryNavigateToSlide(int slideNumber) - { - try - { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; - if (!Marshal.IsComObject(PPTApplication)) return false; - - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow?.View != null) - { - slideShowWindow.View.GotoSlide(slideNumber); - return true; - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"跳转到幻灯片{slideNumber}失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - public bool TryNavigateNext() - { - try - { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; - if (!Marshal.IsComObject(PPTApplication)) return false; - - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow?.View != null) - { - slideShowWindow.Activate(); - slideShowWindow.View.Next(); - return true; - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"切换到下一页失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"切换到下一页失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - public bool TryNavigatePrevious() - { - try - { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; - if (!Marshal.IsComObject(PPTApplication)) return false; - - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow?.View != null) - { - slideShowWindow.Activate(); - slideShowWindow.View.Previous(); - return true; - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"切换到上一页失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"切换到上一页失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - public bool TryEndSlideShow() - { - try - { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; - if (!Marshal.IsComObject(PPTApplication)) return false; - - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow?.View != null) - { - slideShowWindow.View.Exit(); - return true; - } - return false; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"结束幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"结束幻灯片放映失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - public bool TryStartSlideShow() - { - try - { - if (!IsConnected || CurrentPresentation == null || PPTApplication == null) return false; - if (!Marshal.IsComObject(PPTApplication) || !Marshal.IsComObject(CurrentPresentation)) return false; - - CurrentPresentation.SlideShowSettings.Run(); - return true; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"开始幻灯片放映失败: {comEx.Message}", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"开始幻灯片放映失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - public int GetCurrentSlideNumber() - { - try - { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return 0; - if (!Marshal.IsComObject(PPTApplication)) return 0; - - return PPTApplication.SlideShowWindows[1]?.View?.CurrentShowPosition ?? 0; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {comEx.Message}", LogHelper.LogType.Warning); - return 0; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {ex}", LogHelper.LogType.Error); - return 0; - } - } - - public string GetPresentationName() - { - try - { - if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return ""; - - return CurrentPresentation.Name ?? ""; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"获取演示文稿名称失败: {comEx.Message}", LogHelper.LogType.Warning); - return ""; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取演示文稿名称失败: {ex}", LogHelper.LogType.Error); - return ""; - } - } - - public string GetPresentationPath() - { - try - { - if (CurrentPresentation == null || !Marshal.IsComObject(CurrentPresentation)) return ""; - - return CurrentPresentation.FullName ?? ""; - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"获取演示文稿路径失败: {comEx.Message}", LogHelper.LogType.Warning); - return ""; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取演示文稿路径失败: {ex}", LogHelper.LogType.Error); - return ""; - } - } - - public bool TryShowSlideNavigation() - { - try - { - LogHelper.WriteLogToFile($"尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace); - - if (!IsConnected || !IsInSlideShow || PPTApplication == null) - { - LogHelper.WriteLogToFile("PPT未连接或未在放映状态", LogHelper.LogType.Warning); - return false; - } - - if (!Marshal.IsComObject(PPTApplication)) - { - LogHelper.WriteLogToFile("PPT应用程序COM对象无效", LogHelper.LogType.Warning); - return false; - } - - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow == null) - { - LogHelper.WriteLogToFile("幻灯片放映窗口为空", LogHelper.LogType.Warning); - return false; - } - - // 检查是否为WPS,WPS可能不支持SlideNavigation - try - { - if (slideShowWindow.SlideNavigation != null) - { - slideShowWindow.SlideNavigation.Visible = true; - LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event); - return true; - } - else - { - LogHelper.WriteLogToFile("SlideNavigation对象为空,可能是WPS不支持此功能", LogHelper.LogType.Warning); - return false; - } - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - // 0x80020006: 未知名称 - WPS可能不支持SlideNavigation - if (hr == 0x80020006) - { - LogHelper.WriteLogToFile("WPS不支持SlideNavigation功能", LogHelper.LogType.Warning); - return false; - } - throw; // 重新抛出其他COM异常 - } - } - catch (COMException comEx) - { - var hr = (uint)comEx.HResult; - if (hr == 0x8001010E || hr == 0x80004005) - { - // COM对象已失效,触发断开连接 - DisconnectFromPPT(); - } - LogHelper.WriteLogToFile($"显示幻灯片导航COM异常: {comEx.Message} (HRESULT: 0x{hr:X8})", LogHelper.LogType.Error); - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"显示幻灯片导航失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - #endregion - - #region WPS Process Management - private void RecordWpsProcessForManagement() - { - if (!IsSupportWPS || PPTApplication == null) return; - - try - { - Process wpsProcess = null; - - // 方法1:通过应用程序路径检测 - if (PPTApplication.Path.Contains("Kingsoft\\WPS Office\\") || - PPTApplication.Path.Contains("WPS Office\\")) - { - uint processId; - GetWindowThreadProcessId((IntPtr)PPTApplication.HWND, out processId); - wpsProcess = Process.GetProcessById((int)processId); - } - - // 方法2:通过前台窗口检测 - if (wpsProcess == null) - { - var foregroundWpsWindow = GetForegroundWpsWindow(); - if (foregroundWpsWindow != null) - { - wpsProcess = Process.GetProcessById((int)foregroundWpsWindow.ProcessId); - } - } - - // 方法3:通过进程名检测 - if (wpsProcess == null) - { - var wpsProcesses = GetWpsProcesses(); - if (wpsProcesses.Count > 0) - { - wpsProcess = wpsProcesses.First(); - } - } - - if (wpsProcess != null) - { - _wpsProcess = wpsProcess; - _hasWpsProcessId = true; - _wpsProcessRecordTime = DateTime.Now; - _wpsProcessCheckCount = 0; - LogHelper.WriteLogToFile($"成功记录 WPS 进程 ID: {wpsProcess.Id}", LogHelper.LogType.Trace); - - StartWpsProcessCheckTimer(); - } - else - { - LogHelper.WriteLogToFile("未能检测到WPS进程", LogHelper.LogType.Warning); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"记录WPS进程失败: {ex}", LogHelper.LogType.Error); - } - } - - private void StartWpsProcessCheckTimer() - { - if (!IsSupportWPS) return; - - if (_wpsProcessCheckTimer != null) - { - _wpsProcessCheckTimer.Stop(); - _wpsProcessCheckTimer.Dispose(); - } - - // 优化:增加检查间隔到2秒,减少性能开销 - _wpsProcessCheckTimer = new Timer(2000); - _wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed; - _wpsProcessCheckTimer.Start(); - LogHelper.WriteLogToFile("启动 WPS 进程检测定时器", LogHelper.LogType.Trace); - } - - private void OnWpsProcessCheckTimerElapsed(object sender, ElapsedEventArgs e) - { - if (!IsSupportWPS) - { - StopWpsProcessCheckTimer(); - return; - } - - try - { - if (_wpsProcess == null || !_hasWpsProcessId) - { - StopWpsProcessCheckTimer(); - return; - } - - _wpsProcess.Refresh(); - _wpsProcessCheckCount++; - - if (_wpsProcess.HasExited) - { - LogHelper.WriteLogToFile("WPS 进程已正常关闭", LogHelper.LogType.Trace); - StopWpsProcessCheckTimer(); - return; - } - - - // 检查前台WPS窗口是否存在(优化版) - bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized(); - - if (isForegroundWpsWindowActive) - { - if (_wpsProcessCheckCount % 5 == 0) // 每10秒记录一次日志 - { - LogHelper.WriteLogToFile($"WPS窗口仍然活跃,继续监控(已检查{_wpsProcessCheckCount}次)", LogHelper.LogType.Trace); - } - return; - } - - // 优化:多重验证确保准确性 - if (!PerformMultipleWpsWindowChecks()) - { - LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace); - return; - } - - // 前台窗口已消失,准备结束WPS进程 - LogHelper.WriteLogToFile("多重验证确认WPS窗口已消失,准备结束WPS进程", LogHelper.LogType.Event); - - // 安全结束WPS进程 - SafeTerminateWpsProcess(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"WPS 进程检测失败: {ex}", LogHelper.LogType.Error); - StopWpsProcessCheckTimer(); - } - } - - /// - /// 优化版的前台WPS窗口检测,减少性能开销 - /// - private bool IsForegroundWpsWindowStillActiveOptimized() - { - try - { - // 快速检查:直接检查前台窗口 - var foregroundWindow = GetForegroundWindow(); - if (foregroundWindow == IntPtr.Zero) return false; - - // 获取前台窗口的进程ID - uint processId; - GetWindowThreadProcessId(foregroundWindow, out processId); - - // 如果前台窗口就是我们监控的WPS进程,则认为仍然活跃 - if (processId == _wpsProcess?.Id) - { - return true; - } - - // 检查是否为WPS相关窗口 - var windowInfo = GetWindowInfo(foregroundWindow); - return IsWpsWindow(windowInfo); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"优化版WPS窗口检测失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 多重验证WPS窗口状态,确保查杀准确性 - /// - private bool PerformMultipleWpsWindowChecks() - { - try - { - // 第一重验证:等待1秒后再次检查 - Thread.Sleep(1000); - if (IsForegroundWpsWindowStillActiveOptimized()) - { - LogHelper.WriteLogToFile("第一重验证:WPS窗口仍然存在", LogHelper.LogType.Trace); - return false; - } - - // 第二重验证:检查所有WPS进程的窗口 - var wpsProcesses = GetWpsProcesses(); - foreach (var process in wpsProcesses) - { - if (process.Id == _wpsProcess?.Id) continue; // 跳过当前监控的进程 - - var windows = GetWpsWindowsByProcess(process.Id); - if (windows.Any(w => w.IsVisible && !w.IsMinimized)) - { - LogHelper.WriteLogToFile($"第二重验证:发现其他WPS进程{process.Id}有活跃窗口", LogHelper.LogType.Trace); - return false; - } - } - - // 第三重验证:检查任务栏中的WPS窗口 - if (HasWpsWindowInTaskbar()) - { - LogHelper.WriteLogToFile("第三重验证:任务栏中仍有WPS窗口", LogHelper.LogType.Trace); - return false; - } - - LogHelper.WriteLogToFile("多重验证完成:确认WPS窗口已全部消失", LogHelper.LogType.Event); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"多重验证失败: {ex}", LogHelper.LogType.Error); - return false; // 出错时保守处理,不进行查杀 - } - } - - /// - /// 检查任务栏中是否有WPS窗口 - /// - private bool HasWpsWindowInTaskbar() - { - try - { - var allWindows = new List(); - - EnumWindows((hWnd, lParam) => - { - try - { - if (IsWindow(hWnd) && IsWindowVisible(hWnd)) - { - var windowInfo = GetWindowInfo(hWnd); - if (IsWpsWindow(windowInfo) && !string.IsNullOrEmpty(windowInfo.Title)) - { - allWindows.Add(windowInfo); - } - } - } - catch { } - return true; - }, IntPtr.Zero); - - return allWindows.Count > 0; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查任务栏WPS窗口失败: {ex}", LogHelper.LogType.Error); - return true; // 出错时保守处理,认为仍有窗口 - } - } - - /// - /// 安全地结束WPS进程 - 通过释放PPTCOM对象 - /// - private void SafeTerminateWpsProcess() - { - try - { - if (_wpsProcess == null || _wpsProcess.HasExited) - { - LogHelper.WriteLogToFile("WPS进程已经结束,无需查杀", LogHelper.LogType.Trace); - StopWpsProcessCheckTimer(); - return; - } - - LogHelper.WriteLogToFile($"开始通过释放PPTCOM对象安全结束WPS进程 (PID: {_wpsProcess.Id})", LogHelper.LogType.Event); - - // 第一步:释放 pptActWindow 对象(SlideShowWindow) - SlideShowWindow pptActWindow = null; - try - { - if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) - { - if (PPTApplication.SlideShowWindows?.Count > 0) - { - pptActWindow = PPTApplication.SlideShowWindows[1]; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取SlideShowWindow对象时发生异常: {ex}", LogHelper.LogType.Warning); - } - - if (pptActWindow != null) - { - Marshal.ReleaseComObject(pptActWindow); - pptActWindow = null; - LogHelper.WriteLogToFile("已释放pptActWindow对象", LogHelper.LogType.Trace); - } - - // 第二步:释放 pptActDoc 对象(CurrentPresentation) - Presentation pptActDoc = CurrentPresentation; - if (pptActDoc != null) - { - Marshal.ReleaseComObject(pptActDoc); - pptActDoc = null; - CurrentPresentation = null; - LogHelper.WriteLogToFile("已释放pptActDoc对象", LogHelper.LogType.Trace); - } - - // 第三步:释放 pptApp 对象(PPTApplication) - Microsoft.Office.Interop.PowerPoint.Application pptApp = PPTApplication; - if (pptApp != null) - { - Marshal.ReleaseComObject(pptApp); - pptApp = null; - PPTApplication = null; - LogHelper.WriteLogToFile("已释放pptApp对象", LogHelper.LogType.Trace); - } - - // 第四步:强制垃圾回收及等待终结器执行 - LogHelper.WriteLogToFile("执行强制垃圾回收", LogHelper.LogType.Trace); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - // 等待一段时间让COM对象完全释放 - Thread.Sleep(1000); - - // 检查进程是否已经结束 - try - { - _wpsProcess.Refresh(); - if (_wpsProcess.HasExited) - { - LogHelper.WriteLogToFile("WPS进程已通过COM对象释放成功结束", LogHelper.LogType.Event); - StopWpsProcessCheckTimer(); - return; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查WPS进程状态失败: {ex}", LogHelper.LogType.Warning); - } - - // 备用方案:如果COM对象释放后进程仍未结束,尝试关闭 - try - { - LogHelper.WriteLogToFile("COM对象释放后进程仍在运行,尝试关闭", LogHelper.LogType.Warning); - _wpsProcess.CloseMainWindow(); - if (_wpsProcess.WaitForExit(3000)) // 等待3秒 - { - LogHelper.WriteLogToFile("WPS进程已关闭", LogHelper.LogType.Event); - StopWpsProcessCheckTimer(); - return; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"关闭WPS进程失败: {ex}", LogHelper.LogType.Warning); - } - - // 最后备用方案:强制结束进程 - try - { - if (!_wpsProcess.HasExited) - { - LogHelper.WriteLogToFile("所有方法都失败,强制结束WPS进程", LogHelper.LogType.Warning); - _wpsProcess.Kill(); - LogHelper.WriteLogToFile("WPS进程已强制结束", LogHelper.LogType.Event); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"强制结束WPS进程失败: {ex}", LogHelper.LogType.Error); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"安全结束WPS进程时发生异常: {ex}", LogHelper.LogType.Error); - } - finally - { - // 确保清理状态 - if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide)) - { - try { Marshal.ReleaseComObject(CurrentSlide); } catch { } - } - if (CurrentSlides != null && Marshal.IsComObject(CurrentSlides)) - { - try { Marshal.ReleaseComObject(CurrentSlides); } catch { } - } - if (CurrentPresentation != null && Marshal.IsComObject(CurrentPresentation)) - { - try { Marshal.ReleaseComObject(CurrentPresentation); } catch { } - } - if (PPTApplication != null && Marshal.IsComObject(PPTApplication)) - { - try { Marshal.ReleaseComObject(PPTApplication); } catch { } - } - - CurrentSlide = null; - CurrentSlides = null; - CurrentPresentation = null; - PPTApplication = null; - SlidesCount = 0; - StopWpsProcessCheckTimer(); - - // 重新启动连接检查定时器,以便能够检测新的WPS实例 - _connectionCheckTimer?.Start(); - - // 触发连接断开事件 - PPTConnectionChanged?.Invoke(false); - - LogHelper.WriteLogToFile("WPS进程结束后已清理所有COM对象并重启连接检查", LogHelper.LogType.Event); - } - } - - - - private void StopWpsProcessCheckTimer() - { - if (_wpsProcessCheckTimer != null) - { - _wpsProcessCheckTimer.Stop(); - _wpsProcessCheckTimer.Dispose(); - _wpsProcessCheckTimer = null; - } - - _wpsProcess = null; - _hasWpsProcessId = false; - _wpsProcessRecordTime = DateTime.MinValue; - _wpsProcessCheckCount = 0; - _lastForegroundWpsWindow = null; - _lastWindowCheckTime = DateTime.MinValue; - LogHelper.WriteLogToFile("停止 WPS 进程检测定时器", LogHelper.LogType.Trace); - } - #endregion - - #region WPS Window Detection - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [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")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsWindowVisible(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool IsIconic(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool IsZoomed(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern IntPtr GetForegroundWindow(); - - [DllImport("user32.dll")] - private static extern bool IsWindow(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); - - [StructLayout(LayoutKind.Sequential)] - public struct RECT - { - public int Left; - public int Top; - public int Right; - public int Bottom; - - public int Width => Right - Left; - public int Height => Bottom - Top; - } - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); - - private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); - - private class WpsWindowInfo - { - public IntPtr Handle { get; set; } - public string Title { get; set; } - public string ClassName { get; set; } - public bool IsVisible { get; set; } - public bool IsMinimized { get; set; } - public bool IsMaximized { get; set; } - public RECT Rect { get; set; } - public uint ProcessId { get; set; } - public string ProcessName { get; set; } - } - - private WpsWindowInfo GetForegroundWpsWindow() - { - try - { - var foregroundHwnd = GetForegroundWindow(); - if (foregroundHwnd != IntPtr.Zero && IsWindow(foregroundHwnd)) - { - var windowInfo = GetWindowInfo(foregroundHwnd); - if (IsWpsWindow(windowInfo)) - { - return windowInfo; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取前台WPS窗口失败: {ex}", LogHelper.LogType.Error); - } - return null; - } - - private WpsWindowInfo GetWindowInfo(IntPtr hWnd) - { - var windowInfo = new WpsWindowInfo - { - Handle = hWnd, - IsVisible = IsWindowVisible(hWnd), - IsMinimized = IsIconic(hWnd), - IsMaximized = IsZoomed(hWnd) - }; - - // 获取窗口标题 - var windowTitle = new StringBuilder(256); - GetWindowText(hWnd, windowTitle, 256); - windowInfo.Title = windowTitle.ToString().Trim(); - - // 获取窗口类名 - var className = new StringBuilder(256); - GetClassName(hWnd, className, 256); - windowInfo.ClassName = className.ToString().Trim(); - - // 获取窗口位置 - GetWindowRect(hWnd, out RECT rect); - windowInfo.Rect = rect; - - // 获取进程ID - uint processId; - GetWindowThreadProcessId(hWnd, out processId); - windowInfo.ProcessId = processId; - - // 获取进程名 - windowInfo.ProcessName = ""; - try - { - var proc = Process.GetProcessById((int)processId); - windowInfo.ProcessName = proc.ProcessName.ToLower(); - } - catch { } - - return windowInfo; - } - - private bool IsWpsWindow(WpsWindowInfo windowInfo) - { - if (string.IsNullOrEmpty(windowInfo.Title) && string.IsNullOrEmpty(windowInfo.ClassName)) - return false; - - var title = windowInfo.Title.ToLower(); - var className = windowInfo.ClassName.ToLower(); - var processName = windowInfo.ProcessName ?? ""; - - // WPS相关关键词 - var wpsKeywords = new[] { "wps", "wpp", "kingsoft", "金山", "wps演示", "wps presentation", "wps office", "kingsoft office" }; - // 微软Office相关进程名 - var msOfficeProcess = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "microsoftoffice", "office" }; - - // 只要进程名是微软Office,直接排除 - if (msOfficeProcess.Any(keyword => processName.Contains(keyword))) - return false; - - // 只要进程名是WPS/WPP/Kingsoft,直接通过 - if (wpsKeywords.Any(keyword => processName.Contains(keyword))) - return true; - - // 标题或类名包含WPS相关关键词 - bool hasWpsTitle = wpsKeywords.Any(keyword => title.Contains(keyword)); - bool hasWpsClass = wpsKeywords.Any(keyword => className.Contains(keyword)); - bool isWpsClass = className.Contains("wps") || className.Contains("kingsoft") || className.Contains("wpp"); - bool hasValidSize = (windowInfo.Rect.Right - windowInfo.Rect.Left) > 0 && (windowInfo.Rect.Bottom - windowInfo.Rect.Top) > 0; - - return (hasWpsTitle || hasWpsClass || isWpsClass) && hasValidSize; - } - - private List GetWpsProcesses() - { - var wpsProcesses = new List(); - try - { - var allProcesses = Process.GetProcesses(); - foreach (var process in allProcesses) - { - try - { - var pname = process.ProcessName.ToLower(); - - // 精确的WPS进程名匹配,避免误杀 - var exactWpsNames = new[] { "wps", "wpp", "et", "wpspdf", "wpsoffice" }; - var microsoftOfficeNames = new[] { "powerpnt", "excel", "word", "onenote", "outlook", "winword", "msaccess" }; - - // 排除微软Office进程 - if (microsoftOfficeNames.Any(name => pname.Contains(name))) - { - continue; - } - - // 精确匹配WPS进程名 - bool isWpsProcess = exactWpsNames.Any(name => pname.Equals(name) || pname.StartsWith(name + ".")); - - // 额外验证:检查进程路径 - if (isWpsProcess) - { - try - { - var processPath = process.MainModule?.FileName?.ToLower() ?? ""; - if (processPath.Contains("kingsoft") || processPath.Contains("wps office")) - { - wpsProcesses.Add(process); - LogHelper.WriteLogToFile($"检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); - } - } - catch - { - // 无法访问进程路径时,基于进程名判断 - if (exactWpsNames.Contains(pname)) - { - wpsProcesses.Add(process); - LogHelper.WriteLogToFile($"基于进程名检测到WPS进程: {process.ProcessName} (PID: {process.Id})", LogHelper.LogType.Trace); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查进程{process.ProcessName}失败: {ex}", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取WPS进程失败: {ex}", LogHelper.LogType.Error); - } - - LogHelper.WriteLogToFile($"共检测到{wpsProcesses.Count}个WPS进程", LogHelper.LogType.Trace); - return wpsProcesses; - } - - private bool IsForegroundWpsWindowStillActive() - { - try - { - var currentTime = DateTime.Now; - var currentForegroundWindow = GetForegroundWpsWindow(); - - // 检查窗口状态是否发生变化 - if (_lastForegroundWpsWindow != null && currentForegroundWindow != null) - { - if (_lastForegroundWpsWindow.Handle != currentForegroundWindow.Handle || - _lastForegroundWpsWindow.Title != currentForegroundWindow.Title) - { - LogHelper.WriteLogToFile($"前台WPS窗口发生变化: {_lastForegroundWpsWindow.Title} -> {currentForegroundWindow.Title}", LogHelper.LogType.Trace); - } - } - else if (_lastForegroundWpsWindow == null && currentForegroundWindow != null) - { - LogHelper.WriteLogToFile($"检测到新的前台WPS窗口: {currentForegroundWindow.Title}", LogHelper.LogType.Trace); - } - else if (_lastForegroundWpsWindow != null && currentForegroundWindow == null) - { - LogHelper.WriteLogToFile($"前台WPS窗口已消失: {_lastForegroundWpsWindow.Title}", LogHelper.LogType.Trace); - } - - // 更新记录 - _lastForegroundWpsWindow = currentForegroundWindow; - _lastWindowCheckTime = currentTime; - - if (currentForegroundWindow != null) - { - if (IsWindow(currentForegroundWindow.Handle) && IsWindowVisible(currentForegroundWindow.Handle)) - { - return true; - } - } - - // 检查所有WPS进程的活跃窗口 - var wpsProcesses = GetWpsProcesses(); - foreach (var process in wpsProcesses) - { - var windows = GetWpsWindowsByProcess(process.Id); - if (windows.Any(w => w.IsVisible && !w.IsMinimized && w.Handle == GetForegroundWindow())) - { - return true; - } - } - - return false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查前台WPS窗口状态失败: {ex}", LogHelper.LogType.Error); - return false; - } - } - - private List GetWpsWindowsByProcess(int processId) - { - var wpsWindows = new List(); - - try - { - EnumWindows((hWnd, lParam) => - { - try - { - if (!IsWindow(hWnd)) return true; - - uint windowProcessId; - GetWindowThreadProcessId(hWnd, out windowProcessId); - - if ((int)windowProcessId == processId) - { - var windowInfo = GetWindowInfo(hWnd); - if (IsWpsWindow(windowInfo)) - { - wpsWindows.Add(windowInfo); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"枚举窗口时出错: {ex}", LogHelper.LogType.Error); - } - return true; - }, IntPtr.Zero); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取WPS窗口失败: {ex}", LogHelper.LogType.Error); - } - - return wpsWindows; - } - #endregion - - #region Dispose - public void Dispose() - { - if (!_disposed) - { - StopMonitoring(); - StopWpsProcessCheckTimer(); - - _connectionCheckTimer?.Dispose(); - _wpsProcessCheckTimer?.Dispose(); - - _disposed = true; - } - } - #endregion - } -} diff --git a/Ink Canvas/Helpers/PPTUIManager.cs b/Ink Canvas/Helpers/PPTUIManager.cs deleted file mode 100644 index 3bd1713d..00000000 --- a/Ink Canvas/Helpers/PPTUIManager.cs +++ /dev/null @@ -1,435 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Threading; - -namespace Ink_Canvas.Helpers -{ - /// - /// PPT UI管理器 - 统一管理PPT相关的UI更新和样式设置 - /// - public class PPTUIManager - { - #region Properties - public bool ShowPPTButton { get; set; } = true; - public int PPTButtonsDisplayOption { get; set; } = 2222; - public int PPTSButtonsOption { get; set; } = 221; - public int PPTBButtonsOption { get; set; } = 121; - public int PPTLSButtonPosition { get; set; } = 0; - public int PPTRSButtonPosition { get; set; } = 0; - public bool EnablePPTButtonPageClickable { get; set; } = true; - #endregion - - #region Private Fields - private readonly MainWindow _mainWindow; - private readonly Dispatcher _dispatcher; - #endregion - - #region Constructor - public PPTUIManager(MainWindow mainWindow) - { - _mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow)); - _dispatcher = _mainWindow.Dispatcher; - } - #endregion - - #region Public Methods - /// - /// 更新PPT连接状态UI - /// - public void UpdateConnectionStatus(bool isConnected) - { - _dispatcher.InvokeAsync(() => - { - try - { - if (isConnected) - { - _mainWindow.StackPanelPPTControls.Visibility = Visibility.Visible; - _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible; - } - else - { - _mainWindow.StackPanelPPTControls.Visibility = Visibility.Collapsed; - _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed; - _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; - HideAllNavigationPanels(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新PPT连接状态UI失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 更新幻灯片放映状态UI - /// - public void UpdateSlideShowStatus(bool isInSlideShow, int currentSlide = 0, int totalSlides = 0) - { - _dispatcher.InvokeAsync(() => - { - try - { - if (isInSlideShow) - { - _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed; - _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible; - - if (currentSlide > 0 && totalSlides > 0) - { - _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); - _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; - } - - UpdateNavigationPanelsVisibility(); - UpdateNavigationButtonStyles(); - } - else - { - _mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible; - _mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed; - HideAllNavigationPanels(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新幻灯片放映状态UI失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 更新当前页码显示 - /// - public void UpdateCurrentSlideNumber(int currentSlide, int totalSlides) - { - _dispatcher.InvokeAsync(() => - { - try - { - _mainWindow.PPTBtnPageNow.Text = currentSlide.ToString(); - _mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}"; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新页码显示失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 更新导航面板显示状态 - /// - public void UpdateNavigationPanelsVisibility() - { - _dispatcher.InvokeAsync(() => - { - try - { - // 检查是否应该显示PPT按钮 - bool shouldShowButtons = ShowPPTButton && _mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible; - - if (!shouldShowButtons) - { - HideAllNavigationPanels(); - return; - } - - // 设置侧边按钮位置 - _mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2); - _mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2); - - // 根据显示选项设置面板可见性 - var displayOption = PPTButtonsDisplayOption.ToString(); - if (displayOption.Length >= 4) - { - var options = displayOption.ToCharArray(); - - // 左下角面板 - if (options[0] == '2') - AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftBottomPanelForPPTNavigation); - else - _mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - - // 右下角面板 - if (options[1] == '2') - AnimationsHelper.ShowWithFadeIn(_mainWindow.RightBottomPanelForPPTNavigation); - else - _mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - - // 左侧面板 - if (options[2] == '2') - AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftSidePanelForPPTNavigation); - else - _mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - - // 右侧面板 - if (options[3] == '2') - AnimationsHelper.ShowWithFadeIn(_mainWindow.RightSidePanelForPPTNavigation); - else - _mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新导航面板显示状态失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 更新导航按钮样式 - /// - public void UpdateNavigationButtonStyles() - { - _dispatcher.InvokeAsync(() => - { - try - { - UpdateSideButtonStyles(); - UpdateBottomButtonStyles(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新导航按钮样式失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 隐藏所有导航面板 - /// - public void HideAllNavigationPanels() - { - _dispatcher.InvokeAsync(() => - { - try - { - _mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - _mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed; - _mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - _mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"隐藏导航面板失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 显示/隐藏侧边栏退出按钮 - /// - public void UpdateSidebarExitButtons(bool show) - { - _dispatcher.InvokeAsync(() => - { - try - { - var visibility = show ? Visibility.Visible : Visibility.Collapsed; - - if (_mainWindow.BtnExitPptFromSidebarLeft != null) - _mainWindow.BtnExitPptFromSidebarLeft.Visibility = visibility; - - if (_mainWindow.BtnExitPptFromSidebarRight != null) - _mainWindow.BtnExitPptFromSidebarRight.Visibility = visibility; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新侧边栏退出按钮失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 设置浮动栏透明度 - /// - public void SetFloatingBarOpacity(double opacity) - { - _dispatcher.InvokeAsync(() => - { - try - { - _mainWindow.ViewboxFloatingBar.Opacity = opacity; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"设置浮动栏透明度失败: {ex}", LogHelper.LogType.Error); - } - }); - } - - /// - /// 设置主面板边距 - /// - public void SetMainPanelMargin(Thickness margin) - { - _dispatcher.InvokeAsync(() => - { - try - { - _mainWindow.ViewBoxStackPanelMain.Margin = margin; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"设置主面板边距失败: {ex}", LogHelper.LogType.Error); - } - }); - } - #endregion - - #region Private Methods - private void UpdateSideButtonStyles() - { - try - { - var sideOption = PPTSButtonsOption.ToString(); - if (sideOption.Length < 3) return; - - var options = sideOption.ToCharArray(); - - // 页码按钮显示 - var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed; - _mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility; - _mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility; - - // 透明度设置 - var opacity = options[1] == '2' ? 0.5 : 1.0; - _mainWindow.PPTBtnLSBorder.Opacity = opacity; - _mainWindow.PPTBtnRSBorder.Opacity = opacity; - - // 颜色主题 - bool isDarkTheme = options[2] == '2'; - ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新侧边按钮样式失败: {ex}", LogHelper.LogType.Error); - } - } - - private void UpdateBottomButtonStyles() - { - try - { - var bottomOption = PPTBButtonsOption.ToString(); - if (bottomOption.Length < 3) return; - - var options = bottomOption.ToCharArray(); - - // 页码按钮显示 - var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed; - _mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility; - _mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility; - - // 透明度设置 - var opacity = options[1] == '2' ? 0.5 : 1.0; - _mainWindow.PPTBtnLBBorder.Opacity = opacity; - _mainWindow.PPTBtnRBBorder.Opacity = opacity; - - // 颜色主题 - bool isDarkTheme = options[2] == '2'; - ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error); - } - } - - private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton) - { - try - { - Color backgroundColor, borderColor, foregroundColor, feedbackColor; - - if (isDarkTheme) - { - backgroundColor = Color.FromRgb(39, 39, 42); - borderColor = Color.FromRgb(82, 82, 91); - foregroundColor = Colors.White; - feedbackColor = Colors.White; - } - else - { - backgroundColor = Color.FromRgb(244, 244, 245); - borderColor = Color.FromRgb(161, 161, 170); - foregroundColor = Color.FromRgb(39, 39, 42); - feedbackColor = Color.FromRgb(24, 24, 27); - } - - // 应用背景和边框颜色 - var backgroundBrush = new SolidColorBrush(backgroundColor); - var borderBrush = new SolidColorBrush(borderColor); - - leftBorder.Background = backgroundBrush; - leftBorder.BorderBrush = borderBrush; - rightBorder.Background = backgroundBrush; - rightBorder.BorderBrush = borderBrush; - - // 应用图标和文字颜色 - var foregroundBrush = new SolidColorBrush(foregroundColor); - var feedbackBrush = new SolidColorBrush(feedbackColor); - - if (isSideButton) - { - ApplySideButtonColors(foregroundBrush, feedbackBrush); - } - else - { - ApplyBottomButtonColors(foregroundBrush, feedbackBrush); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error); - } - } - - private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush) - { - // 图标颜色 - _mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush; - - // 反馈背景颜色 - _mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush; - - // 文字颜色 - TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush); - TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush); - } - - private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush) - { - // 图标颜色 - _mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush; - _mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush; - - // 反馈背景颜色 - _mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush; - _mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush; - - // 文字颜色 - TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush); - TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush); - } - #endregion - } -} diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs deleted file mode 100644 index 4ccc2abb..00000000 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherButton.cs +++ /dev/null @@ -1,274 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using iNKORE.UI.WPF.Modern.Controls; - -namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher -{ - /// - /// 启动台按钮控件 - /// - public class LauncherButton - { - /// - /// 父插件 - /// - private readonly SuperLauncherPlugin _plugin; - - /// - /// 实际按钮控件 - /// - private readonly SimpleStackPanel _panel; - - /// - /// 获取按钮UI元素 - /// - public UIElement Element => _panel; - - /// - /// 构造函数 - /// - /// 父插件 - public LauncherButton(SuperLauncherPlugin plugin) - { - try - { - _plugin = plugin; - LogHelper.WriteLogToFile("开始创建启动台按钮"); - - // 创建SimpleStackPanel - _panel = new SimpleStackPanel - { - Name = "Launcher_Icon", - Orientation = Orientation.Vertical, - HorizontalAlignment = HorizontalAlignment.Center, - Width = 28, - Margin = new Thickness(0, -2, 0, 0), - Background = Brushes.Transparent - }; - - LogHelper.WriteLogToFile("创建SimpleStackPanel完成"); - - // 添加图标 - var image = CreateIconImage(); - _panel.Children.Add(image); - - // 添加文本 - TextBlock textBlock = new TextBlock - { - Text = "启动台", - Foreground = Brushes.Black, - FontSize = 8, - Margin = new Thickness(0, 1, 0, 0), - TextAlignment = TextAlignment.Center - }; - _panel.Children.Add(textBlock); - - // 设置鼠标事件 - _panel.MouseDown += Panel_MouseDown; - _panel.MouseUp += Panel_MouseUp; - _panel.MouseLeave += Panel_MouseLeave; - - // 右键菜单支持 - _panel.ContextMenu = CreateContextMenu(); - - // 设置工具提示 - _panel.ToolTip = "启动台"; - - LogHelper.WriteLogToFile("启动台按钮创建完成"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - /// - /// 创建右键菜单 - /// - private ContextMenu CreateContextMenu() - { - try - { - // 创建菜单 - ContextMenu menu = new ContextMenu(); - - // 创建位置切换菜单项 - MenuItem positionMenuItem = new MenuItem(); - positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ? - "移至右侧" : "移至左侧"; - positionMenuItem.Click += (s, e) => - { - // 切换位置 - _plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ? - LauncherButtonPosition.Right : LauncherButtonPosition.Left; - - // 更新按钮位置 - _plugin.UpdateButtonPosition(); - - // 保存配置 - _plugin.SaveConfig(); - - LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}"); - }; - menu.Items.Add(positionMenuItem); - - // 添加设置菜单项 - MenuItem settingsMenuItem = new MenuItem(); - settingsMenuItem.Header = "打开设置"; - settingsMenuItem.Click += (s, e) => - { - // 打开插件设置窗口 - var mainWindow = Application.Current.MainWindow; - if (mainWindow != null) - { - try - { - // 使用反射调用主窗口的ShowPluginSettings方法 - var method = mainWindow.GetType().GetMethod("ShowPluginSettings"); - if (method != null) - { - method.Invoke(mainWindow, null); - LogHelper.WriteLogToFile("已打开插件设置窗口"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error); - } - } - }; - menu.Items.Add(settingsMenuItem); - - return menu; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 获取实际的UI元素 - /// - [Obsolete("使用Element属性代替")] - public UIElement GetUIElement() - { - return _panel; - } - - /// - /// 创建图标图像 - /// - private Image CreateIconImage() - { - try - { - // 创建图像 - Image image = new Image - { - Height = 17, - Margin = new Thickness(0, 3, 0, 0) - }; - - // 设置位图缩放模式 - RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality); - - // 创建绘图图像 - DrawingImage drawingImage = new DrawingImage(); - DrawingGroup drawingGroup = new DrawingGroup(); - drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z"); - - // 使用提供的应用网格图标 - GeometryDrawing geometryDrawing = new GeometryDrawing - { - Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)), - Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z") - }; - - drawingGroup.Children.Add(geometryDrawing); - - // 设置图像源 - drawingImage.Drawing = drawingGroup; - image.Source = drawingImage; - - return image; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - - // 返回一个空图像 - return new Image(); - } - } - - /// - /// 鼠标按下事件 - /// - private void Panel_MouseDown(object sender, MouseButtonEventArgs e) - { - try - { - // 提供反馈 - _panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0)); - LogHelper.WriteLogToFile("启动台按钮鼠标按下"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 鼠标抬起事件 - /// - private void Panel_MouseUp(object sender, MouseButtonEventArgs e) - { - try - { - // 只有左键点击才显示启动台窗口 - if (e.ChangedButton != MouseButton.Left) - { - return; - } - - // 恢复背景 - _panel.Background = Brushes.Transparent; - LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口"); - - // 获取按钮在屏幕上的位置 - Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0)); - - // 显示启动台窗口 - _plugin.ShowLauncherWindow(buttonPosition); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - /// - /// 鼠标离开事件 - /// - private void Panel_MouseLeave(object sender, MouseEventArgs e) - { - try - { - // 恢复背景 - _panel.Background = Brushes.Transparent; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs deleted file mode 100644 index a3a63d15..00000000 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherModels.cs +++ /dev/null @@ -1,332 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Interop; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using Microsoft.Win32; -using Newtonsoft.Json; - -namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher -{ - /// - /// 启动台按钮位置 - /// - public enum LauncherButtonPosition - { - /// - /// 左侧 - /// - Left, - - /// - /// 右侧 - /// - Right - } - - /// - /// 启动台配置 - /// - public class LauncherConfig - { - /// - /// 启动台按钮位置 - /// - public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right; - - /// - /// 启动台应用程序列表 - /// - public List Items { get; set; } = new List(); - } - - /// - /// 启动台应用项 - /// - public class LauncherItem - { - /// - /// 应用程序名称 - /// - public string Name { get; set; } - - /// - /// 应用程序路径 - /// - public string Path { get; set; } - - /// - /// 是否可见 - /// - public bool IsVisible { get; set; } = true; - - /// - /// 在启动台中的位置(0-39) - /// - public int Position { get; set; } = -1; - - /// - /// 是否已固定位置 - /// - public bool IsPositionFixed { get; set; } = false; - - /// - /// 图标缓存 - /// - [JsonIgnore] - private ImageSource _iconCache; - - /// - /// 获取应用程序图标 - /// - [JsonIgnore] - public ImageSource Icon - { - get - { - if (_iconCache != null) - { - return _iconCache; - } - - try - { - if (File.Exists(Path)) - { - // 从文件中获取图标 - Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path); - if (icon != null) - { - _iconCache = Imaging.CreateBitmapSourceFromHIcon( - icon.Handle, - Int32Rect.Empty, - BitmapSizeOptions.FromEmptyOptions()); - - icon.Dispose(); - return _iconCache; - } - } - else - { - // 从注册表中获取文件类型关联图标 - string extension = System.IO.Path.GetExtension(Path); - if (!string.IsNullOrEmpty(extension)) - { - string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string; - if (!string.IsNullOrEmpty(fileType)) - { - string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string; - if (!string.IsNullOrEmpty(iconPath)) - { - string[] parts = iconPath.Split(','); - string iconFile = parts[0].Trim('"'); - int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0; - - if (File.Exists(iconFile)) - { - Icon icon = IconExtractor.Extract(iconFile, iconIndex, true); - if (icon != null) - { - _iconCache = Imaging.CreateBitmapSourceFromHIcon( - icon.Handle, - Int32Rect.Empty, - BitmapSizeOptions.FromEmptyOptions()); - - icon.Dispose(); - return _iconCache; - } - } - } - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 返回默认图标 - return GetDefaultIcon(); - } - } - - /// - /// 获取默认图标 - /// - private ImageSource GetDefaultIcon() - { - try - { - // 对于资源管理器,使用特定图标 - if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase)) - { - try - { - // 直接从C:\Windows\explorer.exe获取图标 - string explorerPath = @"C:\Windows\explorer.exe"; - if (File.Exists(explorerPath)) - { - Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(explorerPath); - if (icon != null) - { - _iconCache = Imaging.CreateBitmapSourceFromHIcon( - icon.Handle, - Int32Rect.Empty, - BitmapSizeOptions.FromEmptyOptions()); - - icon.Dispose(); - return _iconCache; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取资源管理器图标时出错: {ex.Message}", LogHelper.LogType.Warning); - // 如果获取Windows图标失败,回退到默认图标 - } - - // 回退到备用图标 - string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png"); - if (File.Exists(explorerIconPath)) - { - Uri uri = new Uri(explorerIconPath); - BitmapImage image = new BitmapImage(uri); - _iconCache = image; - return _iconCache; - } - } - - // 返回一个简单的默认图标 - string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png"); - if (File.Exists(iconPath)) - { - Uri uri = new Uri(iconPath); - BitmapImage image = new BitmapImage(uri); - _iconCache = image; - return _iconCache; - } - - // 如果还是没有找到,尝试使用应用程序图标 - string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png"); - if (File.Exists(appIconPath)) - { - Uri uri = new Uri(appIconPath); - BitmapImage image = new BitmapImage(uri); - _iconCache = image; - return _iconCache; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error); - } - - return null; - } - - /// - /// 启动应用程序 - /// - public void Launch() - { - try - { - if (string.IsNullOrEmpty(Path)) - { - LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error); - return; - } - - // 检查文件是否存在 - if (!File.Exists(Path) && !Path.Contains(":\\")) - { - // 可能是系统命令,如explorer.exe - ProcessStartInfo psi = new ProcessStartInfo - { - FileName = Path, - UseShellExecute = true - }; - Process.Start(psi); - } - else - { - // 使用Process.Start启动应用程序 - ProcessStartInfo psi = new ProcessStartInfo - { - FileName = Path, - UseShellExecute = true - }; - Process.Start(psi); - } - - LogHelper.WriteLogToFile($"已启动应用程序: {Path}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error); - MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - } - - /// - /// 图标提取工具类 - /// - public static class IconExtractor - { - /// - /// 从文件中提取图标 - /// - /// 文件路径 - /// 图标索引 - /// 是否提取大图标 - /// 提取的图标 - public static Icon Extract(string file, int index, bool largeIcon) - { - try - { - IntPtr large; - IntPtr small; - ExtractIconEx(file, index, out large, out small, 1); - - try - { - return Icon.FromHandle(largeIcon ? large : small); - } - catch - { - return null; - } - finally - { - if (large != IntPtr.Zero) - DestroyIcon(large); - - if (small != IntPtr.Zero) - DestroyIcon(small); - } - } - catch - { - return null; - } - } - - [DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")] - private static extern int ExtractIconEx( - [MarshalAs(UnmanagedType.LPStr)] string lpszFile, - int nIconIndex, - out IntPtr phiconLarge, - out IntPtr phiconSmall, - int nIcons); - - [DllImport("User32.dll")] - private static extern int DestroyIcon(IntPtr hIcon); - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml deleted file mode 100644 index dde23f42..00000000 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs deleted file mode 100644 index a1978d29..00000000 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherWindow.xaml.cs +++ /dev/null @@ -1,466 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Threading; - -namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher -{ - /// - /// LauncherWindow.xaml 的交互逻辑 - /// - public partial class LauncherWindow : Window - { - /// - /// 父插件 - /// - private readonly SuperLauncherPlugin _plugin; - - /// - /// 是否处于固定模式 - /// - private bool _isFixMode; - - /// - /// 应用项按钮列表 - /// - private readonly Dictionary _appButtons = new Dictionary(); - - /// - /// 拖拽中的按钮 - /// - private Button _draggingButton; - - /// - /// 拖拽开始位置 - /// - private Point _dragStartPoint; - - /// - /// 构造函数 - /// - public LauncherWindow(SuperLauncherPlugin plugin) - { - InitializeComponent(); - - _plugin = plugin; - - // 加载应用项 - LoadLauncherItems(); - - // 添加鼠标按下事件(用于拖动窗口) - MouseDown += (s, e) => - { - if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed) - { - DragMove(); - } - }; - - // 根据应用数量调整窗口大小 - AdjustWindowSize(); - } - - /// - /// 加载启动台应用项 - /// - private void LoadLauncherItems() - { - // 清空现有应用项 - AppPanel.Children.Clear(); - _appButtons.Clear(); - - // 获取显示的应用项 - var visibleItems = _plugin.LauncherItems - .Where(item => item.IsVisible) - .OrderBy(item => item.Position) - .ToList(); - - foreach (var item in visibleItems) - { - // 创建应用按钮 - Button appButton = new Button - { - Style = (Style)FindResource("LauncherItemStyle"), - DataContext = item, - Tag = item.Position - }; - - // 添加点击事件 - appButton.Click += AppButton_Click; - - // 在固定模式下,添加拖拽事件 - appButton.PreviewMouseDown += AppButton_PreviewMouseDown; - appButton.PreviewMouseMove += AppButton_PreviewMouseMove; - appButton.PreviewMouseUp += AppButton_PreviewMouseUp; - - // 记录按钮和项目的对应关系 - _appButtons.Add(appButton, item); - - // 添加到面板 - AppPanel.Children.Add(appButton); - } - } - - /// - /// 根据应用数量调整窗口大小 - /// - private void AdjustWindowSize() - { - try - { - // 每行最多显示4个应用 - const int appsPerRow = 4; - - // 计算行数 - int visibleCount = _appButtons.Count; - int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow); - - // 设置窗口宽度(每个应用90像素宽 = 80 + 5*2) - Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400 - - // 设置窗口高度(每个应用90像素高 = 80 + 5*2) - Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20 - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 应用按钮点击事件 - /// - private void AppButton_Click(object sender, RoutedEventArgs e) - { - try - { - if (_isFixMode) return; // 在固定模式下,不响应点击事件 - - if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item)) - { - // 获取应用路径和名称,用于后续启动 - string appPath = item.Path; - string appName = item.Name; - - LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}"); - - // 首先标记窗口正在关闭 - IsClosing = true; - - // 创建一个应用启动任务 - var launchTask = new Task(() => - { - try - { - // 等待一段时间,确保窗口关闭流程已经开始 - Thread.Sleep(200); - - // 使用UI线程启动应用 - Application.Current.Dispatcher.Invoke(() => - { - try - { - // 检查应用路径是否存在 - if (File.Exists(appPath) || !appPath.Contains(":\\")) - { - // 创建进程启动信息 - var psi = new ProcessStartInfo - { - FileName = appPath, - UseShellExecute = true, - }; - - // 启动应用程序 - var process = Process.Start(psi); - LogHelper.WriteLogToFile($"应用程序 {appName} 已启动"); - } - else - { - LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error); - MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error); - MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error); - } - }); - - // 关闭窗口 - try - { - Dispatcher.BeginInvoke(new Action(() => - { - try { Close(); } catch { } - - // 启动应用程序任务 - launchTask.Start(); - }), DispatcherPriority.Background); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error); - // 如果无法通过UI关闭窗口,直接启动任务 - launchTask.Start(); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error); - try { IsClosing = true; Close(); } catch { } - } - } - - #region 固定模式拖拽事件 - - /// - /// 应用按钮鼠标按下事件 - /// - private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) - { - if (!_isFixMode) return; - - if (e.ChangedButton == MouseButton.Left && sender is Button button) - { - _draggingButton = button; - _dragStartPoint = e.GetPosition(AppPanel); - button.CaptureMouse(); - button.Opacity = 0.7; - - // 阻止事件冒泡,以避免触发按钮点击 - e.Handled = true; - } - } - - /// - /// 应用按钮鼠标移动事件 - /// - private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e) - { - if (!_isFixMode || _draggingButton == null) return; - - if (e.LeftButton == MouseButtonState.Pressed) - { - Point currentPosition = e.GetPosition(AppPanel); - - // 移动按钮 - System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2); - System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2); - - // 将按钮移到最上层 - Panel.SetZIndex(_draggingButton, 100); - - // 阻止事件冒泡 - e.Handled = true; - } - } - - /// - /// 应用按钮鼠标释放事件 - /// - private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e) - { - if (!_isFixMode || _draggingButton == null) return; - - // 释放鼠标捕获 - _draggingButton.ReleaseMouseCapture(); - - // 计算新位置 - Point releasePoint = e.GetPosition(AppPanel); - int newPosition = CalculateGridPosition(releasePoint); - - // 获取当前项目 - LauncherItem currentItem = _appButtons[_draggingButton]; - - // 重新排序 - ReorderItems(currentItem, newPosition); - - // 重新加载应用项 - LoadLauncherItems(); - - // 保存配置 - _plugin.SaveConfig(); - - // 清除拖拽状态 - _draggingButton.Opacity = 1; - Panel.SetZIndex(_draggingButton, 0); - _draggingButton = null; - - // 阻止事件冒泡 - e.Handled = true; - } - - /// - /// 计算网格位置 - /// - private int CalculateGridPosition(Point point) - { - // 计算行和列 - int columnCount = 4; // 每行最多4个应用 - int columnWidth = 90; // 应用宽度(包括边距) - int rowHeight = 90; // 应用高度(包括边距) - - int column = (int)(point.X / columnWidth); - int row = (int)(point.Y / rowHeight); - - // 确保在有效范围内 - column = Math.Max(0, Math.Min(column, columnCount - 1)); - row = Math.Max(0, row); - - // 计算位置索引 - return row * columnCount + column; - } - - /// - /// 重新排序应用项 - /// - private void ReorderItems(LauncherItem item, int newPosition) - { - try - { - // 设置项目为固定位置 - item.IsPositionFixed = true; - - // 如果位置相同,无需调整 - if (item.Position == newPosition) - { - return; - } - - // 获取所有可见项目 - var visibleItems = _plugin.LauncherItems - .Where(i => i.IsVisible) - .OrderBy(i => i.Position) - .ToList(); - - // 移除当前项目 - visibleItems.Remove(item); - - // 查找插入位置 - int insertIndex = 0; - for (int i = 0; i < visibleItems.Count; i++) - { - if (visibleItems[i].Position >= newPosition) - { - insertIndex = i; - break; - } - insertIndex = i + 1; - } - - // 插入项目 - visibleItems.Insert(insertIndex, item); - - // 重新分配位置 - for (int i = 0; i < visibleItems.Count; i++) - { - visibleItems[i].Position = i; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - #endregion - - #region 窗口事件处理 - - /// - /// 窗口失去焦点事件 - /// - private void Window_Deactivated(object sender, EventArgs e) - { - try - { - // 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口 - if (!_isFixMode && IsLoaded && !IsClosing) - { - // 标记为正在关闭 - IsClosing = true; - - // 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突 - Dispatcher.BeginInvoke(new Action(() => - { - try - { - // 再次检查窗口状态 - if (IsLoaded && !IsClosing) - { - Close(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error); - } - }), DispatcherPriority.Background); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 窗口是否正在关闭 - /// - private bool IsClosing { get; set; } - - /// - /// 重写OnClosing方法,标记窗口正在关闭 - /// - protected override void OnClosing(CancelEventArgs e) - { - IsClosing = true; - base.OnClosing(e); - } - - /// - /// 关闭按钮点击事件 - /// - private void BtnClose_Click(object sender, RoutedEventArgs e) - { - Close(); - } - - /// - /// 固定模式按钮点击事件 - /// - private void BtnFixMode_Click(object sender, RoutedEventArgs e) - { - // 切换固定模式 - _isFixMode = !_isFixMode; - - // 更新固定模式按钮图标颜色 - FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White; - - // 显示提示 - if (_isFixMode) - { - MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs deleted file mode 100644 index cd22668f..00000000 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncherPlugin.cs +++ /dev/null @@ -1,589 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher; -using Newtonsoft.Json; - -namespace Ink_Canvas.Helpers.Plugins.BuiltIn -{ - /// - /// 超级启动台插件 - /// - public class SuperLauncherPlugin : PluginBase - { - #region 插件基本信息 - - public override string Name => "超级启动台"; - - public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。"; - - public override Version Version => new Version(1, 0, 1); - - public override string Author => "ICC CE 团队"; - - public override bool IsBuiltIn => true; - - #endregion - - #region 插件属性和字段 - - /// - /// 启动台配置 - /// - public LauncherConfig Config { get; private set; } - - /// - /// 启动台应用程序列表 - /// - public ObservableCollection LauncherItems { get; private set; } - - /// - /// 启动台按钮 - /// - private LauncherButton _launcherButton; - - /// - /// 启动台窗口 - /// - private LauncherWindow _launcherWindow; - - /// - /// 配置文件路径 - /// - private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json"); - - /// - /// 标记是否已添加到浮动栏 - /// - private bool _isAddedToFloatingBar; - - #endregion - - #region 插件生命周期 - - public override void Initialize() - { - try - { - base.Initialize(); - - // 创建配置目录 - string configDir = Path.Combine(App.RootPath, "PluginConfigs"); - if (!Directory.Exists(configDir)) - { - Directory.CreateDirectory(configDir); - } - - // 加载配置 - LoadConfig(); - - LogHelper.WriteLogToFile("超级启动台插件已初始化"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - public override void Enable() - { - try - { - if (IsEnabled) return; // 防止重复启用 - - // 创建启动台按钮 - if (_launcherButton == null) - { - _launcherButton = new LauncherButton(this); - LogHelper.WriteLogToFile("超级启动台按钮已创建"); - } - - // 添加启动台按钮到浮动栏 - AddLauncherButtonToFloatingBar(); - - // 设置启用状态 - base.Enable(); - - // 保存插件配置 - SavePluginSettings(); - - LogHelper.WriteLogToFile("超级启动台插件已启用"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - public override void Disable() - { - try - { - if (!IsEnabled) return; // 防止重复禁用 - - // 从浮动栏移除启动台按钮 - RemoveLauncherButtonFromFloatingBar(); - - // 如果启动台窗口打开,则关闭 - if (_launcherWindow != null && _launcherWindow.IsVisible) - { - _launcherWindow.Close(); - _launcherWindow = null; - } - - // 设置禁用状态 - base.Disable(); - - // 保存插件配置 - SavePluginSettings(); - - LogHelper.WriteLogToFile("超级启动台插件已禁用"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - public override UserControl GetSettingsView() - { - return new LauncherSettingsControl(this); - } - - public override void Cleanup() - { - // 保存配置 - SaveConfig(); - - // 从浮动栏移除启动台按钮 - RemoveLauncherButtonFromFloatingBar(); - - // 如果启动台窗口打开,则关闭 - if (_launcherWindow != null && _launcherWindow.IsVisible) - { - _launcherWindow.Close(); - _launcherWindow = null; - } - - base.Cleanup(); - } - - /// - /// 保存插件设置 - /// - public override void SavePluginSettings() - { - try - { - // 确保配置已加载 - if (Config == null) - { - LoadConfig(); - } - - // 更新其他设置,但不更改插件启用状态 - - // 保存配置 - SaveConfig(); - - LogHelper.WriteLogToFile("超级启动台插件设置已保存"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - #endregion - - #region 配置管理 - - /// - /// 加载配置 - /// - private void LoadConfig() - { - try - { - if (File.Exists(_configPath)) - { - string json = File.ReadAllText(_configPath); - Config = JsonConvert.DeserializeObject(json) ?? CreateDefaultConfig(); - LauncherItems = new ObservableCollection(Config.Items ?? new List()); - - // 注意:不再根据配置更改插件启用状态 - // 插件状态由PluginManager统一管理 - } - else - { - Config = CreateDefaultConfig(); - LauncherItems = new ObservableCollection(Config.Items); - SaveConfig(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error); - Config = CreateDefaultConfig(); - LauncherItems = new ObservableCollection(Config.Items); - } - } - - /// - /// 保存配置 - /// - public void SaveConfig() - { - try - { - // 同步LauncherItems到Config - Config.Items = new List(LauncherItems); - - // 序列化并保存配置 - string json = JsonConvert.SerializeObject(Config, Formatting.Indented); - File.WriteAllText(_configPath, json); - - LogHelper.WriteLogToFile("超级启动台配置已保存"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 创建默认配置 - /// - private LauncherConfig CreateDefaultConfig() - { - var config = new LauncherConfig - { - ButtonPosition = LauncherButtonPosition.Right, - // 不再使用IsEnabled,插件状态由PluginManager管理 - Items = new List - { - new LauncherItem - { - Name = "资源管理器", - Path = @"C:\Windows\explorer.exe", - IsVisible = true, - Position = 0 - } - } - }; - - return config; - } - - #endregion - - #region 启动台按钮管理 - - /// - /// 将启动台按钮添加到浮动栏 - /// - private void AddLauncherButtonToFloatingBar() - { - try - { - // 如果已经添加,先移除 - if (_isAddedToFloatingBar) - { - RemoveLauncherButtonFromFloatingBar(); - _isAddedToFloatingBar = false; - } - - // 获取主窗口实例 - var mainWindow = Application.Current.MainWindow; - if (mainWindow == null) - { - LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error); - return; - } - - // 创建启动台按钮 - _launcherButton = new LauncherButton(this); - var buttonElement = _launcherButton.Element; - - // 查找浮动栏 - var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel; - if (floatingBar == null) - { - // 如果直接查找失败,则尝试遍历可视树查找 - Panel floatingBarPanelFromTree = null; - FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree); - floatingBar = floatingBarPanelFromTree; - } - - if (floatingBar == null) - { - LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error); - return; - } - - // 添加启动台按钮到浮动栏 - if (Config.ButtonPosition == LauncherButtonPosition.Left) - { - floatingBar.Children.Insert(0, buttonElement); - LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧"); - } - else - { - floatingBar.Children.Add(buttonElement); - LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧"); - } - - _isAddedToFloatingBar = true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - /// - /// 递归查找StackPanelFloatingBar - /// - private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result) - { - if (parent == null || result != null) return; - - try - { - // 检查当前对象是否为我们要找的面板 - if (parent is Panel panel && panel.Name == "StackPanelFloatingBar") - { - result = panel; - return; - } - - // 获取子元素数量 - int childCount = VisualTreeHelper.GetChildrenCount(parent); - - // 遍历所有子元素 - for (int i = 0; i < childCount; i++) - { - DependencyObject child = VisualTreeHelper.GetChild(parent, i); - FindStackPanelFloatingBar(child, ref result); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 从浮动栏移除启动台按钮 - /// - private void RemoveLauncherButtonFromFloatingBar() - { - try - { - if (!_isAddedToFloatingBar || _launcherButton == null) - { - return; - } - - // 获取主窗口实例 - var mainWindow = Application.Current.MainWindow; - if (mainWindow == null) - { - LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error); - return; - } - - // 获取按钮元素 - var buttonElement = _launcherButton.Element; - - // 查找浮动栏 - var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel; - if (floatingBar == null) - { - // 如果直接查找失败,则尝试遍历可视树查找 - Panel floatingBarPanelFromTree = null; - FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree); - floatingBar = floatingBarPanelFromTree; - } - - if (floatingBar == null) - { - LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error); - return; - } - - // 从浮动栏移除启动台按钮 - if (floatingBar.Children.Contains(buttonElement)) - { - floatingBar.Children.Remove(buttonElement); - LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除"); - } - - _isAddedToFloatingBar = false; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - /// - /// 更新启动台按钮位置 - /// - public void UpdateButtonPosition() - { - try - { - // 如果按钮已添加到浮动栏,重新添加以更新位置 - if (_isAddedToFloatingBar) - { - RemoveLauncherButtonFromFloatingBar(); - AddLauncherButtonToFloatingBar(); - LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error); - LogHelper.NewLog(ex); - } - } - - #endregion - - #region 启动台功能 - - /// - /// 显示启动台窗口 - /// - /// 按钮在屏幕上的位置 - public void ShowLauncherWindow(Point buttonPosition) - { - try - { - // 如果窗口已存在,关闭它 - if (_launcherWindow != null && _launcherWindow.IsVisible) - { - _launcherWindow.Close(); - _launcherWindow = null; - return; - } - - // 创建新的启动台窗口 - _launcherWindow = new LauncherWindow(this); - - // 计算窗口位置,使其位于按钮上方 - PositionLauncherWindow(_launcherWindow, buttonPosition); - - // 显示窗口 - _launcherWindow.Show(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 设置启动台窗口位置 - /// - /// 启动台窗口 - /// 按钮在屏幕上的位置 - private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition) - { - // 确保窗口已加载 - if (window.ActualWidth == 0 || window.ActualHeight == 0) - { - window.WindowStartupLocation = WindowStartupLocation.CenterScreen; - - // 设置窗口加载完成后的位置 - window.Loaded += (s, e) => - { - // 窗口位于按钮上方居中 - double left = buttonPosition.X - (window.ActualWidth / 2); - double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距 - - // 确保窗口在屏幕内 - left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth)); - top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight)); - - window.Left = left; - window.Top = top; - }; - } - else - { - // 窗口位于按钮上方居中 - double left = buttonPosition.X - (window.ActualWidth / 2); - double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距 - - // 确保窗口在屏幕内 - left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth)); - top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight)); - - window.Left = left; - window.Top = top; - } - } - - /// - /// 添加应用到启动台 - /// - /// 启动台项 - public void AddLauncherItem(LauncherItem item) - { - // 如果项目数量已达上限,则不添加 - if (LauncherItems.Count >= 40) - { - MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information); - return; - } - - // 寻找合适的位置 - if (item.Position < 0) - { - item.Position = FindNextAvailablePosition(); - } - - // 添加项目并保存配置 - LauncherItems.Add(item); - SaveConfig(); - } - - /// - /// 查找下一个可用位置 - /// - private int FindNextAvailablePosition() - { - // 获取已使用的位置列表 - var usedPositions = new HashSet(); - foreach (var item in LauncherItems) - { - usedPositions.Add(item.Position); - } - - // 查找第一个可用位置 - for (int i = 0; i < 40; i++) - { - if (!usedPositions.Contains(i)) - { - return i; - } - } - - // 如果所有位置都已使用,则返回0 - return 0; - } - - #endregion - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/ICCPPPluginAdapter.cs b/Ink Canvas/Helpers/Plugins/ICCPPPluginAdapter.cs deleted file mode 100644 index a346282b..00000000 --- a/Ink Canvas/Helpers/Plugins/ICCPPPluginAdapter.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.IO; - -namespace Ink_Canvas.Helpers.Plugins -{ - /// - /// ICCPP 插件适配器,用于加载和管理 .iccpp 格式的插件 - /// - public class ICCPPPluginAdapter : PluginBase - { - private readonly byte[] _pluginData; - private readonly string _pluginPath; - private readonly string _pluginName; - private readonly Version _pluginVersion; - private bool _isInitialized; - - /// - /// 创建 ICCPP 插件适配器 - /// - /// 插件文件路径 - /// 插件文件数据 - public ICCPPPluginAdapter(string pluginPath, byte[] pluginData) - { - _pluginPath = pluginPath; - _pluginData = pluginData; - PluginPath = pluginPath; - - // 从文件名获取插件名称 - _pluginName = Path.GetFileNameWithoutExtension(pluginPath); - _pluginVersion = new Version(1, 0, 0); // 默认版本 - - // 尝试从插件数据中读取更多信息 - TryReadPluginMetadata(); - } - - public ICCPPPluginAdapter() - { - _pluginPath = string.Empty; - _pluginData = new byte[0]; - PluginPath = string.Empty; - _pluginName = "ICCPPPlugin"; - _pluginVersion = new Version(1, 0, 0); - // 可选:初始化其他字段 - } - - /// - /// 尝试从插件数据中读取元数据 - /// - private void TryReadPluginMetadata() - { - try - { - // 这里可以根据 .iccpp 文件的实际格式解析元数据 - // 例如,如果文件有特定的头部结构,可以在这里解析 - - // 示例:如果前100字节包含元数据 - if (_pluginData.Length > 100) - { - // 解析元数据的代码... - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"解析插件 {_pluginName} 元数据时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - #region IPlugin 接口实现 - - /// - /// 插件名称 - /// - public override string Name => _pluginName; - - /// - /// 插件描述 - /// - public override string Description => $"{_pluginName} (ICCPP 格式插件)"; - - /// - /// 插件版本 - /// - public override Version Version => _pluginVersion; - - /// - /// 插件作者 - /// - public override string Author => "未知"; - - /// - /// 是否为内置插件 - /// - public override bool IsBuiltIn => false; - - /// - /// 初始化插件 - /// - public override void Initialize() - { - if (_isInitialized) return; - - try - { - // 这里可以添加 .iccpp 插件的初始化逻辑 - // 例如,根据文件格式加载特定资源 - - LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已初始化"); - _isInitialized = true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 启用插件 - /// - public override void Enable() - { - if (IsEnabled) return; - - try - { - // 这里可以添加 .iccpp 插件的启用逻辑 - // 例如,加载动态库、注册事件等 - - base.Enable(); // 设置启用状态并触发事件 - LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已启用"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 禁用插件 - /// - public override void Disable() - { - if (!IsEnabled) return; - - try - { - // 这里可以添加 .iccpp 插件的禁用逻辑 - // 例如,卸载动态库、注销事件等 - - base.Disable(); // 设置禁用状态并触发事件 - LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已禁用"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"禁用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 清理插件资源 - /// - public override void Cleanup() - { - try - { - // 这里可以添加 .iccpp 插件的清理逻辑 - // 例如,释放资源等 - - LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已清理资源"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理 ICCPP 插件 {Name} 资源时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/IPlugin.cs b/Ink Canvas/Helpers/Plugins/IPlugin.cs deleted file mode 100644 index 6a527021..00000000 --- a/Ink Canvas/Helpers/Plugins/IPlugin.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Windows.Controls; - -namespace Ink_Canvas.Helpers.Plugins -{ - /// - /// 定义插件的基本接口 - /// - public interface IPlugin - { - /// - /// 插件名称 - /// - string Name { get; } - - /// - /// 插件描述 - /// - string Description { get; } - - /// - /// 插件版本 - /// - Version Version { get; } - - /// - /// 插件作者 - /// - string Author { get; } - - /// - /// 是否为内置插件 - /// - bool IsBuiltIn { get; } - - /// - /// 初始化插件 - /// 此方法在插件加载时被调用,用于执行一些初始化工作 - /// - void Initialize(); - - /// - /// 启用插件 - /// 此方法在插件被用户或系统启用时调用,激活插件功能 - /// - void Enable(); - - /// - /// 禁用插件 - /// 此方法在插件被用户或系统禁用时调用,停用插件功能 - /// - void Disable(); - - /// - /// 获取插件设置界面 - /// 此方法返回插件的设置界面控件,用于展示在设置窗口 - /// - /// 插件设置界面 - UserControl GetSettingsView(); - - /// - /// 插件卸载时的清理工作 - /// 此方法在插件被卸载前调用,用于释放资源和执行清理 - /// - void Cleanup(); - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginBase.cs b/Ink Canvas/Helpers/Plugins/PluginBase.cs deleted file mode 100644 index c00ecbc5..00000000 --- a/Ink Canvas/Helpers/Plugins/PluginBase.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Windows.Controls; - -namespace Ink_Canvas.Helpers.Plugins -{ - /// - /// 插件基类,提供基本实现 - /// - public abstract class PluginBase : IPlugin - { - /// - /// 插件状态(私有字段) - /// - private bool _isEnabled; - - /// - /// 插件状态(公共属性) - /// - public bool IsEnabled - { - get => _isEnabled; - protected set - { - if (_isEnabled != value) - { - _isEnabled = value; - OnEnabledStateChanged(value); - } - } - } - - /// - /// 插件ID - /// - public string Id { get; protected set; } - - /// - /// 插件路径 - /// - public string PluginPath { get; set; } - - /// - /// 插件名称 - /// - public abstract string Name { get; } - - /// - /// 插件描述 - /// - public abstract string Description { get; } - - /// - /// 插件版本 - /// - public abstract Version Version { get; } - - /// - /// 插件作者 - /// - public abstract string Author { get; } - - /// - /// 是否为内置插件 - /// - public virtual bool IsBuiltIn => false; - - /// - /// 状态变更事件 - /// - public event EventHandler EnabledStateChanged; - - /// - /// 初始化插件 - /// - public virtual void Initialize() - { - Id = GetType().FullName; - - // 添加日志,记录插件名称 - try - { - string name = Name; - LogHelper.WriteLogToFile($"初始化插件: ID={Id}, 名称={name ?? "未命名"}"); - - if (string.IsNullOrEmpty(name)) - { - LogHelper.WriteLogToFile($"警告: 插件 {Id} 的名称为空", LogHelper.LogType.Warning); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"获取插件名称时出错: {ex.Message}", LogHelper.LogType.Error); - } - - LogHelper.WriteLogToFile($"插件 {Name} 已初始化"); - } - - /// - /// 启用插件 - /// - public virtual void Enable() - { - if (!IsEnabled) - { - IsEnabled = true; - LogHelper.WriteLogToFile($"插件 {Name} 已启用"); - } - } - - /// - /// 禁用插件 - /// - public virtual void Disable() - { - if (IsEnabled) - { - IsEnabled = false; - LogHelper.WriteLogToFile($"插件 {Name} 已禁用"); - } - } - - /// - /// 获取插件设置界面 - /// - /// 插件设置界面 - public virtual UserControl GetSettingsView() - { - // 默认返回空设置页面 - return new UserControl(); - } - - /// - /// 插件卸载时的清理工作 - /// - public virtual void Cleanup() - { - LogHelper.WriteLogToFile($"插件 {Name} 已卸载"); - } - - /// - /// 保存插件自身的设置 - /// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态 - /// 插件启用状态由PluginManager统一管理 - /// - public virtual void SavePluginSettings() - { - // 默认实现不做任何事情 - // 子类可以重写此方法,将自身设置保存到配置文件中 - LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event); - } - - /// - /// 触发状态变更事件 - /// - /// 是否启用 - protected virtual void OnEnabledStateChanged(bool isEnabled) - { - EnabledStateChanged?.Invoke(this, isEnabled); - } - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginManager.cs b/Ink Canvas/Helpers/Plugins/PluginManager.cs deleted file mode 100644 index c2775f65..00000000 --- a/Ink Canvas/Helpers/Plugins/PluginManager.cs +++ /dev/null @@ -1,1459 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using Ink_Canvas.Windows; -using Newtonsoft.Json; -using Timer = System.Timers.Timer; - -namespace Ink_Canvas.Helpers.Plugins -{ - /// - /// 插件管理器,负责插件的加载、卸载和管理 - /// - public class PluginManager - { - private static readonly string PluginsDirectory = Path.Combine(App.RootPath, "Plugins"); - private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "PluginConfig.json"); - private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "PluginConfig.json.bak"); - - private static PluginManager _instance; - private static SemaphoreSlim _configLock = new SemaphoreSlim(1, 1); - - /// - /// 插件管理器单例 - /// - public static PluginManager Instance - { - get - { - if (_instance == null) - { - _instance = new PluginManager(); - } - return _instance; - } - } - - /// - /// 已加载的插件集合 - /// - public ObservableCollection Plugins { get; } = new ObservableCollection(); - - /// - /// 插件配置信息 - /// - public Dictionary PluginStates { get; private set; } = new Dictionary(); - - /// - /// 配置是否已更改但未保存 - /// - private bool _configDirty; - - /// - /// 配置自动保存计时器 - /// - private Timer _autoSaveTimer; - - /// - /// 加载的程序集缓存 - /// - private Dictionary _loadedAssemblies = new Dictionary(); - - /// - /// 插件文件哈希缓存,用于热重载检测 - /// - private Dictionary _pluginHashes = new Dictionary(); - - private PluginManager() - { - // 确保插件目录存在 - if (!Directory.Exists(PluginsDirectory)) - { - Directory.CreateDirectory(PluginsDirectory); - } - - // 加载插件配置 - LoadConfig(); - - // 初始化自动保存计时器(3秒) - _autoSaveTimer = new Timer(3000); - _autoSaveTimer.Elapsed += (s, e) => - { - if (_configDirty) - { - SaveConfigAsync().ConfigureAwait(false); - } - }; - _autoSaveTimer.AutoReset = false; - - // 注册插件状态变更事件处理 - AppDomain.CurrentDomain.ProcessExit += (s, e) => - { - // 应用退出时强制保存配置 - if (_configDirty) - { - SaveConfig(); - } - }; - } - - /// - /// 初始化插件系统 - /// - public void Initialize() - { - try - { - LogHelper.WriteLogToFile("开始初始化插件系统"); - - // 加载配置 - LoadConfig(); - LogHelper.WriteLogToFile($"已从配置文件加载 {PluginStates.Count} 个插件状态记录"); - - // 加载内置插件 - LogHelper.WriteLogToFile("正在加载内置插件..."); - LoadBuiltInPlugins(); - - // 加载外部插件 - LogHelper.WriteLogToFile("正在加载外部插件..."); - LoadExternalPlugins(); - - // 启用已配置为启用的插件 - LogHelper.WriteLogToFile("正在应用配置的插件状态..."); - EnableConfiguredPlugins(); - - // 设置定期检查热重载 - StartHotReloadWatcher(); - - // 保存初始化后的配置(可能有新插件) - SaveConfig(); - - LogHelper.WriteLogToFile($"插件系统初始化完成,共加载 {Plugins.Count} 个插件"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化插件系统时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 加载内置插件 - /// - private void LoadBuiltInPlugins() - { - try - { - // 获取当前程序集 - Assembly currentAssembly = Assembly.GetExecutingAssembly(); - - // 查找实现了IPlugin接口的所有类型 - var pluginTypes = currentAssembly.GetTypes() - .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && t.IsClass); - - foreach (var pluginType in pluginTypes) - { - try - { - // 创建插件实例 - IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); - - // 只处理内置插件 - if (plugin.IsBuiltIn) - { - plugin.Initialize(); - Plugins.Add(plugin); - LogHelper.WriteLogToFile($"已加载内置插件: {plugin.Name} v{plugin.Version}"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载内置插件 {pluginType.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载内置插件时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 加载外部插件 - /// - private void LoadExternalPlugins() - { - try - { - // 检查插件目录是否存在 - if (!Directory.Exists(PluginsDirectory)) - { - Directory.CreateDirectory(PluginsDirectory); - return; - } - - // 获取所有插件文件(支持 .iccpp 和 .dll 格式) - var pluginFiles = Directory.GetFiles(PluginsDirectory, "*.iccpp", SearchOption.TopDirectoryOnly) - .Concat(Directory.GetFiles(PluginsDirectory, "*.dll", SearchOption.TopDirectoryOnly)) - .ToArray(); - - LogHelper.WriteLogToFile($"发现 {pluginFiles.Length} 个外部插件文件"); - - foreach (var pluginFile in pluginFiles) - { - LoadExternalPlugin(pluginFile); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载外部插件时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 加载单个外部插件 - /// - /// 插件文件路径 - /// 加载的插件实例,加载失败则返回null - public IPlugin LoadExternalPlugin(string pluginPath) - { - try - { - // 计算文件哈希 - string fileHash = CalculateFileHash(pluginPath); - _pluginHashes[pluginPath] = fileHash; - - // 检查文件扩展名 - string extension = Path.GetExtension(pluginPath).ToLowerInvariant(); - if (extension == ".iccpp") - { - // 创建 ICCPP 插件适配器 - return CreateICCPPPluginAdapter(pluginPath); - } - - // 加载插件程序集 - Assembly pluginAssembly = LoadPluginAssembly(pluginPath); - if (pluginAssembly == null) return null; - - // 查找实现了IPlugin接口的类型 - var pluginTypes = pluginAssembly.GetTypes() - .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && t.IsClass); - - foreach (var pluginType in pluginTypes) - { - try - { - // 创建插件实例 - IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); - - // 设置插件路径 - if (plugin is PluginBase pluginBase) - { - pluginBase.PluginPath = pluginPath; - } - - plugin.Initialize(); - Plugins.Add(plugin); - - LogHelper.WriteLogToFile($"已加载外部插件: {plugin.Name} v{plugin.Version} 来自 {Path.GetFileName(pluginPath)}"); - - return plugin; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"实例化插件类型 {pluginType.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - LogHelper.WriteLogToFile($"在程序集 {Path.GetFileName(pluginPath)} 中未找到有效的插件类型", LogHelper.LogType.Warning); - return null; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载外部插件 {Path.GetFileName(pluginPath)} 时出错: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 创建 ICCPP 插件适配器 - /// - /// 插件文件路径 - /// 适配的插件实例 - private IPlugin CreateICCPPPluginAdapter(string pluginPath) - { - try - { - // 读取插件文件内容 - byte[] pluginData = File.ReadAllBytes(pluginPath); - - // 创建适配器插件实例 - var pluginAdapter = new ICCPPPluginAdapter(pluginPath, pluginData); - - // 添加到插件列表 - Plugins.Add(pluginAdapter); - - LogHelper.WriteLogToFile($"已创建 ICCPP 插件适配器: {pluginAdapter.Name} 来自 {Path.GetFileName(pluginPath)}"); - - return pluginAdapter; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"创建 ICCPP 插件适配器时出错: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 加载插件程序集 - /// - /// 插件文件路径 - /// 加载的程序集 - private Assembly LoadPluginAssembly(string pluginPath) - { - try - { - // 检查是否已加载该程序集 - if (_loadedAssemblies.TryGetValue(pluginPath, out var loadedAssembly)) - { - return loadedAssembly; - } - - // 直接加载程序集 - Assembly pluginAssembly = Assembly.LoadFrom(pluginPath); - _loadedAssemblies[pluginPath] = pluginAssembly; - - return pluginAssembly; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载插件程序集 {Path.GetFileName(pluginPath)} 时出错: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - /// - /// 启用已配置为启用的插件 - /// - private void EnableConfiguredPlugins() - { - int enabledCount = 0; - int disabledCount = 0; - int errorCount = 0; - - foreach (var plugin in Plugins) - { - try - { - string pluginTypeName = plugin.GetType().FullName; - - // 检查配置中的插件状态 - if (PluginStates.TryGetValue(pluginTypeName, out bool enabled)) - { - // 获取当前实际状态 - bool currentState = plugin is PluginBase pluginBase && pluginBase.IsEnabled; - - // 如果配置状态与当前状态不一致,则应用配置状态 - if (currentState != enabled) - { - // 注册插件状态变更事件 - if (plugin is PluginBase pb) - { - pb.EnabledStateChanged += Plugin_EnabledStateChanged; - } - - if (enabled) - { - plugin.Enable(); - enabledCount++; - LogHelper.WriteLogToFile($"根据配置启用插件: {plugin.Name}"); - } - else - { - plugin.Disable(); - disabledCount++; - LogHelper.WriteLogToFile($"根据配置禁用插件: {plugin.Name}"); - } - } - else - { - // 状态一致,只注册事件 - if (plugin is PluginBase pb) - { - pb.EnabledStateChanged += Plugin_EnabledStateChanged; - } - } - } - else - { - // 插件不在配置中,添加默认状态(禁用) - PluginStates[pluginTypeName] = false; - _configDirty = true; - - // 注册插件状态变更事件 - if (plugin is PluginBase pb) - { - pb.EnabledStateChanged += Plugin_EnabledStateChanged; - } - - // 如果当前是启用状态,则禁用 - if (plugin is PluginBase pluginBase && pluginBase.IsEnabled) - { - plugin.Disable(); - disabledCount++; - LogHelper.WriteLogToFile($"插件不在配置中,默认禁用: {plugin.Name}"); - } - } - } - catch (Exception ex) - { - errorCount++; - LogHelper.WriteLogToFile($"应用插件 {plugin.Name} 配置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 如果有配置变更,启动自动保存 - if (_configDirty) - { - TriggerAutoSave(); - } - - LogHelper.WriteLogToFile($"已应用插件配置: 启用 {enabledCount} 个,禁用 {disabledCount} 个,错误 {errorCount} 个"); - } - - /// - /// 插件状态变更事件处理 - /// - private void Plugin_EnabledStateChanged(object sender, bool isEnabled) - { - try - { - if (sender is IPlugin plugin) - { - string pluginTypeName = plugin.GetType().FullName; - - // 更新配置状态 - if (!PluginStates.ContainsKey(pluginTypeName) || PluginStates[pluginTypeName] != isEnabled) - { - PluginStates[pluginTypeName] = isEnabled; - _configDirty = true; - - LogHelper.WriteLogToFile($"插件状态变更: {plugin.Name} = {(isEnabled ? "启用" : "禁用")}"); - - // 立即同步保存配置(不再使用延迟自动保存) - SaveConfig(); - LogHelper.WriteLogToFile($"插件 {plugin.Name} 状态已立即保存到配置文件"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理插件状态变更事件时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 触发自动保存计时器 - /// - private void TriggerAutoSave() - { - // 重置并启动计时器 - _autoSaveTimer.Stop(); - _autoSaveTimer.Start(); - } - - /// - /// 启动热重载监视器 - /// - private void StartHotReloadWatcher() - { - // 创建定时检查任务 - Task.Run(async () => - { - while (true) - { - try - { - // 每5秒检查一次 - await Task.Delay(5000); - - // 获取所有外部插件 - var externalPlugins = Plugins.OfType() - .Where(p => !p.IsBuiltIn && !string.IsNullOrEmpty(p.PluginPath)) - .ToList(); - - foreach (var plugin in externalPlugins) - { - // 检查插件文件是否存在 - if (!File.Exists(plugin.PluginPath)) - { - continue; - } - - // 计算当前文件哈希 - string currentHash = CalculateFileHash(plugin.PluginPath); - - // 比较哈希值是否变化 - if (_pluginHashes.TryGetValue(plugin.PluginPath, out string oldHash) && - !string.IsNullOrEmpty(oldHash) && - oldHash != currentHash) - { - // 文件已变化,执行热重载 - Application.Current.Dispatcher.Invoke(() => - { - ReloadPlugin(plugin); - }); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"热重载检查出错: {ex.Message}", LogHelper.LogType.Error); - } - } - }); - } - - /// - /// 重新加载插件 - /// - /// 要重新加载的插件 - private void ReloadPlugin(PluginBase plugin) - { - try - { - string pluginPath = plugin.PluginPath; - if (string.IsNullOrEmpty(pluginPath) || !File.Exists(pluginPath)) - { - LogHelper.WriteLogToFile($"无法重新加载插件 {plugin.Name}: 插件文件不存在", LogHelper.LogType.Error); - return; - } - - LogHelper.WriteLogToFile($"开始热重载插件: {plugin.Name} ({Path.GetFileName(pluginPath)})"); - - // 保存插件的当前状态 - bool wasEnabled = plugin.IsEnabled; - string pluginTypeName = plugin.GetType().FullName; - - // 卸载插件 - UnloadPlugin(plugin); - - // 从加载缓存中移除 - if (_loadedAssemblies.ContainsKey(pluginPath)) - { - _loadedAssemblies.Remove(pluginPath); - } - - // 计算新的文件哈希 - string newHash = CalculateFileHash(pluginPath); - _pluginHashes[pluginPath] = newHash; - - // 重新加载插件 - IPlugin newPlugin = LoadExternalPlugin(pluginPath); - - if (newPlugin != null) - { - // 恢复插件状态 - if (wasEnabled) - { - newPlugin.Enable(); - } - - // 更新配置(如果类型名称变化) - string newPluginTypeName = newPlugin.GetType().FullName; - if (pluginTypeName != newPluginTypeName && PluginStates.ContainsKey(pluginTypeName)) - { - bool state = PluginStates[pluginTypeName]; - PluginStates.Remove(pluginTypeName); - PluginStates[newPluginTypeName] = state; - _configDirty = true; - SaveConfig(); - } - - LogHelper.WriteLogToFile($"插件 {newPlugin.Name} v{newPlugin.Version} 热重载成功"); - - // 通知UI刷新 - NotifyUIRefresh(); - } - else - { - LogHelper.WriteLogToFile($"插件 {plugin.Name} 热重载失败", LogHelper.LogType.Error); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"重新加载插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 卸载插件 - /// - /// 要卸载的插件 - /// 是否从配置中移除 - public void UnloadPlugin(IPlugin plugin, bool removeFromConfig = false) - { - try - { - // 保存插件名称,以便在卸载后使用 - string pluginName = plugin.Name; - - // 如果插件已启用,先禁用它 - if (plugin is PluginBase pluginBase && pluginBase.IsEnabled) - { - plugin.Disable(); - } - - // 执行插件清理 - plugin.Cleanup(); - - // 从插件集合中移除 - Plugins.Remove(plugin); - - // 从配置中移除(如果需要) - if (removeFromConfig && plugin.GetType() != null) - { - string pluginTypeName = plugin.GetType().FullName; - if (PluginStates.ContainsKey(pluginTypeName)) - { - PluginStates.Remove(pluginTypeName); - SaveConfig(); - } - } - - LogHelper.WriteLogToFile($"已卸载插件: {pluginName}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 删除插件 - /// - /// 要删除的插件 - /// 删除是否成功 - public bool DeletePlugin(IPlugin plugin) - { - try - { - // 只能删除外部插件 - if (plugin.IsBuiltIn) - { - return false; - } - - // 保存插件名称,以便在删除后使用 - string pluginName = plugin.Name; - - // 获取插件路径 - string pluginPath = null; - if (plugin is PluginBase pluginBase) - { - pluginPath = pluginBase.PluginPath; - } - - if (string.IsNullOrEmpty(pluginPath) || !File.Exists(pluginPath)) - { - return false; - } - - // 卸载插件(并从配置中移除状态) - UnloadPlugin(plugin, true); - - // 删除插件文件 - File.Delete(pluginPath); - - // 清理缓存 - _loadedAssemblies.Remove(pluginPath); - _pluginHashes.Remove(pluginPath); - - // 保存配置 - SaveConfig(); - - LogHelper.WriteLogToFile($"已删除插件: {pluginName}"); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"删除插件时出错: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - /// - /// 切换插件启用状态 - /// - /// 目标插件 - /// 是否启用 - public void TogglePlugin(IPlugin plugin, bool enable) - { - try - { - // 检查当前状态是否已经是目标状态 - bool currentState = plugin is PluginBase pluginBase && pluginBase.IsEnabled; - if (currentState == enable) - { - // 已经是目标状态,无需操作 - LogHelper.WriteLogToFile($"插件 {plugin.Name} 已经是 {(enable ? "启用" : "禁用")} 状态,无需切换"); - return; - } - - // 记录插件信息,用于日志 - string pluginName = plugin.Name; - string pluginTypeName = plugin.GetType().FullName; - - LogHelper.WriteLogToFile($"开始切换插件 {pluginName} 状态为: {(enable ? "启用" : "禁用")}"); - - // 首先更新配置状态 - PluginStates[pluginTypeName] = enable; - _configDirty = true; - - // 更新插件状态 - try - { - // 注册事件(无需检查事件是否为null) - if (plugin is PluginBase pb) - { - // 先取消可能已有的订阅,避免重复订阅 - pb.EnabledStateChanged -= Plugin_EnabledStateChanged; - // 重新订阅 - pb.EnabledStateChanged += Plugin_EnabledStateChanged; - } - - // 更新插件状态 - if (enable) - { - plugin.Enable(); - LogHelper.WriteLogToFile($"插件 {pluginName} 已启用"); - } - else - { - // 禁用前先记录是否为内置插件 - bool isBuiltIn = plugin.IsBuiltIn; - LogHelper.WriteLogToFile($"尝试禁用{(isBuiltIn ? "内置" : "外部")}插件 {pluginName}"); - - // 禁用插件 - plugin.Disable(); - - // 禁用后立即检查状态,确保禁用成功 - bool actuallyDisabled = !(plugin is PluginBase pb2 && pb2.IsEnabled); - if (!actuallyDisabled) - { - LogHelper.WriteLogToFile($"警告: 插件 {pluginName} 禁用失败,再次尝试禁用", LogHelper.LogType.Warning); - plugin.Disable(); // 再次尝试禁用 - - // 再次检查 - actuallyDisabled = !(plugin is PluginBase pb3 && pb3.IsEnabled); - if (!actuallyDisabled) - { - LogHelper.WriteLogToFile($"错误: 插件 {pluginName} 禁用失败,强制设置禁用状态", LogHelper.LogType.Error); - // 强制设置状态 - if (plugin is PluginBase pb4) - { - // 使用反射强制设置禁用状态 - var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); - if (enabledProperty != null) - { - enabledProperty.SetValue(pb4, false); - LogHelper.WriteLogToFile($"已通过反射强制设置插件 {pluginName} 为禁用状态"); - } - } - } - } - - LogHelper.WriteLogToFile($"插件 {pluginName} 已禁用"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"更改插件 {pluginName} 状态时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 立即保存配置 - SaveConfigAsync().ConfigureAwait(false); - - // 插件状态切换后,始终进行重载(无论是启用还是禁用) - if (plugin is PluginBase pluginInstance) - { - // 对于内置插件,执行专门的处理 - if (pluginInstance.IsBuiltIn) - { - LogHelper.WriteLogToFile($"处理内置插件 {pluginName} 状态变更"); - - // 对于内置插件,我们需要确保状态正确应用 - bool finalState = pluginInstance.IsEnabled; - bool expectedState = enable; - - if (finalState != expectedState) - { - LogHelper.WriteLogToFile($"内置插件状态不匹配: 当前={finalState}, 期望={expectedState},尝试纠正", LogHelper.LogType.Warning); - - // 再次尝试设置状态 - if (expectedState) - { - plugin.Enable(); - } - else - { - plugin.Disable(); - - // 最后一次检查,如果仍然不匹配,强制设置 - if (pluginInstance.IsEnabled != expectedState) - { - var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); - if (enabledProperty != null) - { - enabledProperty.SetValue(pluginInstance, expectedState); - LogHelper.WriteLogToFile($"已通过反射强制设置内置插件 {pluginName} 状态为 {(expectedState ? "启用" : "禁用")}"); - } - } - } - } - - // 通知UI刷新 - NotifyUIRefresh(); - } - else - { - // 外部插件,执行热重载 - try - { - if (!string.IsNullOrEmpty(pluginInstance.PluginPath) && File.Exists(pluginInstance.PluginPath)) - { - LogHelper.WriteLogToFile($"开始重载外部插件 {pluginName}"); - - // 使用调度器确保在UI线程执行热重载 - if (Application.Current != null && Application.Current.Dispatcher != null) - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - ReloadPlugin(pluginInstance); - LogHelper.WriteLogToFile($"插件 {pluginName} 已重载以应用{(enable ? "启用" : "禁用")}状态"); - })); - } - else - { - // 当前不在UI线程,直接重载 - ReloadPlugin(pluginInstance); - LogHelper.WriteLogToFile($"插件 {pluginName} 已重载以应用{(enable ? "启用" : "禁用")}状态"); - } - } - else - { - LogHelper.WriteLogToFile($"外部插件 {pluginName} 文件不存在,无法重载", LogHelper.LogType.Warning); - NotifyUIRefresh(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"重载插件 {pluginName} 时出错: {ex.Message}", LogHelper.LogType.Error); - // 出错时也要刷新UI - NotifyUIRefresh(); - } - } - } - else - { - // 通知UI刷新 - NotifyUIRefresh(); - } - - LogHelper.WriteLogToFile($"插件 {pluginName} 状态切换完成"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"切换插件状态时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 应用插件实时状态 - /// - /// 目标插件 - /// 是否启用 - private void ApplyPluginRealTimeState(IPlugin plugin, bool enable) - { - try - { - // 确保当前实例状态正确 - bool currentState = plugin is PluginBase pluginBase && pluginBase.IsEnabled; - if (currentState != enable) - { - if (enable) - { - plugin.Enable(); - LogHelper.WriteLogToFile($"实时应用: 已启用插件 {plugin.Name}"); - } - else - { - plugin.Disable(); - LogHelper.WriteLogToFile($"实时应用: 已禁用插件 {plugin.Name}"); - } - - // 同步状态到插件自身的配置 - if (plugin is PluginBase pluginSettings) - { - try - { - // 保存插件设置(与启用状态无关) - pluginSettings.SavePluginSettings(); - LogHelper.WriteLogToFile($"实时应用: 已保存插件 {plugin.Name} 设置"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"实时应用: 保存插件 {plugin.Name} 设置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } - - // 对于外部插件,尝试执行热重载以确保状态立即生效 - if (plugin is PluginBase externalPlugin && !externalPlugin.IsBuiltIn) - { - string pluginPath = externalPlugin.PluginPath; - if (!string.IsNullOrEmpty(pluginPath) && File.Exists(pluginPath)) - { - // 记录插件类型名称,用于后续状态检查 - string pluginTypeName = plugin.GetType().FullName; - bool targetState = enable; - - // 使用调度器确保在UI线程执行热重载 - if (Application.Current != null && Application.Current.Dispatcher != null) - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - try - { - // 热重载前再次确认配置状态正确 - if (PluginStates.TryGetValue(pluginTypeName, out bool storedStateUi) && storedStateUi != targetState) - { - LogHelper.WriteLogToFile($"热重载前发现状态不一致,修正配置: {plugin.Name}, 配置={storedStateUi}, 目标={targetState}", LogHelper.LogType.Warning); - PluginStates[pluginTypeName] = targetState; - SaveConfig(); - } - - // 执行热重载 - ReloadPlugin(externalPlugin); - LogHelper.WriteLogToFile($"插件 {plugin.Name} 已成功热重载以应用实时状态"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"热重载插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - })); - } - else - { - // 当前不在UI线程,直接重载 - // 热重载前再次确认配置状态正确 - if (PluginStates.TryGetValue(pluginTypeName, out bool storedStateNonUi) && storedStateNonUi != targetState) - { - LogHelper.WriteLogToFile($"热重载前发现状态不一致,修正配置: {plugin.Name}, 配置={storedStateNonUi}, 目标={targetState}", LogHelper.LogType.Warning); - PluginStates[pluginTypeName] = targetState; - SaveConfig(); - } - - ReloadPlugin(externalPlugin); - } - } - } - - LogHelper.WriteLogToFile($"插件 {plugin.Name} 实时状态已应用: {(enable ? "启用" : "禁用")}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用插件实时状态时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 通知UI刷新 - /// - private void NotifyUIRefresh() - { - try - { - // 通知UI刷新 - if (Application.Current != null && Application.Current.Dispatcher != null) - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => { - // 通知任何可能打开的插件设置窗口刷新 - foreach (Window window in Application.Current.Windows) - { - if (window is PluginSettingsWindow pluginWindow) - { - pluginWindow.RefreshPluginList(); - break; - } - } - })); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"通知UI刷新时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 加载插件配置 - /// - private void LoadConfig() - { - const int maxRetries = 3; // 最大重试次数 - const int retryDelayMs = 300; // 重试延迟时间(毫秒) - - LogHelper.WriteLogToFile($"开始从配置文件加载插件状态: {PluginConfigFile}"); - - // 确保至少有一个默认配置 - Dictionary defaultConfig = new Dictionary(); - - // 尝试获取配置锁 - _configLock.Wait(); - - try - { - for (int attempt = 1; attempt <= maxRetries; attempt++) - { - try - { - if (File.Exists(PluginConfigFile)) - { - string json; - // 使用共享读取模式,允许其他进程同时读取但不允许写入 - using (FileStream fs = new FileStream(PluginConfigFile, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (StreamReader reader = new StreamReader(fs)) - { - json = reader.ReadToEnd(); - } - - var loadedStates = JsonConvert.DeserializeObject>(json); - - if (loadedStates != null && loadedStates.Count > 0) - { - PluginStates = loadedStates; - _configDirty = false; // 重置脏标记 - LogHelper.WriteLogToFile($"成功从配置文件加载了 {PluginStates.Count} 个插件状态"); - } - else - { - LogHelper.WriteLogToFile("配置文件解析为空,尝试使用备份", LogHelper.LogType.Warning); - // 尝试加载备份 - if (File.Exists(PluginConfigBackupFile)) - { - try - { - string backupJson = File.ReadAllText(PluginConfigBackupFile); - var backupStates = JsonConvert.DeserializeObject>(backupJson); - - if (backupStates != null && backupStates.Count > 0) - { - PluginStates = backupStates; - _configDirty = true; // 从备份加载,需要重新保存主配置 - LogHelper.WriteLogToFile($"已从备份恢复 {PluginStates.Count} 个插件状态"); - return; // 成功从备份加载,提前退出 - } - } - catch (Exception backupEx) - { - LogHelper.WriteLogToFile($"从备份恢复配置失败: {backupEx.Message}", LogHelper.LogType.Error); - } - } - - // 备份也失败,使用默认配置 - PluginStates = defaultConfig; - _configDirty = true; - } - } - else - { - LogHelper.WriteLogToFile($"配置文件不存在,尝试使用备份: {PluginConfigFile}", LogHelper.LogType.Warning); - - // 尝试加载备份 - if (File.Exists(PluginConfigBackupFile)) - { - try - { - string backupJson = File.ReadAllText(PluginConfigBackupFile); - var backupStates = JsonConvert.DeserializeObject>(backupJson); - - if (backupStates != null && backupStates.Count > 0) - { - PluginStates = backupStates; - _configDirty = true; // 从备份加载,需要重新保存主配置 - LogHelper.WriteLogToFile($"已从备份恢复 {PluginStates.Count} 个插件状态"); - return; // 成功从备份加载,提前退出 - } - } - catch (Exception backupEx) - { - LogHelper.WriteLogToFile($"从备份恢复配置失败: {backupEx.Message}", LogHelper.LogType.Error); - } - } - - PluginStates = defaultConfig; - _configDirty = true; - LogHelper.WriteLogToFile("使用默认空配置", LogHelper.LogType.Warning); - } - - // 没有成功加载或使用备份,使用默认配置 - break; - } - catch (Exception ex) - { - if (attempt < maxRetries) - { - LogHelper.WriteLogToFile($"加载配置失败 (尝试 {attempt}/{maxRetries}): {ex.Message},将在 {retryDelayMs}ms 后重试", LogHelper.LogType.Warning); - Thread.Sleep(retryDelayMs); - } - else - { - LogHelper.WriteLogToFile($"加载插件配置失败,已达最大重试次数 ({maxRetries}): {ex.Message}", LogHelper.LogType.Error); - - // 最终失败,使用默认配置 - PluginStates = defaultConfig; - _configDirty = true; - } - } - } - } - finally - { - // 释放配置锁 - _configLock.Release(); - } - } - - /// - /// 异步保存插件配置 - /// - public async Task SaveConfigAsync() - { - // 如果配置没有变化,无需保存 - if (!_configDirty) - { - return; - } - - // 尝试获取配置锁(异步) - if (!await _configLock.WaitAsync(0)) - { - // 已有保存操作在进行中,触发自动保存延迟 - TriggerAutoSave(); - return; - } - - try - { - // 创建配置任务 - await Task.Run(() => SaveConfig()); - } - finally - { - // 释放配置锁 - _configLock.Release(); - } - } - - /// - /// 保存插件配置 - /// - public void SaveConfig() - { - // 如果配置没有变化,无需保存 - if (!_configDirty) - { - return; - } - - const int maxRetries = 3; // 最大重试次数 - const int retryDelayMs = 500; // 重试延迟时间(毫秒) - - try - { - LogHelper.WriteLogToFile($"开始保存插件配置到: {PluginConfigFile}"); - - // 生成JSON数据 - string json = JsonConvert.SerializeObject(PluginStates, Formatting.Indented); - string tempFile = PluginConfigFile + ".temp"; // 临时文件路径 - - // 确保目录存在 - string configDir = Path.GetDirectoryName(PluginConfigFile); - if (!Directory.Exists(configDir)) - { - Directory.CreateDirectory(configDir); - LogHelper.WriteLogToFile($"创建配置目录: {configDir}"); - } - - // 先备份当前配置 - try - { - if (File.Exists(PluginConfigFile)) - { - File.Copy(PluginConfigFile, PluginConfigBackupFile, true); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"备份配置文件失败: {ex.Message}", LogHelper.LogType.Warning); - } - - for (int attempt = 1; attempt <= maxRetries; attempt++) - { - try - { - // 直接写入目标文件 - File.WriteAllText(PluginConfigFile, json); - - // 验证写入是否成功 - if (File.Exists(PluginConfigFile)) - { - // 重置脏标记 - _configDirty = false; - LogHelper.WriteLogToFile($"插件配置已成功保存到磁盘: {PluginConfigFile}, 共 {PluginStates.Count} 个插件状态"); - return; - } - } - catch (Exception ex) - { - if (attempt < maxRetries) - { - LogHelper.WriteLogToFile($"保存配置失败 (尝试 {attempt}/{maxRetries}): {ex.Message},将在 {retryDelayMs}ms 后重试", LogHelper.LogType.Warning); - Thread.Sleep(retryDelayMs); - } - else - { - LogHelper.WriteLogToFile($"保存插件配置失败,已达最大重试次数 ({maxRetries}): {ex.Message}", LogHelper.LogType.Error); - - // 尝试使用临时文件方式 - try - { - // 删除可能存在的旧临时文件 - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } - - // 写入临时文件 - File.WriteAllText(tempFile, json); - - // 如果目标文件存在,先删除 - if (File.Exists(PluginConfigFile)) - { - File.Delete(PluginConfigFile); - } - - // 重命名临时文件 - File.Move(tempFile, PluginConfigFile); - - // 重置脏标记 - _configDirty = false; - LogHelper.WriteLogToFile($"使用临时文件方式成功保存配置: {PluginConfigFile}"); - return; - } - catch (Exception fallbackEx) - { - LogHelper.WriteLogToFile($"临时文件保存方式也失败: {fallbackEx.Message}", LogHelper.LogType.Error); - } - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存插件配置时发生未处理异常: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 计算文件哈希 - /// - /// 文件路径 - /// 文件哈希值 - private string CalculateFileHash(string filePath) - { - try - { - using (var md5 = MD5.Create()) - using (var stream = File.OpenRead(filePath)) - { - byte[] hash = md5.ComputeHash(stream); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"计算文件哈希值时出错: {ex.Message}", LogHelper.LogType.Error); - return string.Empty; - } - } - - /// - /// 从配置文件重新加载所有插件状态并应用 - /// - public void ReloadPluginsFromConfig() - { - try - { - LogHelper.WriteLogToFile("开始从配置文件重新加载插件状态"); - - // 保存当前配置状态,以便在加载失败时回滚 - Dictionary previousStates = new Dictionary(PluginStates); - - // 重新加载配置文件 - LoadConfig(); - - // 如果配置文件加载失败,PluginStates可能为空,这时使用之前的状态 - if (PluginStates == null || PluginStates.Count == 0) - { - LogHelper.WriteLogToFile("加载的配置为空,恢复到之前的状态", LogHelper.LogType.Warning); - PluginStates = previousStates; - return; - } - - LogHelper.WriteLogToFile($"已加载 {PluginStates.Count} 个插件状态,开始应用..."); - - // 对比配置,查找变更的插件 - foreach (var plugin in Plugins.ToList()) // 创建副本进行遍历,避免集合修改异常 - { - string pluginTypeName = plugin.GetType().FullName; - - // 检查插件在配置中是否存在 - if (PluginStates.TryGetValue(pluginTypeName, out bool shouldBeEnabled)) - { - bool currentlyEnabled = plugin is PluginBase pluginBase && pluginBase.IsEnabled; - - // 如果状态需要变更 - if (currentlyEnabled != shouldBeEnabled) - { - LogHelper.WriteLogToFile($"应用插件 {plugin.Name} 的配置状态: {(shouldBeEnabled ? "启用" : "禁用")}"); - - if (shouldBeEnabled) - { - try - { - plugin.Enable(); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启用插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - else - { - try - { - // 记录禁用信息,特别是内置插件 - bool isBuiltIn = plugin.IsBuiltIn; - LogHelper.WriteLogToFile($"尝试禁用{(isBuiltIn ? "内置" : "外部")}插件 {plugin.Name}"); - - // 禁用插件 - plugin.Disable(); - - // 对于内置插件,特别检查禁用状态 - if (isBuiltIn && plugin is PluginBase builtInPluginBase) - { - if (builtInPluginBase.IsEnabled) - { - LogHelper.WriteLogToFile($"内置插件 {plugin.Name} 禁用失败,尝试强制禁用", LogHelper.LogType.Warning); - // 强制设置禁用状态 - var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); - if (enabledProperty != null) - { - enabledProperty.SetValue(builtInPluginBase, false); - LogHelper.WriteLogToFile($"已通过反射强制禁用内置插件 {plugin.Name}"); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"禁用插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 如果是外部插件,执行重载 - if (!plugin.IsBuiltIn && plugin is PluginBase externalPlugin && !string.IsNullOrEmpty(externalPlugin.PluginPath)) - { - try - { - ReloadPlugin(externalPlugin); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"重载外部插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } - } - else - { - // 插件不在配置中,将其添加为禁用状态 - PluginStates[pluginTypeName] = false; - LogHelper.WriteLogToFile($"插件 {plugin.Name} 不在配置中,默认设置为禁用状态"); - - // 如果当前是启用状态,则禁用它 - if (plugin is PluginBase pluginBase && pluginBase.IsEnabled) - { - try - { - bool isBuiltIn = plugin.IsBuiltIn; - LogHelper.WriteLogToFile($"尝试禁用未配置的{(isBuiltIn ? "内置" : "外部")}插件 {plugin.Name}"); - - plugin.Disable(); - - // 对于内置插件,特别检查禁用状态 - if (isBuiltIn && pluginBase.IsEnabled) - { - LogHelper.WriteLogToFile($"未配置的内置插件 {plugin.Name} 禁用失败,尝试强制禁用", LogHelper.LogType.Warning); - // 强制设置禁用状态 - var enabledProperty = typeof(PluginBase).GetProperty("IsEnabled"); - if (enabledProperty != null) - { - enabledProperty.SetValue(pluginBase, false); - LogHelper.WriteLogToFile($"已通过反射强制禁用未配置的内置插件 {plugin.Name}"); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"禁用未配置插件 {plugin.Name} 时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } - } - - // 保存更新后的配置 - SaveConfig(); - - // 通知UI更新 - if (Application.Current != null && Application.Current.Dispatcher != null) - { - Application.Current.Dispatcher.Invoke(() => { - // 通知任何可能打开的插件设置窗口刷新 - foreach (Window window in Application.Current.Windows) - { - if (window is PluginSettingsWindow pluginWindow) - { - pluginWindow.RefreshPluginList(); - } - } - }); - } - - LogHelper.WriteLogToFile("插件状态已从配置文件重新加载完成"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"从配置文件重新加载插件状态时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/Plugins/PluginTemplate.cs b/Ink Canvas/Helpers/Plugins/PluginTemplate.cs deleted file mode 100644 index ba28dee4..00000000 --- a/Ink Canvas/Helpers/Plugins/PluginTemplate.cs +++ /dev/null @@ -1,276 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Controls; - -namespace Ink_Canvas.Helpers.Plugins -{ - /// - /// 插件模板,用于开发者参考 - /// 注意:实际开发时,请将此类移到单独的程序集中 - /// - public class PluginTemplate : PluginBase - { - #region 插件基本信息 - - /// - /// 插件名称 - /// - public override string Name => "插件模板"; - - /// - /// 插件描述 - /// - public override string Description => "这是一个插件开发模板,用于开发者参考。"; - - /// - /// 插件版本 - /// - public override Version Version => new Version(1, 0, 0); - - /// - /// 插件作者 - /// - public override string Author => "Your Name"; - - /// - /// 是否为内置插件(外部插件请返回false) - /// - public override bool IsBuiltIn => false; - - #endregion - - #region 插件生命周期 - - /// - /// 插件初始化 - /// 在这里进行插件的初始化工作,如加载配置、注册事件等 - /// - public override void Initialize() - { - // 先调用基类方法,这样会设置插件ID和记录日志 - base.Initialize(); - - // TODO: 在这里进行插件初始化工作 - - // 示例:记录初始化信息 - LogHelper.WriteLogToFile($"插件 {Name} 开始初始化"); - - // 示例:加载配置 - LoadConfig(); - - // 示例:注册自定义事件 - // MainWindow.Instance.SomeEvent += OnSomeEvent; - - LogHelper.WriteLogToFile($"插件 {Name} 初始化完成"); - } - - /// - /// 启用插件 - /// 在这里激活插件功能 - /// - public override void Enable() - { - // 先调用基类方法,这样会设置插件状态和记录日志 - base.Enable(); - - // TODO: 在这里启用插件功能 - - LogHelper.WriteLogToFile($"插件 {Name} 已启用"); - } - - /// - /// 禁用插件 - /// 在这里停用插件功能 - /// - public override void Disable() - { - // 先调用基类方法,这样会设置插件状态和记录日志 - base.Disable(); - - // TODO: 在这里禁用插件功能 - - LogHelper.WriteLogToFile($"插件 {Name} 已禁用"); - } - - /// - /// 清理资源 - /// 在插件卸载时调用,清理资源 - /// - public override void Cleanup() - { - // TODO: 在这里清理插件资源 - - // 示例:取消注册事件 - // MainWindow.Instance.SomeEvent -= OnSomeEvent; - - // 示例:保存配置 - SaveConfig(); - - // 最后调用基类方法 - base.Cleanup(); - } - - #endregion - - #region 插件配置 - - /// - /// 加载插件配置 - /// - private void LoadConfig() - { - try - { - // TODO: 从文件或其他位置加载配置 - // 示例: - // string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json"); - // if (File.Exists(configPath)) - // { - // string json = File.ReadAllText(configPath); - // YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject(json); - // } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 保存插件配置 - /// - private void SaveConfig() - { - try - { - // TODO: 保存配置到文件或其他位置 - // 示例: - // string configDir = Path.Combine(App.RootPath, "PluginConfigs"); - // if (!Directory.Exists(configDir)) - // { - // Directory.CreateDirectory(configDir); - // } - // string configPath = Path.Combine(configDir, "YourPluginName.json"); - // string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented); - // File.WriteAllText(configPath, json); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - #endregion - - #region 插件设置界面 - - /// - /// 获取插件设置界面 - /// - /// 插件设置界面 - public override UserControl GetSettingsView() - { - // 创建插件设置界面 - return new PluginTemplateSettingsControl(); - } - - #endregion - - #region 插件功能方法 - - // TODO: 在这里添加插件的具体功能方法 - - /// - /// 示例方法:执行一些功能 - /// - public void DoSomething() - { - if (!IsEnabled) return; - - try - { - // TODO: 实现你的功能 - MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - #endregion - } - - /// - /// 插件设置控件 - /// - public class PluginTemplateSettingsControl : UserControl - { - public PluginTemplateSettingsControl() - { - // 创建设置界面布局 - var panel = new StackPanel - { - Margin = new Thickness(10) - }; - - // 添加标题 - panel.Children.Add(new TextBlock - { - Text = "插件模板设置", - FontSize = 16, - FontWeight = FontWeights.Bold, - Margin = new Thickness(0, 0, 0, 10) - }); - - // 添加说明文字 - panel.Children.Add(new TextBlock - { - Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。", - TextWrapping = TextWrapping.Wrap, - Margin = new Thickness(0, 0, 0, 15) - }); - - // 添加示例设置选项 - var checkBox = new CheckBox - { - Content = "启用某项功能", - Margin = new Thickness(0, 0, 0, 10) - }; - panel.Children.Add(checkBox); - - // 添加文本输入框 - panel.Children.Add(new TextBlock - { - Text = "设置项:", - Margin = new Thickness(0, 5, 0, 5) - }); - - panel.Children.Add(new TextBox - { - Margin = new Thickness(0, 0, 0, 10), - Width = 200, - HorizontalAlignment = HorizontalAlignment.Left - }); - - // 添加按钮 - var button = new Button - { - Content = "保存设置", - Padding = new Thickness(10, 5, 10, 5), - Margin = new Thickness(0, 10, 0, 0), - HorizontalAlignment = HorizontalAlignment.Left - }; - - button.Click += (sender, e) => - { - MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information); - }; - - panel.Children.Add(button); - - // 设置控件内容 - Content = panel; - } - } -} \ No newline at end of file diff --git a/Ink Canvas/Helpers/StartupCount.cs b/Ink Canvas/Helpers/StartupCount.cs deleted file mode 100644 index 4bb46fda..00000000 --- a/Ink Canvas/Helpers/StartupCount.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.IO; - -namespace Ink_Canvas.Helpers -{ - public static class StartupCount - { - private static readonly string CountFilePath = Path.Combine(App.RootPath, "startup-count"); - private static readonly object fileLock = new object(); - - public static int GetCount() - { - try - { - if (File.Exists(CountFilePath)) - { - var text = File.ReadAllText(CountFilePath).Trim(); - if (int.TryParse(text, out int count)) - return count; - } - } - catch { } - return 0; - } - - public static void Increment() - { - lock (fileLock) - { - int count = GetCount() + 1; - try - { - File.WriteAllText(CountFilePath, count.ToString()); - } - catch { } - } - } - - public static void Reset() - { - lock (fileLock) - { - try - { - if (File.Exists(CountFilePath)) - File.Delete(CountFilePath); - } - catch { } - } - } - } -} diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj deleted file mode 100644 index 4c53cecb..00000000 --- a/Ink Canvas/InkCanvasForClass.csproj +++ /dev/null @@ -1,575 +0,0 @@ - - - win;win-x86;win-x64;win-arm64 - WinExe - Ink_Canvas - InkCanvasForClass - net472 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - true - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 2 - 2.0.2.%2a - false - false - False - true - Debug;Release;x86 Debug - - - embedded - bin\$(Configuration)\ - True - - - embedded - bin\$(Configuration)\ - True - - - embedded - bin\$(Configuration)\ - True - - - Resources\icc.ico - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - pdbonly - 7.3 - true - - - app.manifest - InkCanvasForClass - 5.0.4 - Dubi906w - InkCanvasForClass - © Copyright HARKOTEK Studio 2024-now - https://icc.bliemhax.com - bundled - False - AnyCPU - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - pdbonly - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - pdbonly - 7.3 - true - - - - .\IACore.dll - False - - - .\IALoader.dll - False - - - .\IAWinFX.dll - False - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} - 1 - 0 - 0 - tlbimp - False - True - - - {00020430-0000-0000-C000-000000000046} - 2 - 0 - 0 - primary - False - True - - - {0002E157-0000-0000-C000-000000000046} - 5 - 3 - 0 - primary - False - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - \ No newline at end of file diff --git a/Ink Canvas/InkCanvasForClass.csproj.user b/Ink Canvas/InkCanvasForClass.csproj.user deleted file mode 100644 index 92ed6d11..00000000 --- a/Ink Canvas/InkCanvasForClass.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - <_LastSelectedProfileId>D:\vs\ica\Ink Canvas\Properties\PublishProfiles\FolderProfile.pubxml - - \ No newline at end of file diff --git a/Ink Canvas/InkCanvasForClass_mlbp2gn0_wpftmp.csproj b/Ink Canvas/InkCanvasForClass_mlbp2gn0_wpftmp.csproj deleted file mode 100644 index ddd440ee..00000000 --- a/Ink Canvas/InkCanvasForClass_mlbp2gn0_wpftmp.csproj +++ /dev/null @@ -1,449 +0,0 @@ - - - InkCanvasForClass - obj\Debug\ - obj\ - C:\Users\Administrator\Desktop\ICC CE\ICC CE main\community\Ink Canvas\obj\ - <_TargetAssemblyProjectName>InkCanvasForClass - Ink_Canvas - - - - win;win-x86;win-x64;win-arm64 - WinExe - Ink_Canvas - net472 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - true - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 2 - 2.0.2.%2a - false - false - False - true - Debug;Release;x86 Debug - - - embedded - bin\$(Configuration)\ - True - - - embedded - bin\$(Configuration)\ - True - - - embedded - bin\$(Configuration)\ - True - - - Resources\icc.ico - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - pdbonly - 7.3 - true - - - app.manifest - InkCanvasForClass - 5.0.4 - Dubi906w - InkCanvasForClass - © Copyright HARKOTEK Studio 2024-now - https://icc.bliemhax.com - bundled - False - AnyCPU - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - pdbonly - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - full - 7.3 - true - - - bin\$(Platform)\$(Configuration)\ - pdbonly - 7.3 - true - - - - - - - - - - - - - - - - - - - - - - {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} - 1 - 0 - 0 - tlbimp - False - True - - - {00020430-0000-0000-C000-000000000046} - 2 - 0 - 0 - primary - False - True - - - {0002E157-0000-0000-C000-000000000046} - 5 - 3 - 0 - primary - False - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - - - True - - - True - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs deleted file mode 100644 index 4691bd5d..00000000 --- a/Ink Canvas/MainWindow.xaml.cs +++ /dev/null @@ -1,1621 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Forms; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; -using System.Windows.Threading; -using Ink_Canvas.Helpers; -using Ink_Canvas.Helpers.Plugins; -using Ink_Canvas.Windows; -using iNKORE.UI.WPF.Modern; -using iNKORE.UI.WPF.Modern.Controls; -using Microsoft.Win32; -using Application = System.Windows.Application; -using File = System.IO.File; -using MessageBox = System.Windows.MessageBox; -using Brushes = System.Windows.Media.Brushes; -using Button = System.Windows.Controls.Button; -using Cursor = System.Windows.Input.Cursor; -using Cursors = System.Windows.Input.Cursors; -using DpiChangedEventArgs = System.Windows.DpiChangedEventArgs; -using GroupBox = System.Windows.Controls.GroupBox; -using Point = System.Windows.Point; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - // 新增:每一页一个Canvas对象 - private List whiteboardPages = new List(); - private int currentPageIndex; - private System.Windows.Controls.Canvas currentCanvas; - private AutoUpdateHelper.UpdateLineGroup AvailableLatestLineGroup; - - - - #region Window Initialization - - public MainWindow() { - /* - 处于画板模式内:Topmost == false / currentMode != 0 - 处于 PPT 放映内:BtnPPTSlideShowEnd.Visibility - */ - InitializeComponent(); - - BlackboardLeftSide.Visibility = Visibility.Collapsed; - BlackboardCenterSide.Visibility = Visibility.Collapsed; - BlackboardRightSide.Visibility = Visibility.Collapsed; - BorderTools.Visibility = Visibility.Collapsed; - BorderSettings.Visibility = Visibility.Collapsed; - LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed; - BorderSettings.Margin = new Thickness(0, 0, 0, 0); - TwoFingerGestureBorder.Visibility = Visibility.Collapsed; - BoardTwoFingerGestureBorder.Visibility = Visibility.Collapsed; - BorderDrawShape.Visibility = Visibility.Collapsed; - BoardBorderDrawShape.Visibility = Visibility.Collapsed; - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - - //if (!App.StartArgs.Contains("-o")) - - ViewBoxStackPanelMain.Visibility = Visibility.Collapsed; - ViewBoxStackPanelShapes.Visibility = Visibility.Collapsed; - var workingArea = Screen.PrimaryScreen.WorkingArea; - ViewboxFloatingBar.Margin = new Thickness( - (workingArea.Width - 284) / 2, - workingArea.Bottom - 60 - workingArea.Top, - -2000, -200); - ViewboxFloatingBarMarginAnimation(100, true); - - try { - if (File.Exists("debug.ini")) Label.Visibility = Visibility.Visible; - } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - } - - try { - if (File.Exists("Log.txt")) { - var fileInfo = new FileInfo("Log.txt"); - var fileSizeInKB = fileInfo.Length / 1024; - if (fileSizeInKB > 512) - try { - File.Delete("Log.txt"); - LogHelper.WriteLogToFile( - "The Log.txt file has been successfully deleted. Original file size: " + fileSizeInKB + - " KB"); - } - catch (Exception ex) { - LogHelper.WriteLogToFile( - ex + " | Can not delete the Log.txt file. File size: " + fileSizeInKB + " KB", - LogHelper.LogType.Error); - } - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - } - - InitTimers(); - timeMachine.OnRedoStateChanged += TimeMachine_OnRedoStateChanged; - timeMachine.OnUndoStateChanged += TimeMachine_OnUndoStateChanged; - inkCanvas.Strokes.StrokesChanged += StrokesOnStrokesChanged; - - SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; - try { - if (File.Exists("SpecialVersion.ini")) SpecialVersionResetToSuggestion_Click(); - } - catch (Exception ex) { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - } - - CheckColorTheme(true); - CheckPenTypeUIState(); - - // 初始化墨迹平滑管理器 - _inkSmoothingManager = new InkSmoothingManager(Dispatcher); - - // 注册输入事件 - inkCanvas.PreviewMouseDown += inkCanvas_PreviewMouseDown; - inkCanvas.StylusDown += inkCanvas_StylusDown; - inkCanvas.MouseRightButtonUp += InkCanvas_MouseRightButtonUp; - - // 初始化第一页Canvas - var firstCanvas = new System.Windows.Controls.Canvas(); - whiteboardPages.Add(firstCanvas); - InkCanvasGridForInkReplay.Children.Add(firstCanvas); - currentPageIndex = 0; - ShowPage(currentPageIndex); - - // 手动实现触摸滑动 - double leftTouchStartY = 0; - double leftScrollStartOffset = 0; - bool leftIsTouching = false; - BlackBoardLeftSidePageListScrollViewer.TouchDown += (s, e) => { - leftIsTouching = true; - leftTouchStartY = e.GetTouchPoint(BlackBoardLeftSidePageListScrollViewer).Position.Y; - leftScrollStartOffset = BlackBoardLeftSidePageListScrollViewer.VerticalOffset; - BlackBoardLeftSidePageListScrollViewer.CaptureTouch(e.TouchDevice); - e.Handled = true; - }; - BlackBoardLeftSidePageListScrollViewer.TouchMove += (s, e) => { - if (leftIsTouching) { - double currentY = e.GetTouchPoint(BlackBoardLeftSidePageListScrollViewer).Position.Y; - double delta = leftTouchStartY - currentY; - BlackBoardLeftSidePageListScrollViewer.ScrollToVerticalOffset(leftScrollStartOffset + delta); - e.Handled = true; - } - }; - BlackBoardLeftSidePageListScrollViewer.TouchUp += (s, e) => { - leftIsTouching = false; - BlackBoardLeftSidePageListScrollViewer.ReleaseTouchCapture(e.TouchDevice); - e.Handled = true; - }; - double rightTouchStartY = 0; - double rightScrollStartOffset = 0; - bool rightIsTouching = false; - BlackBoardRightSidePageListScrollViewer.TouchDown += (s, e) => { - rightIsTouching = true; - rightTouchStartY = e.GetTouchPoint(BlackBoardRightSidePageListScrollViewer).Position.Y; - rightScrollStartOffset = BlackBoardRightSidePageListScrollViewer.VerticalOffset; - BlackBoardRightSidePageListScrollViewer.CaptureTouch(e.TouchDevice); - e.Handled = true; - }; - BlackBoardRightSidePageListScrollViewer.TouchMove += (s, e) => { - if (rightIsTouching) { - double currentY = e.GetTouchPoint(BlackBoardRightSidePageListScrollViewer).Position.Y; - double delta = rightTouchStartY - currentY; - BlackBoardRightSidePageListScrollViewer.ScrollToVerticalOffset(rightScrollStartOffset + delta); - e.Handled = true; - } - }; - BlackBoardRightSidePageListScrollViewer.TouchUp += (s, e) => { - rightIsTouching = false; - BlackBoardRightSidePageListScrollViewer.ReleaseTouchCapture(e.TouchDevice); - e.Handled = true; - }; - // 初始化无焦点模式开关 - ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode; - ApplyNoFocusMode(); - } - - - - #endregion - - #region Ink Canvas Functions - - private Color Ink_DefaultColor = Colors.Red; - - private DrawingAttributes drawingAttributes; - private InkSmoothingManager _inkSmoothingManager; - - private void loadPenCanvas() { - try { - //drawingAttributes = new DrawingAttributes(); - drawingAttributes = inkCanvas.DefaultDrawingAttributes; - drawingAttributes.Color = Ink_DefaultColor; - - - drawingAttributes.Height = 2.5; - drawingAttributes.Width = 2.5; - drawingAttributes.IsHighlighter = false; - // 默认使用高级贝塞尔曲线平滑,如果未启用则使用原来的FitToCurve - if (Settings.Canvas.UseAdvancedBezierSmoothing) - { - drawingAttributes.FitToCurve = false; - } - else - { - drawingAttributes.FitToCurve = Settings.Canvas.FitToCurve; - } - - inkCanvas.EditingMode = InkCanvasEditingMode.Ink; - inkCanvas.Gesture += InkCanvas_Gesture; - } - catch { } - } - - //ApplicationGesture lastApplicationGesture = ApplicationGesture.AllGestures; - private DateTime lastGestureTime = DateTime.Now; - - private void InkCanvas_Gesture(object sender, InkCanvasGestureEventArgs e) { - var gestures = e.GetGestureRecognitionResults(); - try { - foreach (var gest in gestures) - //Trace.WriteLine(string.Format("Gesture: {0}, Confidence: {1}", gest.ApplicationGesture, gest.RecognitionConfidence)); - if (StackPanelPPTControls.Visibility == Visibility.Visible) { - if (gest.ApplicationGesture == ApplicationGesture.Left) - BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null); - if (gest.ApplicationGesture == ApplicationGesture.Right) - BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null); - } - } - catch { } - } - - private void inkCanvas_EditingModeChanged(object sender, RoutedEventArgs e) { - var inkCanvas1 = sender as InkCanvas; - if (inkCanvas1 == null) return; - - // 使用辅助方法设置光标 - SetCursorBasedOnEditingMode(inkCanvas1); - if (Settings.Canvas.IsShowCursor) { - if (inkCanvas1.EditingMode == InkCanvasEditingMode.Ink || - inkCanvas1.EditingMode == InkCanvasEditingMode.Select || - drawingShapeMode != 0) - inkCanvas1.ForceCursor = true; - else - inkCanvas1.ForceCursor = false; - } else { - // 套索选择模式下始终强制显示光标,即使用户设置不显示光标 - if (inkCanvas1.EditingMode == InkCanvasEditingMode.Select) { - inkCanvas1.ForceCursor = true; - } else { - inkCanvas1.ForceCursor = false; - } - } - - if (inkCanvas1.EditingMode == InkCanvasEditingMode.Ink) forcePointEraser = !forcePointEraser; - - // 处理高级橡皮擦覆盖层的启用/禁用 - var eraserOverlay = FindName("AdvancedEraserOverlay") as Border; - if (eraserOverlay != null) { - if (inkCanvas1.EditingMode == InkCanvasEditingMode.EraseByPoint) { - // 橡皮擦模式下启用覆盖层 - eraserOverlay.IsHitTestVisible = true; - Trace.WriteLine("Advanced Eraser: Overlay enabled in eraser mode"); - } else { - // 其他模式下禁用覆盖层 - eraserOverlay.IsHitTestVisible = false; - // 同时禁用高级橡皮擦系统 - DisableAdvancedEraserSystem(); - Trace.WriteLine("Advanced Eraser: Overlay disabled in non-eraser mode"); - } - } - } - - #endregion Ink Canvas - - #region Definations and Loading - - public static Settings Settings = new Settings(); - public static string settingsFileName = "Settings.json"; - private bool isLoaded; - private bool forcePointEraser; - - private void Window_Loaded(object sender, RoutedEventArgs e) { - loadPenCanvas(); - //加载设置 - LoadSettings(true); - // 检查保存路径是否可用,不可用则修正 - try - { - string savePath = Settings.Automation.AutoSavedStrokesLocation; - bool needFix = false; - if (string.IsNullOrWhiteSpace(savePath) || !Directory.Exists(savePath)) - { - needFix = true; - } - else - { - // 检查是否可写 - try - { - string testFile = Path.Combine(savePath, "test.tmp"); - File.WriteAllText(testFile, "test"); - File.Delete(testFile); - } - catch - { - needFix = true; - } - } - if (needFix) - { - string newPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves"); - Settings.Automation.AutoSavedStrokesLocation = newPath; - if (!Directory.Exists(newPath)) - Directory.CreateDirectory(newPath); - SaveSettingsToFile(); - LogHelper.WriteLogToFile($"自动修正保存路径为: {newPath}"); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检测或修正保存路径时出错: {ex.Message}", LogHelper.LogType.Error); - } - - // 加载自定义背景颜色 - LoadCustomBackgroundColor(); - - // 注册设置面板滚动事件 - if (SettingsPanelScrollViewer != null) - { - SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; - } - - // 初始化PPT管理器 - InitializePPTManagers(); - - // 如果启用PPT支持,开始监控 - if (Settings.PowerPointSettings.PowerPointSupport) - { - StartPPTMonitoring(); - } - - // HasNewUpdateWindow hasNewUpdateWindow = new HasNewUpdateWindow(); - if (Environment.Is64BitProcess) GroupBoxInkRecognition.Visibility = Visibility.Collapsed; - - ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light; - SystemEvents_UserPreferenceChanged(null, null); - - //TextBlockVersion.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - LogHelper.WriteLogToFile("Ink Canvas Loaded", LogHelper.LogType.Event); - - isLoaded = true; - - BlackBoardLeftSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection; - BlackBoardRightSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection; - - BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = - new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); - BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5; - BtnRightWhiteBoardSwitchPreviousGeometry.Brush = - new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); - BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5; - - // 应用颜色主题,这将考虑自定义背景色 - CheckColorTheme(true); - - BtnWhiteBoardSwitchPrevious.IsEnabled = CurrentWhiteboardIndex != 1; - BorderInkReplayToolBox.Visibility = Visibility.Collapsed; - - // 提前加载IA库,优化第一笔等待时间 - if (Settings.InkToShape.IsInkToShapeEnabled && !Environment.Is64BitProcess) { - var strokeEmpty = new StrokeCollection(); - InkRecognizeHelper.RecognizeShape(strokeEmpty); - } - - SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged; - // 自动收纳到侧边栏 - if (Settings.Startup.IsFoldAtStartup) - { - FoldFloatingBar_MouseUp(new object(), null); - } - - // 恢复崩溃后操作设置 - if (App.CrashAction == App.CrashActionType.SilentRestart) - RadioCrashSilentRestart.IsChecked = true; - else - RadioCrashNoAction.IsChecked = true; - - - - // 如果当前不是黑板模式,则切换到黑板模式 - if (currentMode == 0) - { - // 延迟执行,确保UI已完全加载 - Dispatcher.BeginInvoke(new Action(() => { - // 重新加载自定义背景颜色 - LoadCustomBackgroundColor(); - - // 模拟点击切换按钮进入黑板模式 - if (GridTransparencyFakeBackground.Background != Brushes.Transparent) - { - BtnSwitch_Click(BtnSwitch, null); - } - - // 确保背景颜色正确设置为黑板颜色 - CheckColorTheme(true); - }), DispatcherPriority.Loaded); - } - - // 初始化插件系统 - InitializePluginSystem(); - // 确保开关和设置同步 - ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode; - ApplyNoFocusMode(); - - // 初始化UIElement选择系统 - InitializeUIElementSelection(); - - // 初始化剪贴板监控 - InitializeClipboardMonitoring(); - } - - private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e) { - if (!Settings.Advanced.IsEnableResolutionChangeDetection) return; - ShowNotification($"检测到显示器信息变化,变为{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height})"); - new Thread(() => { - var isFloatingBarOutsideScreen = false; - var isInPPTPresentationMode = false; - Dispatcher.Invoke(() => { - isFloatingBarOutsideScreen = IsOutsideOfScreenHelper.IsOutsideOfScreen(ViewboxFloatingBar); - isInPPTPresentationMode = BtnPPTSlideShowEnd.Visibility == Visibility.Visible; - }); - if (isFloatingBarOutsideScreen) dpiChangedDelayAction.DebounceAction(3000, null, () => { - if (!isFloatingBarFolded) - { - if (isInPPTPresentationMode) ViewboxFloatingBarMarginAnimation(60); - else ViewboxFloatingBarMarginAnimation(100, true); - } - }); - }).Start(); - } - - public DelayAction dpiChangedDelayAction = new DelayAction(); - - private void MainWindow_OnDpiChanged(object sender, DpiChangedEventArgs e) - { - if (e.OldDpi.DpiScaleX != e.NewDpi.DpiScaleX && e.OldDpi.DpiScaleY != e.NewDpi.DpiScaleY && Settings.Advanced.IsEnableDPIChangeDetection) - { - ShowNotification($"系统DPI发生变化,从 {e.OldDpi.DpiScaleX}x{e.OldDpi.DpiScaleY} 变化为 {e.NewDpi.DpiScaleX}x{e.NewDpi.DpiScaleY}"); - - new Thread(() => { - var isFloatingBarOutsideScreen = false; - var isInPPTPresentationMode = false; - Dispatcher.Invoke(() => { - isFloatingBarOutsideScreen = IsOutsideOfScreenHelper.IsOutsideOfScreen(ViewboxFloatingBar); - isInPPTPresentationMode = BtnPPTSlideShowEnd.Visibility == Visibility.Visible; - }); - if (isFloatingBarOutsideScreen) dpiChangedDelayAction.DebounceAction(3000,null, () => { - if (!isFloatingBarFolded) - { - if (isInPPTPresentationMode) ViewboxFloatingBarMarginAnimation(60); - else ViewboxFloatingBarMarginAnimation(100, true); - } - }); - }).Start(); - } - } - - private void Window_Closing(object sender, CancelEventArgs e) { - LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event); - if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp) { - // 第一个确认对话框 - var result1 = MessageBox.Show("是否继续关闭 InkCanvasForClass,这将丢失当前未保存的墨迹。", "InkCanvasForClass", - MessageBoxButton.OKCancel, MessageBoxImage.Warning); - - if (result1 == MessageBoxResult.Cancel) { - e.Cancel = true; - LogHelper.WriteLogToFile("Ink Canvas closing cancelled at first confirmation", LogHelper.LogType.Event); - return; - } - - // 第二个确认对话框 - var result2 = MessageBox.Show("真的狠心关闭 InkCanvasForClass吗?", "InkCanvasForClass", - MessageBoxButton.OKCancel, MessageBoxImage.Error); - - if (result2 == MessageBoxResult.Cancel) { - e.Cancel = true; - LogHelper.WriteLogToFile("Ink Canvas closing cancelled at second confirmation", LogHelper.LogType.Event); - return; - } - - // 第三个最终确认对话框 - var result3 = MessageBox.Show("最后确认:确定要关闭 InkCanvasForClass 吗?", "InkCanvasForClass", - MessageBoxButton.OKCancel, MessageBoxImage.Question); - - if (result3 == MessageBoxResult.Cancel) { - e.Cancel = true; - LogHelper.WriteLogToFile("Ink Canvas closing cancelled at final confirmation", LogHelper.LogType.Event); - return; - } - - // 所有确认都通过,允许关闭 - e.Cancel = false; - LogHelper.WriteLogToFile("Ink Canvas closing confirmed by user", LogHelper.LogType.Event); - } - - if (e.Cancel) LogHelper.WriteLogToFile("Ink Canvas closing cancelled", LogHelper.LogType.Event); - } - - [DllImport("user32.dll", SetLastError = true)] - public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); - - private void MainWindow_OnSizeChanged(object sender, SizeChangedEventArgs e) { - if (Settings.Advanced.IsEnableForceFullScreen) { - if (isLoaded) ShowNotification( - $"检测到窗口大小变化,已自动恢复到全屏:{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}(缩放比例为{Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth}x{Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight})"); - WindowState = WindowState.Maximized; - MoveWindow(new WindowInteropHelper(this).Handle, 0, 0, - Screen.PrimaryScreen.Bounds.Width, - Screen.PrimaryScreen.Bounds.Height, true); - } - } - - - private void Window_Closed(object sender, EventArgs e) { - SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged; - - // 释放PPT管理器资源 - DisposePPTManagers(); - - // 清理剪贴板监控 - CleanupClipboardMonitoring(); - ClipboardNotification.Stop(); - - LogHelper.WriteLogToFile("Ink Canvas closed", LogHelper.LogType.Event); - - // 检查是否有待安装的更新 - CheckPendingUpdates(); - } - - private void CheckPendingUpdates() - { - try - { - // 如果有可用的更新版本且启用了自动更新 - if (AvailableLatestVersion != null && Settings.Startup.IsAutoUpdate) - { - // 检查更新文件是否已下载 - string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate"); - string statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{AvailableLatestVersion}Status.txt"); - - if (File.Exists(statusFilePath) && File.ReadAllText(statusFilePath).Trim().ToLower() == "true") - { - LogHelper.WriteLogToFile($"AutoUpdate | Installing pending update v{AvailableLatestVersion} on application close"); - - // 设置为用户主动退出,避免被看门狗判定为崩溃 - App.IsAppExitByUser = true; - - // 创建批处理脚本并启动,软件关闭后会执行更新操作 - AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | Error checking pending updates: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 辅助方法:使用多线路组下载更新 - private async Task DownloadUpdateWithFallback(string version, AutoUpdateHelper.UpdateLineGroup primaryGroup, UpdateChannel channel) - { - try - { - // 如果主要线路组可用,直接使用 - if (primaryGroup != null) - { - LogHelper.WriteLogToFile($"AutoUpdate | 使用主要线路组下载: {primaryGroup.GroupName}"); - return await AutoUpdateHelper.DownloadSetupFile(version, primaryGroup); - } - - // 如果主要线路组不可用,获取所有可用线路组 - LogHelper.WriteLogToFile("AutoUpdate | 主要线路组不可用,获取所有可用线路组"); - var availableGroups = await AutoUpdateHelper.GetAvailableLineGroupsOrdered(channel); - if (availableGroups.Count == 0) - { - LogHelper.WriteLogToFile("AutoUpdate | 没有可用的线路组", LogHelper.LogType.Error); - return false; - } - - LogHelper.WriteLogToFile($"AutoUpdate | 使用 {availableGroups.Count} 个可用线路组进行下载"); - return await AutoUpdateHelper.DownloadSetupFileWithFallback(version, availableGroups); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error); - return false; - } - } - - private async void AutoUpdate() { - // 清除之前的更新状态,确保使用新通道重新检查 - AvailableLatestVersion = null; - AvailableLatestLineGroup = null; - - // 使用当前选择的更新通道检查更新 - var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel); - AvailableLatestVersion = remoteVersion; - AvailableLatestLineGroup = lineGroup; - - // 声明下载状态变量,用于整个方法 - bool isDownloadSuccessful = false; - - if (AvailableLatestVersion != null) { - // 检测到新版本 - LogHelper.WriteLogToFile($"AutoUpdate | New version available: {AvailableLatestVersion}"); - - // 检查是否是用户选择跳过的版本 - if (!string.IsNullOrEmpty(Settings.Startup.SkippedVersion) && - Settings.Startup.SkippedVersion == AvailableLatestVersion) { - LogHelper.WriteLogToFile($"AutoUpdate | Version {AvailableLatestVersion} was marked to be skipped by the user"); - return; // 跳过此版本,不执行更新操作 - } - - // 如果检测到的版本与跳过的版本不同,则清除跳过版本记录 - // 这确保用户只能跳过当前最新版本,而不是永久跳过所有更新 - if (!string.IsNullOrEmpty(Settings.Startup.SkippedVersion) && - Settings.Startup.SkippedVersion != AvailableLatestVersion) { - LogHelper.WriteLogToFile($"AutoUpdate | Detected new version {AvailableLatestVersion} different from skipped version {Settings.Startup.SkippedVersion}, clearing skip record"); - Settings.Startup.SkippedVersion = ""; - SaveSettingsToFile(); - } - - // 获取当前版本 - string currentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - - // 如果启用了静默更新,则自动下载更新而不显示提示 - if (Settings.Startup.IsAutoUpdateWithSilence) { - LogHelper.WriteLogToFile("AutoUpdate | Silent update enabled, downloading update automatically without notification"); - - // 静默下载更新,使用多线路组下载功能 - isDownloadSuccessful = await DownloadUpdateWithFallback(AvailableLatestVersion, AvailableLatestLineGroup, Settings.Startup.UpdateChannel); - - if (isDownloadSuccessful) { - LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will install when conditions are met"); - - // 启动检查定时器,定期检查是否可以安装 - timerCheckAutoUpdateWithSilence.Start(); - } else { - LogHelper.WriteLogToFile("AutoUpdate | Silent update download failed", LogHelper.LogType.Error); - } - - return; - } - - // 如果没有启用静默更新,则显示常规更新窗口 - string releaseDate = DateTime.Now.ToString("yyyy年MM月dd日"); - - // 从服务器获取更新日志 - string releaseNotes = await AutoUpdateHelper.GetUpdateLog(Settings.Startup.UpdateChannel); - - // 如果获取失败,使用默认文本 - if (string.IsNullOrEmpty(releaseNotes)) - { - releaseNotes = $@"# InkCanvasForClass v{AvailableLatestVersion}更新 - - 无法获取更新日志,但新版本已准备就绪。"; - } - - // 创建并显示更新窗口 - HasNewUpdateWindow updateWindow = new HasNewUpdateWindow(currentVersion, AvailableLatestVersion, releaseDate, releaseNotes); - bool? dialogResult = updateWindow.ShowDialog(); - - // 如果窗口被关闭但没有点击按钮,则不执行任何操作 - if (dialogResult != true) { - LogHelper.WriteLogToFile("AutoUpdate | Update dialog closed without selection"); - return; - } - - // 不再从更新窗口获取自动更新设置 - - // 根据用户选择处理更新 - switch (updateWindow.Result) { - case HasNewUpdateWindow.UpdateResult.UpdateNow: - // 立即更新:显示下载进度,下载完成后立即安装 - LogHelper.WriteLogToFile("AutoUpdate | User chose to update now"); - - // 显示下载进度提示 - MessageBox.Show("开始下载更新,请稍候...", "正在更新", MessageBoxButton.OK, MessageBoxImage.Information); - - // 下载更新文件,使用多线路组下载功能 - isDownloadSuccessful = await DownloadUpdateWithFallback(AvailableLatestVersion, AvailableLatestLineGroup, Settings.Startup.UpdateChannel); - - if (isDownloadSuccessful) { - // 下载成功,提示用户准备安装 - MessageBoxResult result = MessageBox.Show("更新已下载完成,点击确定后将关闭软件并安装新版本!", "安装更新", MessageBoxButton.OKCancel, MessageBoxImage.Information); - - // 只有当用户点击确定按钮后才关闭软件 - if (result == MessageBoxResult.OK) { - // 设置为用户主动退出,避免被看门狗判定为崩溃 - App.IsAppExitByUser = true; - - // 准备批处理脚本 - AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, false); - - // 关闭软件,让安装程序接管 - Application.Current.Shutdown(); - } else { - LogHelper.WriteLogToFile("AutoUpdate | User cancelled update installation"); - } - } else { - // 下载失败 - MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error); - } - break; - - case HasNewUpdateWindow.UpdateResult.UpdateLater: - // 稍后更新:静默下载,在软件关闭时自动安装 - LogHelper.WriteLogToFile("AutoUpdate | User chose to update later"); - - // 不管设置如何,都进行下载,使用多线路组下载功能 - isDownloadSuccessful = await DownloadUpdateWithFallback(AvailableLatestVersion, AvailableLatestLineGroup, Settings.Startup.UpdateChannel); - - if (isDownloadSuccessful) { - LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will install when application closes"); - - // 设置标志,在应用程序关闭时安装 - Settings.Startup.IsAutoUpdate = true; - Settings.Startup.IsAutoUpdateWithSilence = true; - - // 启动检查定时器 - timerCheckAutoUpdateWithSilence.Start(); - - // 通知用户 - MessageBox.Show("更新已下载完成,将在软件关闭时自动安装。", "更新已准备就绪", MessageBoxButton.OK, MessageBoxImage.Information); - } else { - LogHelper.WriteLogToFile("AutoUpdate | Update download failed", LogHelper.LogType.Error); - MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error); - } - break; - - case HasNewUpdateWindow.UpdateResult.SkipVersion: - // 跳过该版本:记录到设置中 - LogHelper.WriteLogToFile($"AutoUpdate | User chose to skip version {AvailableLatestVersion}"); - - // 记录要跳过的版本号 - Settings.Startup.SkippedVersion = AvailableLatestVersion; - - // 保存设置到文件 - SaveSettingsToFile(); - - // 通知用户 - MessageBox.Show($"已设置跳过版本 {AvailableLatestVersion},在下次发布新版本之前不会再提示更新。", - "已跳过此版本", - MessageBoxButton.OK, - MessageBoxImage.Information); - break; - } - } else { - AutoUpdateHelper.DeleteUpdatesFolder(); - } - } - - // 新增:崩溃后操作设置按钮事件 - private void RadioCrashAction_Checked(object sender, RoutedEventArgs e) - { - if (RadioCrashSilentRestart != null && RadioCrashSilentRestart.IsChecked == true) - { - App.CrashAction = App.CrashActionType.SilentRestart; - Settings.Startup.CrashAction = 0; - } - else if (RadioCrashNoAction != null && RadioCrashNoAction.IsChecked == true) - { - App.CrashAction = App.CrashActionType.NoAction; - Settings.Startup.CrashAction = 1; - } - SaveSettingsToFile(); - // 强制同步全局变量,防止后台逻辑未及时感知 - App.SyncCrashActionFromSettings(); - } - - // 添加一个辅助方法,根据当前编辑模式设置光标 - public void SetCursorBasedOnEditingMode(InkCanvas canvas) - { - // 套索选择模式下光标始终显示,无论用户设置如何 - if (canvas.EditingMode == InkCanvasEditingMode.Select) { - canvas.UseCustomCursor = true; - canvas.ForceCursor = true; - canvas.Cursor = Cursors.Cross; - System.Windows.Forms.Cursor.Show(); - return; - } - - // 其他模式按照用户设置处理 - if (Settings.Canvas.IsShowCursor) { - canvas.UseCustomCursor = true; - canvas.ForceCursor = true; - - // 根据编辑模式设置不同的光标 - if (canvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - canvas.Cursor = Cursors.Cross; - } else if (canvas.EditingMode == InkCanvasEditingMode.Ink) { - var sri = Application.GetResourceStream(new Uri("Resources/Cursors/Pen.cur", UriKind.Relative)); - if (sri != null) - canvas.Cursor = new Cursor(sri.Stream); - } - - // 确保光标可见,无论是鼠标、触控还是手写笔 - System.Windows.Forms.Cursor.Show(); - - // 确保手写笔模式下也能显示光标 - if (Tablet.TabletDevices.Count > 0) { - foreach (TabletDevice device in Tablet.TabletDevices) { - if (device.Type == TabletDeviceType.Stylus) { - // 手写笔设备存在,强制显示光标 - System.Windows.Forms.Cursor.Show(); - break; - } - } - } - } else { - canvas.UseCustomCursor = false; - canvas.ForceCursor = false; - System.Windows.Forms.Cursor.Show(); - } - } - - // 鼠标输入 - private void inkCanvas_PreviewMouseDown(object sender, MouseButtonEventArgs e) - { - // 使用辅助方法设置光标 - SetCursorBasedOnEditingMode(sender as InkCanvas); - - // 在选择模式下,如果点击的不是UI元素,则取消选择 - if (inkCanvas.EditingMode == InkCanvasEditingMode.Select) - { - var hitTest = e.OriginalSource; - // 如果点击的不是图片或其他UI元素,则取消选择 - if (!(hitTest is Image) && !(hitTest is MediaElement)) - { - // 检查是否点击在已选择的UI元素上 - bool clickedOnSelectedElement = false; - if (selectedUIElement != null) - { - var elementBounds = GetUIElementBounds(selectedUIElement); - var clickPoint = e.GetPosition(inkCanvas); - clickedOnSelectedElement = elementBounds.Contains(clickPoint); - } - - if (!clickedOnSelectedElement) - { - DeselectUIElement(); - } - } - } - } - - // 手写笔输入 - private void inkCanvas_StylusDown(object sender, StylusDownEventArgs e) - { - // 使用辅助方法设置光标 - SetCursorBasedOnEditingMode(sender as InkCanvas); - } - - // 触摸结束,恢复光标 - - #endregion Definations and Loading - - #region Navigation Sidebar Methods - - // 侧边栏导航按钮事件处理 - private void NavStartup_Click(object sender, RoutedEventArgs e) - { - // 切换到启动设置页面 - ShowSettingsSection("startup"); - } - - private void NavCanvas_Click(object sender, RoutedEventArgs e) - { - // 切换到画布设置页面 - ShowSettingsSection("canvas"); - } - - private void NavGesture_Click(object sender, RoutedEventArgs e) - { - // 切换到手势设置页面 - ShowSettingsSection("gesture"); - } - - private void NavInkRecognition_Click(object sender, RoutedEventArgs e) - { - // 切换到墨迹识别设置页面 - ShowSettingsSection("inkrecognition"); - } - - private void NavCrashAction_Click(object sender, RoutedEventArgs e) - { - // 切换到崩溃处理设置页面 - ShowSettingsSection("crashaction"); - } - - private void NavPPT_Click(object sender, RoutedEventArgs e) - { - // 切换到PPT设置页面 - ShowSettingsSection("ppt"); - } - - private void NavAdvanced_Click(object sender, RoutedEventArgs e) - { - // 切换到高级设置页面 - ShowSettingsSection("advanced"); - } - - private void NavAutomation_Click(object sender, RoutedEventArgs e) - { - // 切换到自动化设置页面 - ShowSettingsSection("automation"); - } - - private void NavRandomWindow_Click(object sender, RoutedEventArgs e) - { - // 切换到随机窗口设置页面 - ShowSettingsSection("randomwindow"); - } - - private void NavAbout_Click(object sender, RoutedEventArgs e) - { - // 切换到关于页面 - ShowSettingsSection("about"); - // 刷新设备信息 - RefreshDeviceInfo(); - } - - // 新增:个性化设置 - private void NavTheme_Click(object sender, RoutedEventArgs e) - { - // 切换到个性化设置页面 - ShowSettingsSection("theme"); - } - - // 新增:快捷键设置 - private void NavShortcuts_Click(object sender, RoutedEventArgs e) - { - // 切换到快捷键设置页面 - ShowSettingsSection("shortcuts"); - // 如果设置部分尚未快捷键 - MessageBox.Show("设置功能正在开发中", "提示", MessageBoxButton.OK, MessageBoxImage.Information); - } - - private void BtnCloseSettings_Click(object sender, RoutedEventArgs e) - { - // 关闭设置面板 - BorderSettings.Visibility = Visibility.Collapsed; - // 设置蒙版为不可点击,并清除背景 - BorderSettingsMask.IsHitTestVisible = false; - BorderSettingsMask.Background = null; // 确保清除蒙层背景 - } - - /// - /// 刷新设备信息按钮点击事件 - /// - private void RefreshDeviceInfo_Click(object sender, RoutedEventArgs e) - { - RefreshDeviceInfo(); - } - - /// - /// 刷新设备信息显示 - /// - private void RefreshDeviceInfo() - { - try - { - // 获取设备ID - string deviceId = DeviceIdentifier.GetDeviceId(); - DeviceIdTextBlock.Text = deviceId; - - // 获取使用频率 - var usageFrequency = DeviceIdentifier.GetUsageFrequency(); - string frequencyText; - switch (usageFrequency) - { - case DeviceIdentifier.UsageFrequency.High: - frequencyText = "高频用户"; - break; - case DeviceIdentifier.UsageFrequency.Medium: - frequencyText = "中频用户"; - break; - case DeviceIdentifier.UsageFrequency.Low: - frequencyText = "低频用户"; - break; - default: - frequencyText = "未知"; - break; - } - UsageFrequencyTextBlock.Text = frequencyText; - - // 获取更新优先级 - var updatePriority = DeviceIdentifier.GetUpdatePriority(); - string priorityText; - switch (updatePriority) - { - case DeviceIdentifier.UpdatePriority.High: - priorityText = "高优先级(优先推送更新)"; - break; - case DeviceIdentifier.UpdatePriority.Medium: - priorityText = "中优先级(正常推送更新)"; - break; - case DeviceIdentifier.UpdatePriority.Low: - priorityText = "低优先级(延迟推送更新)"; - break; - default: - priorityText = "未知"; - break; - } - UpdatePriorityTextBlock.Text = priorityText; - - // 获取使用统计(秒级精度) - var (launchCount, totalSeconds, avgSessionSeconds, _) = DeviceIdentifier.GetUsageStats(); - LaunchCountTextBlock.Text = launchCount.ToString(); - - // 使用新的格式化方法显示秒级精度的使用时长 - string totalUsageText = DeviceIdentifier.FormatDuration(totalSeconds); - TotalUsageTextBlock.Text = totalUsageText; - - LogHelper.WriteLogToFile($"MainWindow | 设备信息已刷新 - ID: {deviceId}, 频率: {frequencyText}, 优先级: {priorityText}"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"MainWindow | 刷新设备信息失败: {ex.Message}", LogHelper.LogType.Error); - - // 显示错误信息 - DeviceIdTextBlock.Text = "获取失败"; - UsageFrequencyTextBlock.Text = "获取失败"; - UpdatePriorityTextBlock.Text = "获取失败"; - LaunchCountTextBlock.Text = "获取失败"; - TotalUsageTextBlock.Text = "获取失败"; - } - } - - // 新增:折叠侧边栏 - private void CollapseNavSidebar_Click(object sender, RoutedEventArgs e) - { - // 折叠/展开侧边栏 - var columnDefinitions = ((Grid)BorderSettings.Child).ColumnDefinitions; - if (columnDefinitions[0].Width.Value == 50) - { - // 折叠侧边栏 - columnDefinitions[0].Width = new GridLength(0); - } - else - { - // 展开侧边栏 - columnDefinitions[0].Width = new GridLength(50); - } - } - - // 新增:显示侧边栏 - private void ShowNavSidebar_Click(object sender, RoutedEventArgs e) - { - // 确保侧边栏展开 - var columnDefinitions = ((Grid)BorderSettings.Child).ColumnDefinitions; - columnDefinitions[0].Width = new GridLength(50); - } - - // 辅助方法:显示指定的设置部分 - private async void ShowSettingsSection(string sectionTag) - { - // 显示设置面板 - BorderSettings.Visibility = Visibility.Visible; - // 设置蒙版为可点击,并添加半透明背景 - BorderSettingsMask.IsHitTestVisible = true; - BorderSettingsMask.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); - - // 获取SettingsPanelScrollViewer中的所有GroupBox - var stackPanel = SettingsPanelScrollViewer.Content as StackPanel; - if (stackPanel == null) return; - - // 确保所有GroupBox都是可见的 - foreach (var child in stackPanel.Children) - { - if (child is GroupBox groupBox) - { - groupBox.Visibility = Visibility.Visible; - } - } - - // 确保UI完全更新 - await Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Render); - - // 根据传入的sectionTag滚动到相应的设置部分 - GroupBox targetGroupBox = null; - - switch (sectionTag.ToLower()) - { - case "startup": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "启动"); - break; - case "canvas": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "画板和墨迹"); - break; - case "gesture": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "手势"); - break; - case "inkrecognition": - targetGroupBox = GroupBoxInkRecognition; - break; - case "crashaction": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "崩溃后操作"); - break; - case "ppt": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "PPT联动"); - break; - case "advanced": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "高级设置"); - break; - case "automation": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "自动化"); - break; - case "randomwindow": - targetGroupBox = GroupBoxRandWindow; - break; - case "theme": - targetGroupBox = GroupBoxAppearanceNewUI; - break; - case "shortcuts": - // 快捷键设置部分可能尚未实现 - targetGroupBox = FindGroupBoxByHeader(stackPanel, "快捷键"); - break; - case "about": - targetGroupBox = FindGroupBoxByHeader(stackPanel, "关于"); - break; - case "plugins": - targetGroupBox = GroupBoxPlugins; - break; - default: - // 默认滚动到顶部 - SettingsPanelScrollViewer.ScrollToTop(); - return; - } - - // 如果找到目标GroupBox,则滚动到它的位置 - if (targetGroupBox != null) - { - // 使用动画平滑滚动到目标位置 - ScrollToElement(targetGroupBox); - - // 高亮显示当前选中的导航项 - UpdateNavigationButtonState(sectionTag); - } - else - { - // 如果没有找到目标GroupBox,则滚动到顶部 - SettingsPanelScrollViewer.ScrollToTop(); - } - } - - // 根据Header文本查找GroupBox - private GroupBox FindGroupBoxByHeader(StackPanel parent, string headerText) - { - foreach (var child in parent.Children) - { - if (child is GroupBox groupBox) - { - // 查找GroupBox的Header - if (groupBox.Header is TextBlock headerTextBlock && - headerTextBlock.Text != null && - headerTextBlock.Text.Contains(headerText)) - { - return groupBox; - } - } - } - return null; - } - - // 平滑滚动到指定元素 - private async void ScrollToElement(FrameworkElement element) - { - if (element == null || SettingsPanelScrollViewer == null) return; - - try - { - // 暂时禁用滚动事件处理 - SettingsPanelScrollViewer.ScrollChanged -= SettingsPanelScrollViewer_ScrollChanged; - - // 记录当前滚动位置 - double originalOffset = SettingsPanelScrollViewer.VerticalOffset; - - // 将ScrollViewer内部的位置信息重置到顶部(不会触发视觉更新) - SettingsPanelScrollViewer.ScrollToHome(); - - // 使用Dispatcher进行延迟处理,确保布局更新 - await Dispatcher.InvokeAsync(() => { - try - { - // 强制更新布局 - SettingsPanelScrollViewer.UpdateLayout(); - - // 获取元素相对于顶部的准确位置 - Point elementPosition = element.TransformToAncestor(SettingsPanelScrollViewer).Transform(new Point(0, 0)); - - // 计算目标位置,减去一些偏移,使元素不会贴在顶部 - double targetPosition = elementPosition.Y - 20; - - // 确保目标位置不小于0 - targetPosition = Math.Max(0, targetPosition); - - // 直接设置滚动位置,不使用动画 - SettingsPanelScrollViewer.ScrollToVerticalOffset(targetPosition); - } - catch (Exception ex) - { - // 如果出现异常,恢复到原来的滚动位置 - SettingsPanelScrollViewer.ScrollToVerticalOffset(originalOffset); - } - finally - { - // 重新启用滚动事件处理 - SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; - } - }, DispatcherPriority.Render); - } - catch (Exception) - { - // 确保在异常情况下也重新启用滚动事件处理 - SettingsPanelScrollViewer.ScrollChanged += SettingsPanelScrollViewer_ScrollChanged; - } - } - - // 滚动条变化事件处理 - private void SettingsPanelScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) - { - // 可以在这里添加滚动事件的处理逻辑,如果需要的话 - } - - // 更新导航按钮状态 - private void UpdateNavigationButtonState(string activeTag) - { - // 清除所有导航按钮的Tag属性 - ClearAllNavButtonTags(); - - // 设置当前活动按钮的Tag属性 - switch (activeTag.ToLower()) - { - case "startup": - SetNavButtonTag("startup"); - break; - case "canvas": - SetNavButtonTag("canvas"); - break; - case "gesture": - SetNavButtonTag("gesture"); - break; - case "inkrecognition": - SetNavButtonTag("inkrecognition"); - break; - case "crashaction": - SetNavButtonTag("crashaction"); - break; - case "ppt": - SetNavButtonTag("ppt"); - break; - case "advanced": - SetNavButtonTag("advanced"); - break; - case "automation": - SetNavButtonTag("automation"); - break; - case "randomwindow": - SetNavButtonTag("randomwindow"); - break; - case "theme": - SetNavButtonTag("theme"); - break; - case "shortcuts": - SetNavButtonTag("shortcuts"); - break; - case "about": - SetNavButtonTag("about"); - break; - case "plugins": - SetNavButtonTag("plugins"); - break; - } - } - - // 清除所有导航按钮的Tag属性 - private void ClearAllNavButtonTags() - { - var grid = BorderSettings.Child as Grid; - if (grid == null) return; - - var navSidebar = grid.Children[0] as Border; - if (navSidebar == null) return; - - var navGrid = navSidebar.Child as Grid; - if (navGrid == null) return; - - var scrollViewer = navGrid.Children[1] as ScrollViewer; - if (scrollViewer == null) return; - - var stackPanel = scrollViewer.Content as StackPanel; - if (stackPanel == null) return; - - foreach (var child in stackPanel.Children) - { - if (child is Button button) - { - button.Tag = null; - } - } - } - - // 设置导航按钮的Tag属性 - private void SetNavButtonTag(string tag) - { - var grid = BorderSettings.Child as Grid; - if (grid == null) return; - - var navSidebar = grid.Children[0] as Border; - if (navSidebar == null) return; - - var navGrid = navSidebar.Child as Grid; - if (navGrid == null) return; - - var scrollViewer = navGrid.Children[1] as ScrollViewer; - if (scrollViewer == null) return; - - var stackPanel = scrollViewer.Content as StackPanel; - if (stackPanel == null) return; - - foreach (var child in stackPanel.Children) - { - if (child is Button button) - { - // 检查按钮的ToolTip属性,根据tag设置对应的按钮 - string buttonTag = button.Tag as string; - - // 如果按钮的Tag与要设置的tag匹配,则设置Tag - if (buttonTag != null && buttonTag.ToLower() == tag.ToLower()) - { - button.Tag = tag; - return; - } - } - } - } - - // 根据Header文本查找并显示GroupBox - private void ShowGroupBoxByHeader(StackPanel parent, string headerText) - { - foreach (var child in parent.Children) - { - if (child is GroupBox groupBox) - { - // 查找GroupBox的Header - if (groupBox.Header is TextBlock headerTextBlock && - headerTextBlock.Text != null && - headerTextBlock.Text.Contains(headerText)) - { - groupBox.Visibility = Visibility.Visible; - return; - } - } - } - } - - #endregion Navigation Sidebar Methods - - // 添加插件系统初始化方法 - private void InitializePluginSystem() - { - try - { - // 初始化插件管理器 - PluginManager.Instance.Initialize(); - LogHelper.WriteLogToFile("插件系统已初始化"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化插件系统时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 添加插件管理导航点击事件处理 - private void NavPlugins_Click(object sender, RoutedEventArgs e) - { - ShowSettingsSection("plugins"); - } - - // 添加打开插件管理器按钮点击事件 - private void BtnOpenPluginManager_Click(object sender, RoutedEventArgs e) - { - try - { - // 暂时隐藏设置面板 - BorderSettings.Visibility = Visibility.Hidden; - BorderSettingsMask.Visibility = Visibility.Hidden; - - // 创建并显示插件设置窗口 - PluginSettingsWindow pluginSettingsWindow = new PluginSettingsWindow(); - - // 设置窗口关闭事件,用于在插件管理窗口关闭后恢复设置面板 - pluginSettingsWindow.Closed += (s, args) => - { - // 恢复设置面板显示 - BorderSettings.Visibility = Visibility.Visible; - BorderSettingsMask.Visibility = Visibility.Visible; - }; - - // 显示插件设置窗口 - pluginSettingsWindow.ShowDialog(); - } - catch (Exception ex) - { - // 确保在发生错误时也恢复设置面板显示 - BorderSettings.Visibility = Visibility.Visible; - BorderSettingsMask.Visibility = Visibility.Visible; - - LogHelper.WriteLogToFile($"打开插件管理器时出错: {ex.Message}", LogHelper.LogType.Error); - MessageBox.Show($"打开插件管理器时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - // 在MainWindow类中添加: - private void ApplyCurrentEraserShape() - { - double k = 1; - switch (Settings.Canvas.EraserSize) - { - case 0: - k = Settings.Canvas.EraserShapeType == 0 ? 0.5 : 0.7; - break; - case 1: - k = Settings.Canvas.EraserShapeType == 0 ? 0.8 : 0.9; - break; - case 3: - k = Settings.Canvas.EraserShapeType == 0 ? 1.25 : 1.2; - break; - case 4: - k = Settings.Canvas.EraserShapeType == 0 ? 1.5 : 1.3; - break; - } - if (Settings.Canvas.EraserShapeType == 0) - { - inkCanvas.EraserShape = new EllipseStylusShape(k * 90, k * 90); - } - else if (Settings.Canvas.EraserShapeType == 1) - { - inkCanvas.EraserShape = new RectangleStylusShape(k * 90 * 0.6, k * 90); - } - } - - // 显示指定页 - private void ShowPage(int index) - { - if (index < 0 || index >= whiteboardPages.Count) return; - // 只切换可见性 - for (int i = 0; i < whiteboardPages.Count; i++) - { - whiteboardPages[i].Visibility = (i == index) ? Visibility.Visible : Visibility.Collapsed; - } - currentCanvas = whiteboardPages[index]; - currentPageIndex = index; - } - // 新建页面 - private void AddNewPage() - { - var newCanvas = new System.Windows.Controls.Canvas(); - whiteboardPages.Add(newCanvas); - InkCanvasGridForInkReplay.Children.Add(newCanvas); - ShowPage(whiteboardPages.Count - 1); - } - // 删除当前页面 - private void DeleteCurrentPage() - { - if (whiteboardPages.Count <= 1) return; - InkCanvasGridForInkReplay.Children.Remove(currentCanvas); - whiteboardPages.RemoveAt(currentPageIndex); - if (currentPageIndex >= whiteboardPages.Count) - currentPageIndex = whiteboardPages.Count - 1; - ShowPage(currentPageIndex); - } - // 快速面板退出PPT放映按钮事件 - private void ExitPPTSlideShow_MouseUp(object sender, MouseButtonEventArgs e) - { - // 直接调用PPT放映结束按钮的逻辑 - BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null); - } - - private void HistoryRollbackButton_Click(object sender, RoutedEventArgs e) - { - // 收起设置面板(与插件面板一致) - BorderSettings.Visibility = Visibility.Hidden; - BorderSettingsMask.Visibility = Visibility.Hidden; - var win = new HistoryRollbackWindow(Settings.Startup.UpdateChannel); - win.ShowDialog(); - // 可选:回滚窗口关闭后恢复设置面板显示 - BorderSettings.Visibility = Visibility.Visible; - BorderSettingsMask.Visibility = Visibility.Visible; - } - - [DllImport("user32.dll")] - private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); - [DllImport("user32.dll")] - private static extern int GetWindowLong(IntPtr hWnd, int nIndex); - private const int GWL_EXSTYLE = -20; - private const int WS_EX_NOACTIVATE = 0x08000000; - - private void ApplyNoFocusMode() - { - var hwnd = new WindowInteropHelper(this).Handle; - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - if (Settings.Advanced.IsNoFocusMode) - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE); - } - else - { - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_NOACTIVATE); - } - } - - private void ToggleSwitchNoFocusMode_Toggled(object sender, RoutedEventArgs e) - { - if (!isLoaded) return; - var toggle = sender as ToggleSwitch; - Settings.Advanced.IsNoFocusMode = toggle != null && toggle.IsOn; - SaveSettingsToFile(); - ApplyNoFocusMode(); - } - - #region Image Toolbar Event Handlers - - private void BorderImageClone_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - CloneImage(image); - } - } - - private void BorderImageCloneToNewBoard_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - CloneImageToNewBoard(image); - } - } - - private void BorderImageRotateLeft_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - RotateImage(image, -90); - } - } - - private void BorderImageRotateRight_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - RotateImage(image, 90); - } - } - - private void GridImageScaleIncrease_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - ScaleImage(image, 1.25); // 放大5% - } - } - - private void GridImageScaleDecrease_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - ScaleImage(image, 0.8); // 缩小5% - } - } - - private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e) - { - if (lastBorderMouseDownObject != sender) return; - - if (selectedUIElement is Image image) - { - DeleteImage(image); - } - } - - #endregion - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/ConfigHelper.cs b/Ink Canvas/MainWindow_cs/ConfigHelper.cs deleted file mode 100644 index 9fa03cf7..00000000 --- a/Ink Canvas/MainWindow_cs/ConfigHelper.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ink_Canvas -{ - internal class ConfigHelper - { - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs deleted file mode 100644 index de356541..00000000 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ /dev/null @@ -1,308 +0,0 @@ -using Ink_Canvas.Helpers; -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Ink; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Controls; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.IO; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - private StrokeCollection[] strokeCollections = new StrokeCollection[101]; - private bool[] whiteboadLastModeIsRedo = new bool[101]; - private StrokeCollection lastTouchDownStrokeCollection = new StrokeCollection(); - - private int CurrentWhiteboardIndex = 1; - private int WhiteboardTotalCount = 1; - private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][]; //最多99页,0用来存储非白板时的墨迹以便还原 - - // 保存每页白板图片信息 - private void SaveStrokes(bool isBackupMain = false) { - // 确保画布上的所有UI元素都被保存到时间机器历史记录中 - var currentHistory = timeMachine.ExportTimeMachineHistory(); - var elementsInHistory = new HashSet(); - - // 收集已经在历史记录中的元素 - if (currentHistory != null) { - foreach (var h in currentHistory) { - if (h.CommitType == TimeMachineHistoryType.ElementInsert && - h.InsertedElement != null && - !h.StrokeHasBeenCleared) { - elementsInHistory.Add(h.InsertedElement); - } - } - } - - // 检查画布上的所有UI元素,确保它们都在历史记录中 - var missingElements = 0; - foreach (UIElement child in inkCanvas.Children) { - if (child is Image || child is MediaElement) { - if (!elementsInHistory.Contains(child)) { - timeMachine.CommitElementInsertHistory(child); - missingElements++; - } - } - } - - - // 确保画布上的所有墨迹都被保存 - if (inkCanvas.Strokes.Count > 0) { - // 检查是否有墨迹没有在时间机器历史记录中 - var strokesInHistory = new HashSet(); - if (currentHistory != null) { - foreach (var h in currentHistory) { - if (h.CommitType == TimeMachineHistoryType.UserInput && - h.CurrentStroke != null && - !h.StrokeHasBeenCleared) { - foreach (Stroke stroke in h.CurrentStroke) { - strokesInHistory.Add(stroke); - } - } - } - } - - // 收集没有在历史记录中的墨迹 - var missingStrokes = new StrokeCollection(); - foreach (Stroke stroke in inkCanvas.Strokes) { - if (!strokesInHistory.Contains(stroke)) { - missingStrokes.Add(stroke); - } - } - - if (missingStrokes.Count > 0) { - timeMachine.CommitStrokeUserInputHistory(missingStrokes); - } - } - - if (isBackupMain) { - var timeMachineHistory = timeMachine.ExportTimeMachineHistory(); - TimeMachineHistories[0] = timeMachineHistory; - timeMachine.ClearStrokeHistory(); - - - } else { - var timeMachineHistory = timeMachine.ExportTimeMachineHistory(); - TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory; - timeMachine.ClearStrokeHistory(); - - - } - } - - private void ClearStrokes(bool isErasedByCode) { - _currentCommitType = CommitReason.ClearingCanvas; - if (isErasedByCode) _currentCommitType = CommitReason.CodeInput; - - // 取消任何UI元素的选择,隐藏拉伸控件 - DeselectUIElement(); - - // 只清除笔画,不清除图片元素 - // 图片元素的清除由调用方决定 - inkCanvas.Strokes.Clear(); - - _currentCommitType = CommitReason.UserInput; - } - - // 恢复每页白板图片信息 - private void RestoreStrokes(bool isBackupMain = false) { - try { - var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex; - - // 先清空当前画布的墨迹 - inkCanvas.Strokes.Clear(); - - // 清空当前画布的所有内容(墨迹和图片) - // 这里必须清除图片,因为页面切换时需要完全重置画布状态 - inkCanvas.Children.Clear(); - - // 如果历史记录为空,直接返回(新页面或空页面) - if (TimeMachineHistories[targetIndex] == null) { - timeMachine.ClearStrokeHistory(); - return; - } - - if (isBackupMain) { - timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]); - foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item); - } else { - timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]); - // 通过时间机器历史恢复所有内容(墨迹和图片) - foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item); - } - - // 确保选中状态被清除,因为我们切换了页面 - if (selectedUIElement != null) { - DeselectUIElement(); - } - } - catch { - // ignored - } - } - - private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e) { - if (sender == BtnLeftPageListWB) { - if (BoardBorderLeftPageListView.Visibility == Visibility.Visible) { - AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView); - } else { - AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView); - RefreshBlackBoardSidePageListView(); - AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderLeftPageListView); - await Task.Delay(1); - ScrollViewToVerticalTop( - (ListViewItem)BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex( - CurrentWhiteboardIndex - 1), BlackBoardLeftSidePageListScrollViewer); - } - } else if (sender == BtnRightPageListWB) - { - if (BoardBorderRightPageListView.Visibility == Visibility.Visible) { - AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView); - } else { - AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView); - RefreshBlackBoardSidePageListView(); - AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderRightPageListView); - await Task.Delay(1); - ScrollViewToVerticalTop( - (ListViewItem)BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex( - CurrentWhiteboardIndex - 1), BlackBoardRightSidePageListScrollViewer); - } - } - - } - - private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e) { - if (CurrentWhiteboardIndex <= 1) return; - - // 取消任何UI元素的选择 - DeselectUIElement(); - - SaveStrokes(); - - ClearStrokes(true); - CurrentWhiteboardIndex--; - - RestoreStrokes(); - - UpdateIndexInfoDisplay(); - } - - private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e) { - Trace.WriteLine("113223234"); - - if (Settings.Automation.IsAutoSaveStrokesAtClear && - inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true); - if (CurrentWhiteboardIndex >= WhiteboardTotalCount) { - // 在最后一页时,点击“新页面”按钮直接新增一页 - BtnWhiteBoardAdd_Click(sender, e); - return; - } - - // 取消任何UI元素的选择 - DeselectUIElement(); - - SaveStrokes(); - - ClearStrokes(true); - CurrentWhiteboardIndex++; - - RestoreStrokes(); - - UpdateIndexInfoDisplay(); - } - - private void BtnWhiteBoardAdd_Click(object sender, EventArgs e) { - if (WhiteboardTotalCount >= 99) return; - if (Settings.Automation.IsAutoSaveStrokesAtClear && - inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true); - - // 取消任何UI元素的选择 - DeselectUIElement(); - - SaveStrokes(); - ClearStrokes(true); - - WhiteboardTotalCount++; - CurrentWhiteboardIndex++; - - if (CurrentWhiteboardIndex != WhiteboardTotalCount) - for (var i = WhiteboardTotalCount; i > CurrentWhiteboardIndex; i--) - TimeMachineHistories[i] = TimeMachineHistories[i - 1]; - - // 确保新页面的历史记录为空 - TimeMachineHistories[CurrentWhiteboardIndex] = null; - - // 恢复新页面(这会清空画布,因为历史记录为null) - RestoreStrokes(); - - UpdateIndexInfoDisplay(); - - if (WhiteboardTotalCount >= 99) BtnWhiteBoardAdd.IsEnabled = false; - - if (BlackBoardLeftSidePageListView.Visibility == Visibility.Visible) { - RefreshBlackBoardSidePageListView(); - } - } - - private void BtnWhiteBoardDelete_Click(object sender, RoutedEventArgs e) { - ClearStrokes(true); - - if (CurrentWhiteboardIndex != WhiteboardTotalCount) - for (var i = CurrentWhiteboardIndex; i <= WhiteboardTotalCount; i++) - TimeMachineHistories[i] = TimeMachineHistories[i + 1]; - else - CurrentWhiteboardIndex--; - - WhiteboardTotalCount--; - - RestoreStrokes(); - - UpdateIndexInfoDisplay(); - - if (WhiteboardTotalCount < 99) BtnWhiteBoardAdd.IsEnabled = true; - } - - private void UpdateIndexInfoDisplay() { - TextBlockWhiteBoardIndexInfo.Text = - $"{CurrentWhiteboardIndex}/{WhiteboardTotalCount}"; - - bool isLastPage = CurrentWhiteboardIndex == WhiteboardTotalCount; - bool isMaxPage = WhiteboardTotalCount >= 99; - - // 设置按钮文本 - BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页"; - BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页"; - - // 始终允许点击“下一页/新页面”按钮(除非已达最大页数) - BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage; - - // 保持按钮常亮(高亮) - BtnLeftWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27)); - BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1; - BtnRightWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27)); - BtnRightWhiteBoardSwitchNextLabel.Opacity = 1; - - BtnWhiteBoardSwitchPrevious.IsEnabled = true; - - if (CurrentWhiteboardIndex == 1) { - BtnWhiteBoardSwitchPrevious.IsEnabled = false; - BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); - BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5; - BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); - BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5; - } else { - BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27)); - BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1; - BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27)); - BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1; - } - - BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1; - } - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_BoardIcons.cs b/Ink Canvas/MainWindow_cs/MW_BoardIcons.cs deleted file mode 100644 index b7ae1687..00000000 --- a/Ink Canvas/MainWindow_cs/MW_BoardIcons.cs +++ /dev/null @@ -1,833 +0,0 @@ -using System; -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using Ink_Canvas.Helpers; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - private void BoardChangeBackgroundColorBtn_MouseUp(object sender, RoutedEventArgs e) { - if (!isLoaded) return; - - // 创建背景选项面板(如果不存在) - if (BackgroundPalette == null) - { - CreateBackgroundPalette(); - } - - // 显示或隐藏背景选项面板 - if (BackgroundPalette != null) - { - if (BackgroundPalette.Visibility == Visibility.Visible) - { - // 如果面板已经显示,则隐藏它 - AnimationsHelper.HideWithSlideAndFade(BackgroundPalette); - } - else - { - // 隐藏其他可能显示的面板 - AnimationsHelper.HideWithSlideAndFade(EraserSizePanel); - AnimationsHelper.HideWithSlideAndFade(BorderTools); - AnimationsHelper.HideWithSlideAndFade(BoardBorderTools); - AnimationsHelper.HideWithSlideAndFade(PenPalette); - AnimationsHelper.HideWithSlideAndFade(BoardPenPalette); - AnimationsHelper.HideWithSlideAndFade(BorderDrawShape); - AnimationsHelper.HideWithSlideAndFade(BoardBorderDrawShape); - AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel); - AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder); - AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder); - - // 显示背景选项面板 - AnimationsHelper.ShowWithSlideFromBottomAndFade(BackgroundPalette); - } - return; - } - - // 原有的背景切换代码 - Settings.Canvas.UsingWhiteboard = !Settings.Canvas.UsingWhiteboard; - SaveSettingsToFile(); - if (Settings.Canvas.UsingWhiteboard) { - if (inkColor == 5) lastBoardInkColor = 0; - ICCWaterMarkDark.Visibility = Visibility.Visible; - ICCWaterMarkWhite.Visibility = Visibility.Collapsed; - - // 设置为白板默认背景色 - Color defaultWhiteboardColor = Color.FromRgb(234, 235, 237); - - if (currentMode == 1) // 白板模式 - { - // 设置背景为默认白板背景色 - GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor); - - // 更新RGB滑块的值为默认白板背景色 - if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible) - { - UpdateRGBSliders(defaultWhiteboardColor); - } - - // 更新自定义背景色为默认白板背景色 - CustomBackgroundColor = defaultWhiteboardColor; - - // 保存到设置 - string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}"; - Settings.Canvas.CustomBackgroundColor = colorHex; - SaveSettingsToFile(); - } - - // 设置墨迹颜色为黑色 - CheckLastColor(0); - forceEraser = false; - } - else { - if (inkColor == 0) lastBoardInkColor = 5; - ICCWaterMarkWhite.Visibility = Visibility.Visible; - ICCWaterMarkDark.Visibility = Visibility.Collapsed; - - // 设置为黑板默认背景色 - Color defaultBlackboardColor = Color.FromRgb(22, 41, 36); - - if (currentMode == 1) // 黑板模式 - { - // 设置背景为默认黑板背景色 - GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor); - - // 更新RGB滑块的值为默认黑板背景色 - if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible) - { - UpdateRGBSliders(defaultBlackboardColor); - } - - // 更新自定义背景色为默认黑板背景色 - CustomBackgroundColor = defaultBlackboardColor; - - // 保存到设置 - string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}"; - Settings.Canvas.CustomBackgroundColor = colorHex; - SaveSettingsToFile(); - } - - // 设置墨迹颜色为白色 - CheckLastColor(5); - forceEraser = false; - } - - CheckColorTheme(true); - } - - // 创建背景选项面板 - private void CreateBackgroundPalette() - { - // 确保加载自定义背景色 - LoadCustomBackgroundColor(); - - // 创建一个类似于PenPalette的面板 - BackgroundPalette = new Border - { - Name = "BackgroundPalette", - Visibility = Visibility.Collapsed, - Background = new SolidColorBrush(Colors.White), - Opacity = 1, - BorderBrush = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)), - BorderThickness = new Thickness(1), - CornerRadius = new CornerRadius(8), - Width = 300, - MaxHeight = 400 - }; - - // 确保面板显示在顶层 - Panel.SetZIndex(BackgroundPalette, 1000); - - // 创建面板内容 - var stackPanel = new StackPanel(); - - // 创建标题栏 - var titleBorder = new Border - { - BorderBrush = new SolidColorBrush(Color.FromRgb(0x1e, 0x3a, 0x8a)), - Height = 32, - BorderThickness = new Thickness(0, 0, 0, 1), - CornerRadius = new CornerRadius(8, 8, 0, 0), - Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)), - Margin = new Thickness(-1, -1, -1, 0), - Padding = new Thickness(1, 1, 1, 0) - }; - - var titleCanvas = new System.Windows.Controls.Canvas { Height = 24, ClipToBounds = true }; - var titleText = new TextBlock - { - Text = "背景设置", - Foreground = new SolidColorBrush(Colors.White), - Padding = new Thickness(0, 5, 0, 0), - FontSize = 11, - FontWeight = FontWeights.Bold, - TextAlignment = TextAlignment.Center - }; - System.Windows.Controls.Canvas.SetLeft(titleText, 8); - titleCanvas.Children.Add(titleText); - - // 关闭按钮 - var closeImage = new Image - { - Source = new BitmapImage(new Uri("/Resources/new-icons/close-white.png", UriKind.Relative)), - Height = 16, - Width = 16 - }; - RenderOptions.SetBitmapScalingMode(closeImage, BitmapScalingMode.HighQuality); - closeImage.MouseUp += CloseBordertools_MouseUp; - System.Windows.Controls.Canvas.SetRight(closeImage, 8); - System.Windows.Controls.Canvas.SetTop(closeImage, 4); - titleCanvas.Children.Add(closeImage); - - titleBorder.Child = titleCanvas; - stackPanel.Children.Add(titleBorder); - - // 创建背景选项内容区域 - var contentPanel = new StackPanel { Margin = new Thickness(8) }; - - // 黑板/白板选择 - var modeTitle = new TextBlock - { - Text = "白板模式", - Foreground = new SolidColorBrush(Color.FromRgb(0x17, 0x25, 0x54)), - FontSize = 10, - FontWeight = FontWeights.Bold, - HorizontalAlignment = HorizontalAlignment.Center, - Margin = new Thickness(0, 4, 0, 8) - }; - contentPanel.Children.Add(modeTitle); - - var modePanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center }; - - // 白板按钮 - var whiteboardButton = new Border - { - Width = 60, - Height = 30, - Background = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray), - CornerRadius = new CornerRadius(4), - Margin = new Thickness(0, 0, 8, 0) - }; - var whiteboardText = new TextBlock - { - Text = "白板", - Foreground = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black), - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - whiteboardButton.Child = whiteboardText; - whiteboardButton.MouseUp += (s, args) => { - Settings.Canvas.UsingWhiteboard = true; - SaveSettingsToFile(); - ICCWaterMarkDark.Visibility = Visibility.Visible; - ICCWaterMarkWhite.Visibility = Visibility.Collapsed; - - // 设置为白板默认背景色 - Color defaultWhiteboardColor = Color.FromRgb(234, 235, 237); - - if (currentMode == 1) // 白板模式 - { - // 设置背景为默认白板背景色 - GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor); - - // 更新RGB滑块的值为默认白板背景色 - UpdateRGBSliders(defaultWhiteboardColor); - - // 更新自定义背景色为默认白板背景色 - CustomBackgroundColor = defaultWhiteboardColor; - - // 保存到设置 - string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}"; - Settings.Canvas.CustomBackgroundColor = colorHex; - SaveSettingsToFile(); - } - - // 设置墨迹颜色为黑色 - CheckLastColor(0); - forceEraser = false; - - CheckColorTheme(true); - UpdateBackgroundButtonsState(); - }; - modePanel.Children.Add(whiteboardButton); - - // 黑板按钮 - var blackboardButton = new Border - { - Width = 60, - Height = 30, - Background = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray), - CornerRadius = new CornerRadius(4) - }; - var blackboardText = new TextBlock - { - Text = "黑板", - Foreground = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black), - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }; - blackboardButton.Child = blackboardText; - blackboardButton.MouseUp += (s, args) => { - Settings.Canvas.UsingWhiteboard = false; - SaveSettingsToFile(); - ICCWaterMarkWhite.Visibility = Visibility.Visible; - ICCWaterMarkDark.Visibility = Visibility.Collapsed; - - // 设置为黑板默认背景色 - Color defaultBlackboardColor = Color.FromRgb(22, 41, 36); - - if (currentMode == 1) // 黑板模式 - { - // 设置背景为默认黑板背景色 - GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor); - - // 更新RGB滑块的值为默认黑板背景色 - UpdateRGBSliders(defaultBlackboardColor); - - // 更新自定义背景色为默认黑板背景色 - CustomBackgroundColor = defaultBlackboardColor; - - // 保存到设置 - string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}"; - Settings.Canvas.CustomBackgroundColor = colorHex; - SaveSettingsToFile(); - } - - // 设置墨迹颜色为白色 - CheckLastColor(5); - forceEraser = false; - - CheckColorTheme(true); - UpdateBackgroundButtonsState(); - }; - modePanel.Children.Add(blackboardButton); - - contentPanel.Children.Add(modePanel); - - // 添加一条分隔线 - var separator = new Border - { - Height = 1, - Background = new SolidColorBrush(Color.FromRgb(0xd4, 0xd4, 0xd8)), - Margin = new Thickness(0, 12, 0, 12) - }; - contentPanel.Children.Add(separator); - - // 添加RGB颜色选择器部分 - var colorTitle = new TextBlock - { - Text = "背景颜色", - Foreground = new SolidColorBrush(Color.FromRgb(0x17, 0x25, 0x54)), - FontSize = 10, - FontWeight = FontWeights.Bold, - HorizontalAlignment = HorizontalAlignment.Center, - Margin = new Thickness(0, 4, 0, 8) - }; - contentPanel.Children.Add(colorTitle); - - // 创建颜色预览 - Border colorPreview = new Border - { - Width = 100, - Height = 40, - BorderThickness = new Thickness(1), - BorderBrush = new SolidColorBrush(Color.FromRgb(0xd4, 0xd4, 0xd8)), - Background = new SolidColorBrush(Colors.White), - CornerRadius = new CornerRadius(4), - Margin = new Thickness(0, 0, 0, 10), - HorizontalAlignment = HorizontalAlignment.Center - }; - contentPanel.Children.Add(colorPreview); - - // 获取当前背景颜色 - Color currentBackgroundColor; - if (currentMode == 1) // 白板或黑板模式 - { - if (GridBackgroundCover.Background is SolidColorBrush brush) - { - currentBackgroundColor = brush.Color; - } - else - { - // 默认颜色 - currentBackgroundColor = Settings.Canvas.UsingWhiteboard ? - Color.FromRgb(234, 235, 237) : // 白板默认颜色 - Color.FromRgb(22, 41, 36); // 黑板默认颜色 - } - } - else - { - // 默认白色 - currentBackgroundColor = Colors.White; - } - - // 更新颜色预览 - colorPreview.Background = new SolidColorBrush(currentBackgroundColor); - - // 先创建所有滑块控件 - // R滑块和文本框 - var rPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) }; - var rLabel = new TextBlock { Text = "R:", Width = 20, VerticalAlignment = VerticalAlignment.Center }; - var rSlider = new Slider - { - Minimum = 0, - Maximum = 255, - Value = currentBackgroundColor.R, - Width = 150, - Margin = new Thickness(5, 0, 5, 0), - VerticalAlignment = VerticalAlignment.Center - }; - var rValueText = new TextBlock - { - Text = currentBackgroundColor.R.ToString(), - Width = 30, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Right - }; - - // G滑块和文本框 - var gPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) }; - var gLabel = new TextBlock { Text = "G:", Width = 20, VerticalAlignment = VerticalAlignment.Center }; - var gSlider = new Slider - { - Minimum = 0, - Maximum = 255, - Value = currentBackgroundColor.G, - Width = 150, - Margin = new Thickness(5, 0, 5, 0), - VerticalAlignment = VerticalAlignment.Center - }; - var gValueText = new TextBlock - { - Text = currentBackgroundColor.G.ToString(), - Width = 30, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Right - }; - - // B滑块和文本框 - var bPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) }; - var bLabel = new TextBlock { Text = "B:", Width = 20, VerticalAlignment = VerticalAlignment.Center }; - var bSlider = new Slider - { - Minimum = 0, - Maximum = 255, - Value = currentBackgroundColor.B, - Width = 150, - Margin = new Thickness(5, 0, 5, 0), - VerticalAlignment = VerticalAlignment.Center - }; - var bValueText = new TextBlock - { - Text = currentBackgroundColor.B.ToString(), - Width = 30, - VerticalAlignment = VerticalAlignment.Center, - TextAlignment = TextAlignment.Right - }; - - // 现在添加事件处理程序 - rSlider.ValueChanged += (s, e) => { - int value = (int)e.NewValue; - rValueText.Text = value.ToString(); - UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider); - }; - - gSlider.ValueChanged += (s, e) => { - int value = (int)e.NewValue; - gValueText.Text = value.ToString(); - UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider); - }; - - bSlider.ValueChanged += (s, e) => { - int value = (int)e.NewValue; - bValueText.Text = value.ToString(); - UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider); - }; - - // 添加控件到面板 - rPanel.Children.Add(rLabel); - rPanel.Children.Add(rSlider); - rPanel.Children.Add(rValueText); - contentPanel.Children.Add(rPanel); - - gPanel.Children.Add(gLabel); - gPanel.Children.Add(gSlider); - gPanel.Children.Add(gValueText); - contentPanel.Children.Add(gPanel); - - bPanel.Children.Add(bLabel); - bPanel.Children.Add(bSlider); - bPanel.Children.Add(bValueText); - contentPanel.Children.Add(bPanel); - - // 应用按钮 - var applyButton = new Button - { - Content = "应用颜色", - Margin = new Thickness(0, 10, 0, 0), - Padding = new Thickness(10, 5, 10, 5), - Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)), - Foreground = new SolidColorBrush(Colors.White), - BorderThickness = new Thickness(0), - HorizontalAlignment = HorizontalAlignment.Center - }; - - applyButton.Click += (s, e) => { - Color selectedColor = Color.FromRgb( - (byte)rSlider.Value, - (byte)gSlider.Value, - (byte)bSlider.Value - ); - ApplyCustomBackgroundColor(selectedColor); - }; - - contentPanel.Children.Add(applyButton); - - stackPanel.Children.Add(contentPanel); - - // 将面板添加到父容器 - BackgroundPalette.Child = stackPanel; - - // 获取主窗口中的根网格,确保面板添加到顶层 - Grid mainGrid = FindName("Main_Grid") as Grid; - if (mainGrid != null) - { - // 删除可能已存在的BackgroundPalette - foreach (UIElement element in mainGrid.Children) - { - if (element is Border border && border.Name == "BackgroundPalette") - { - mainGrid.Children.Remove(border); - break; - } - } - - // 重新定位面板 - BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Center; - BackgroundPalette.VerticalAlignment = VerticalAlignment.Center; - BackgroundPalette.Margin = new Thickness(0, 0, 0, 0); - - // 添加到主网格 - mainGrid.Children.Add(BackgroundPalette); - - // 设置面板位置 - var clickElement = FindName("BoardChangeBackgroundColorBtn") as FrameworkElement; - if (clickElement != null) - { - Point position = clickElement.TranslatePoint(new Point(0, 0), mainGrid); - BackgroundPalette.Margin = new Thickness( - position.X - 150, - position.Y + clickElement.ActualHeight + 5, - 0, 0); - BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Left; - BackgroundPalette.VerticalAlignment = VerticalAlignment.Top; - } - } - } - - // 更新背景按钮状态 - private void UpdateBackgroundButtonsState() - { - if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel) - { - if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel) - { - if (contentPanel.Children.Count > 1 && contentPanel.Children[1] is StackPanel modePanel) - { - if (modePanel.Children.Count > 1) - { - var whiteboardButton = modePanel.Children[0] as Border; - var blackboardButton = modePanel.Children[1] as Border; - - if (whiteboardButton != null && whiteboardButton.Child is TextBlock whiteboardText) - { - whiteboardButton.Background = Settings.Canvas.UsingWhiteboard ? - new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : - new SolidColorBrush(Colors.LightGray); - whiteboardText.Foreground = Settings.Canvas.UsingWhiteboard ? - new SolidColorBrush(Colors.White) : - new SolidColorBrush(Colors.Black); - } - - if (blackboardButton != null && blackboardButton.Child is TextBlock blackboardText) - { - blackboardButton.Background = !Settings.Canvas.UsingWhiteboard ? - new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : - new SolidColorBrush(Colors.LightGray); - blackboardText.Foreground = !Settings.Canvas.UsingWhiteboard ? - new SolidColorBrush(Colors.White) : - new SolidColorBrush(Colors.Black); - } - } - } - } - } - } - - // 添加成员变量保存背景面板引用 - private Border BackgroundPalette { get; set; } - - // 添加成员变量保存当前自定义背景色 - private Color? CustomBackgroundColor { get; set; } - - /// - /// 更新颜色预览框的颜色 - /// - private void UpdateColorPreview(Border colorPreview, Slider rSlider, Slider gSlider, Slider bSlider) - { - Color previewColor = Color.FromRgb( - (byte)rSlider.Value, - (byte)gSlider.Value, - (byte)bSlider.Value - ); - colorPreview.Background = new SolidColorBrush(previewColor); - } - - /// - /// 应用自定义背景颜色 - /// - private void ApplyCustomBackgroundColor(Color color) - { - // 保存当前选择的颜色 - CustomBackgroundColor = color; - - // 将颜色转换为十六进制字符串并保存到设置中 - string colorHex = $"#{color.R:X2}{color.G:X2}{color.B:X2}"; - Settings.Canvas.CustomBackgroundColor = colorHex; - - // 只在白板或黑板模式下应用自定义背景色 - if (currentMode == 1) // 白板或黑板模式 - { - // 设置白板/黑板模式下的背景 - GridBackgroundCover.Background = new SolidColorBrush(color); - } - - // 保存设置 - SaveSettingsToFile(); - - // 立即更新界面 - if (BackgroundPalette != null) - { - UpdateBackgroundButtonsState(); - UpdateRGBSliders(color); // 更新RGB滑块的值 - } - - // 显示提示信息 - ShowNotification($"已应用自定义背景色: {colorHex}"); - } - - /// - /// 从设置中加载自定义背景色 - /// - private void LoadCustomBackgroundColor() - { - if (!string.IsNullOrEmpty(Settings.Canvas.CustomBackgroundColor)) - { - try - { - // 解析颜色字符串 - string colorHex = Settings.Canvas.CustomBackgroundColor; - if (colorHex.StartsWith("#") && colorHex.Length == 7) // #RRGGBB 格式 - { - byte r = Convert.ToByte(colorHex.Substring(1, 2), 16); - byte g = Convert.ToByte(colorHex.Substring(3, 2), 16); - byte b = Convert.ToByte(colorHex.Substring(5, 2), 16); - - // 保存到内存中 - CustomBackgroundColor = Color.FromRgb(r, g, b); - } - } - catch (Exception ex) - { - // 解析失败,根据当前模式设置默认颜色 - if (!Settings.Canvas.UsingWhiteboard) - { - // 黑板模式默认颜色 - CustomBackgroundColor = Color.FromRgb(22, 41, 36); - } - else - { - // 白板模式默认颜色 - CustomBackgroundColor = Color.FromRgb(234, 235, 237); - } - - // 可以在这里记录日志 - Console.WriteLine($"解析自定义背景色失败: {ex.Message}"); - } - } - else - { - // 如果没有设置自定义背景色,根据当前模式设置默认颜色 - if (!Settings.Canvas.UsingWhiteboard) - { - // 黑板模式默认颜色 - CustomBackgroundColor = Color.FromRgb(22, 41, 36); - } - else - { - // 白板模式默认颜色 - CustomBackgroundColor = Color.FromRgb(234, 235, 237); - } - } - - // 只在白板或黑板模式下应用自定义背景色 - if (currentMode == 1 && CustomBackgroundColor.HasValue) // 白板或黑板模式 - { - // 设置白板/黑板模式下的背景 - GridBackgroundCover.Background = new SolidColorBrush(CustomBackgroundColor.Value); - - // 更新RGB滑块的值(如果调色板已经创建) - if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible) - { - UpdateRGBSliders(CustomBackgroundColor.Value); - } - } - } - - private void BoardLassoIcon_Click(object sender, RoutedEventArgs e) { - forceEraser = false; - forcePointEraser = false; - drawingShapeMode = 0; - inkCanvas.EditingMode = InkCanvasEditingMode.Select; - SetCursorBasedOnEditingMode(inkCanvas); - } - - private void BoardEraserIconByStrokes_Click(object sender, RoutedEventArgs e) { - //if (BoardEraserByStrokes.Background.ToString() == "#FF679CF4") { - // AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardDeleteIcon); - //} - //else { - // 禁用高级橡皮擦系统 - DisableAdvancedEraserSystem(); - - forceEraser = true; - forcePointEraser = false; - - inkCanvas.EraserShape = new EllipseStylusShape(5, 5); - inkCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke; - drawingShapeMode = 0; - - // 修复:切换到线擦时,确保重置笔的状态 - penType = 0; - drawingAttributes.IsHighlighter = false; - drawingAttributes.StylusTip = StylusTip.Ellipse; - - inkCanvas_EditingModeChanged(inkCanvas, null); - CancelSingleFingerDragMode(); - - HideSubPanels("eraserByStrokes"); - //} - } - - private void BoardSymbolIconDelete_MouseUp(object sender, RoutedEventArgs e) { - PenIcon_Click(null, null); - SymbolIconDelete_MouseUp(null, null); - - // 根据设置决定是否清空图片 - if (Settings.Canvas.ClearCanvasAlsoClearImages) { - // 如果设置为清空图片,则直接清空所有子元素 - Debug.WriteLine("BoardSymbolIconDelete: Clearing all children including images"); - inkCanvas.Children.Clear(); - } else { - // 保存非笔画元素(如图片) - Debug.WriteLine("BoardSymbolIconDelete: Preserving non-stroke elements (images)"); - var preservedElements = PreserveNonStrokeElements(); - Debug.WriteLine($"BoardSymbolIconDelete: Preserved elements count: {preservedElements.Count}"); - inkCanvas.Children.Clear(); - // 恢复非笔画元素 - RestoreNonStrokeElements(preservedElements); - Debug.WriteLine($"BoardSymbolIconDelete: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}"); - } - } - private void BoardSymbolIconDeleteInkAndHistories_MouseUp(object sender, RoutedEventArgs e) - { - PenIcon_Click(null, null); - SymbolIconDelete_MouseUp(null, null); - if (Settings.Canvas.ClearCanvasAndClearTimeMachine == false) timeMachine.ClearStrokeHistory(); - - // 根据设置决定是否清空图片 - if (Settings.Canvas.ClearCanvasAlsoClearImages) { - // 如果设置为清空图片,则直接清空所有子元素 - Debug.WriteLine("BoardSymbolIconDeleteInkAndHistories: Clearing all children including images"); - inkCanvas.Children.Clear(); - } else { - // 保存非笔画元素(如图片) - Debug.WriteLine("BoardSymbolIconDeleteInkAndHistories: Preserving non-stroke elements (images)"); - var preservedElements = PreserveNonStrokeElements(); - Debug.WriteLine($"BoardSymbolIconDeleteInkAndHistories: Preserved elements count: {preservedElements.Count}"); - inkCanvas.Children.Clear(); - // 恢复非笔画元素 - RestoreNonStrokeElements(preservedElements); - Debug.WriteLine($"BoardSymbolIconDeleteInkAndHistories: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}"); - } - } - - private void BoardLaunchEasiCamera_MouseUp(object sender, MouseButtonEventArgs e) { - ImageBlackboard_MouseUp(null, null); - SoftwareLauncher.LaunchEasiCamera("希沃视频展台"); - } - - private void BoardLaunchDesmos_MouseUp(object sender, MouseButtonEventArgs e) { - HideSubPanelsImmediately(); - ImageBlackboard_MouseUp(null, null); - Process.Start("https://www.desmos.com/calculator?lang=zh-CN"); - } - - /// - /// 根据当前背景颜色更新RGB滑块的值 - /// - private void UpdateRGBSliders(Color color) - { - if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel) - { - if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel) - { - // 查找RGB滑块 - Slider rSlider = null; - Slider gSlider = null; - Slider bSlider = null; - - // 遍历面板查找RGB滑块 - foreach (var child in contentPanel.Children) - { - if (child is StackPanel panel && panel.Orientation == Orientation.Horizontal) - { - foreach (var panelChild in panel.Children) - { - if (panelChild is Slider slider) - { - if (panel.Children.Count > 0 && panel.Children[0] is TextBlock label) - { - if (label.Text == "R:") - { - rSlider = slider; - } - else if (label.Text == "G:") - { - gSlider = slider; - } - else if (label.Text == "B:") - { - bSlider = slider; - } - } - } - } - } - } - - // 更新滑块值 - if (rSlider != null && gSlider != null && bSlider != null) - { - rSlider.Value = color.R; - gSlider.Value = color.G; - bSlider.Value = color.B; - } - } - } - } - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs b/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs deleted file mode 100644 index 30aa9cc5..00000000 --- a/Ink Canvas/MainWindow_cs/MW_ClipboardHandler.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media.Imaging; -using Ink_Canvas.Helpers; - -namespace Ink_Canvas -{ - public partial class MainWindow : Window - { - private bool isClipboardMonitoringEnabled = false; - private BitmapSource lastClipboardImage = null; - - // 初始化剪贴板监控 - private void InitializeClipboardMonitoring() - { - try - { - // 监听剪贴板变化 - ClipboardNotification.ClipboardUpdate += OnClipboardUpdate; - isClipboardMonitoringEnabled = true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"初始化剪贴板监控失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 剪贴板内容变化事件处理 - private void OnClipboardUpdate() - { - try - { - if (Clipboard.ContainsImage()) - { - var clipboardImage = Clipboard.GetImage(); - if (clipboardImage != null && clipboardImage != lastClipboardImage) - { - lastClipboardImage = clipboardImage; - // 在白板模式下显示粘贴提示 - if (currentMode == 1) // 白板模式 - { - ShowPasteNotification(); - } - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理剪贴板更新失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 显示粘贴提示 - private void ShowPasteNotification() - { - try - { - Dispatcher.Invoke(() => - { - ShowNotification("检测到剪贴板中有图片,右键点击白板可粘贴"); - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"显示粘贴提示失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 处理右键菜单显示 - private void ShowPasteContextMenu(Point position) - { - try - { - if (!Clipboard.ContainsImage()) return; - - // 创建右键菜单 - var contextMenu = new ContextMenu(); - - var pasteMenuItem = new MenuItem - { - Header = "粘贴图片" - }; - - pasteMenuItem.Click += async (s, e) => await PasteImageFromClipboard(position); - contextMenu.Items.Add(pasteMenuItem); - - // 显示菜单 - contextMenu.IsOpen = true; - contextMenu.PlacementTarget = inkCanvas; - contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"显示粘贴菜单失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 从剪贴板粘贴图片 - private async Task PasteImageFromClipboard(Point? position = null) - { - try - { - if (!Clipboard.ContainsImage()) - { - ShowNotification("剪贴板中没有图片"); - return; - } - - var clipboardImage = Clipboard.GetImage(); - if (clipboardImage == null) - { - ShowNotification("无法获取剪贴板图片"); - return; - } - - // 创建Image控件 - var image = new Image - { - Source = clipboardImage, - Width = clipboardImage.PixelWidth, - Height = clipboardImage.PixelHeight, - Stretch = System.Windows.Media.Stretch.Fill - }; - - // 生成唯一名称 - string timestamp = "img_clipboard_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - image.Name = timestamp; - - // 设置位置 - if (position.HasValue) - { - // 在指定位置居中显示 - InkCanvas.SetLeft(image, position.Value.X - image.Width / 2); - InkCanvas.SetTop(image, position.Value.Y - image.Height / 2); - } - else - { - // 使用与文件选择相同的居中和缩放逻辑 - CenterAndScaleElement(image); - } - - // 添加到画布 - inkCanvas.Children.Add(image); - - // 添加鼠标事件处理 - image.MouseDown += UIElement_MouseDown; - image.IsManipulationEnabled = true; - - // 提交到历史记录 - timeMachine.CommitElementInsertHistory(image); - - ShowNotification("图片已从剪贴板粘贴"); - } - catch (Exception ex) - { - ShowNotification($"粘贴图片失败: {ex.Message}"); - LogHelper.WriteLogToFile($"粘贴图片失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - - - // 处理白板右键事件 - private void InkCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e) - { - try - { - // 只在白板模式下处理 - if (currentMode != 1) return; - - // 检查是否有图片在剪贴板中 - if (Clipboard.ContainsImage()) - { - var position = e.GetPosition(inkCanvas); - ShowPasteContextMenu(position); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理右键事件失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 处理全局粘贴快捷键 - private async void HandleGlobalPaste(object sender, ExecutedRoutedEventArgs e) - { - try - { - // 只在白板模式下处理 - if (currentMode != 1) return; - - if (Clipboard.ContainsImage()) - { - await PasteImageFromClipboard(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理全局粘贴失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 清理剪贴板监控 - private void CleanupClipboardMonitoring() - { - try - { - if (isClipboardMonitoringEnabled) - { - ClipboardNotification.ClipboardUpdate -= OnClipboardUpdate; - isClipboardMonitoringEnabled = false; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理剪贴板监控失败: {ex.Message}", LogHelper.LogType.Error); - } - } - } - - // 剪贴板通知类 - public static class ClipboardNotification - { - public static event Action ClipboardUpdate; - - private static System.Windows.Forms.Timer clipboardTimer; - private static string lastClipboardText = ""; - private static bool lastHadImage = false; - - static ClipboardNotification() - { - clipboardTimer = new System.Windows.Forms.Timer(); - clipboardTimer.Interval = 500; // 每500ms检查一次 - clipboardTimer.Tick += CheckClipboard; - clipboardTimer.Start(); - } - - private static void CheckClipboard(object sender, EventArgs e) - { - try - { - bool currentHasImage = Clipboard.ContainsImage(); - string currentText = Clipboard.ContainsText() ? Clipboard.GetText() : ""; - - if (currentHasImage != lastHadImage || currentText != lastClipboardText) - { - lastHadImage = currentHasImage; - lastClipboardText = currentText; - ClipboardUpdate?.Invoke(); - } - } - catch - { - // 忽略剪贴板访问错误 - } - } - - public static void Stop() - { - clipboardTimer?.Stop(); - clipboardTimer?.Dispose(); - } - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs b/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs deleted file mode 100644 index 1591aa7f..00000000 --- a/Ink Canvas/MainWindow_cs/MW_ElementsControls.cs +++ /dev/null @@ -1,430 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using Ink_Canvas.Helpers; -using Microsoft.Win32; - -namespace Ink_Canvas -{ - public partial class MainWindow : Window - { - #region Image - private async void BtnImageInsert_Click(object sender, RoutedEventArgs e) - { - OpenFileDialog openFileDialog = new OpenFileDialog(); - openFileDialog.Filter = "Image files (*.jpg; *.jpeg; *.png; *.bmp)|*.jpg;*.jpeg;*.png;*.bmp"; - - if (openFileDialog.ShowDialog() == true) - { - string filePath = openFileDialog.FileName; - - Image image = await CreateAndCompressImageAsync(filePath); - - if (image != null) - { - string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - image.Name = timestamp; - - CenterAndScaleElement(image); - inkCanvas.Children.Add(image); - - // 添加鼠标事件处理,使图片可以被选择 - image.MouseDown += UIElement_MouseDown; - image.IsManipulationEnabled = true; - - timeMachine.CommitElementInsertHistory(image); - } - } - } - - private async Task CreateAndCompressImageAsync(string filePath) - { - string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency"); - if (!Directory.Exists(savePath)) - { - Directory.CreateDirectory(savePath); - } - - string fileExtension = Path.GetExtension(filePath); - string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - string newFilePath = Path.Combine(savePath, timestamp + fileExtension); - - await Task.Run(() => File.Copy(filePath, newFilePath, true)); - - return await Dispatcher.InvokeAsync(() => - { - BitmapImage bitmapImage = new BitmapImage(); - bitmapImage.BeginInit(); - bitmapImage.UriSource = new Uri(newFilePath); - bitmapImage.CacheOption = BitmapCacheOption.OnLoad; - bitmapImage.EndInit(); - - int width = bitmapImage.PixelWidth; - int height = bitmapImage.PixelHeight; - - Image image = new Image(); - // 设置拉伸模式为Fill,支持任意比例缩放 - image.Stretch = Stretch.Fill; - - if (isLoaded && Settings.Canvas.IsCompressPicturesUploaded && (width > 1920 || height > 1080)) - { - double scaleX = 1920.0 / width; - double scaleY = 1080.0 / height; - double scale = Math.Min(scaleX, scaleY); - - TransformedBitmap transformedBitmap = new TransformedBitmap(bitmapImage, new ScaleTransform(scale, scale)); - - image.Source = transformedBitmap; - image.Width = transformedBitmap.PixelWidth; - image.Height = transformedBitmap.PixelHeight; - } - else - { - image.Source = bitmapImage; - image.Width = width; - image.Height = height; - } - - return image; - }); - } - #endregion - - #region Media - private async void BtnMediaInsert_Click(object sender, RoutedEventArgs e) - { - OpenFileDialog openFileDialog = new OpenFileDialog(); - openFileDialog.Filter = "Media files (*.mp4; *.avi; *.wmv)|*.mp4;*.avi;*.wmv"; - - if (openFileDialog.ShowDialog() == true) - { - string filePath = openFileDialog.FileName; - - byte[] mediaBytes = await Task.Run(() => File.ReadAllBytes(filePath)); - - MediaElement mediaElement = await CreateMediaElementAsync(filePath); - - if (mediaElement != null) - { - CenterAndScaleElement(mediaElement); - - InkCanvas.SetLeft(mediaElement, 0); - InkCanvas.SetTop(mediaElement, 0); - inkCanvas.Children.Add(mediaElement); - - // 添加鼠标事件处理,使媒体元素可以被选择 - mediaElement.MouseDown += UIElement_MouseDown; - mediaElement.IsManipulationEnabled = true; - - mediaElement.LoadedBehavior = MediaState.Manual; - mediaElement.UnloadedBehavior = MediaState.Manual; - mediaElement.Loaded += async (_, args) => - { - mediaElement.Play(); - await Task.Delay(100); - mediaElement.Pause(); - }; - - timeMachine.CommitElementInsertHistory(mediaElement); - } - } - } - - private async Task CreateMediaElementAsync(string filePath) - { - string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency"); - if (!Directory.Exists(savePath)) - { - Directory.CreateDirectory(savePath); - } - return await Dispatcher.InvokeAsync(() => - { - MediaElement mediaElement = new MediaElement(); - mediaElement.Source = new Uri(filePath); - string timestamp = "media_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff"); - mediaElement.Name = timestamp; - mediaElement.LoadedBehavior = MediaState.Manual; - mediaElement.UnloadedBehavior = MediaState.Manual; - - mediaElement.Width = 256; - mediaElement.Height = 256; - - string fileExtension = Path.GetExtension(filePath); - string newFilePath = Path.Combine(savePath, mediaElement.Name + fileExtension); - - File.Copy(filePath, newFilePath, true); - - mediaElement.Source = new Uri(newFilePath); - - return mediaElement; - }); - } - #endregion - - #region Image Operations - - /// - /// 旋转图片 - /// - /// 要旋转的图片 - /// 旋转角度(正数为顺时针,负数为逆时针) - private void RotateImage(Image image, double angle) - { - if (image == null) return; - - try - { - // 获取当前的变换 - var transformGroup = image.RenderTransform as TransformGroup ?? new TransformGroup(); - - // 查找现有的旋转变换 - RotateTransform rotateTransform = null; - foreach (Transform transform in transformGroup.Children) - { - if (transform is RotateTransform rt) - { - rotateTransform = rt; - break; - } - } - - // 如果没有旋转变换,创建一个新的 - if (rotateTransform == null) - { - rotateTransform = new RotateTransform(); - transformGroup.Children.Add(rotateTransform); - } - - // 设置旋转中心为图片中心 - rotateTransform.CenterX = image.ActualWidth / 2; - rotateTransform.CenterY = image.ActualHeight / 2; - - // 累加旋转角度 - rotateTransform.Angle = (rotateTransform.Angle + angle) % 360; - - // 应用变换 - image.RenderTransform = transformGroup; - - // 提交到时间机器以支持撤销 - // 注意:旋转操作目前不支持撤销,因为需要更复杂的历史记录机制 - } - catch (Exception ex) - { - // 记录错误但不中断程序 - System.Diagnostics.Debug.WriteLine($"旋转图片时发生错误: {ex.Message}"); - } - } - - /// - /// 克隆图片 - /// - /// 要克隆的图片 - private void CloneImage(Image image) - { - if (image == null) return; - - try - { - // 创建图片的副本 - var clonedImage = new Image - { - Source = image.Source, - Width = image.Width, - Height = image.Height, - Stretch = image.Stretch, - RenderTransform = image.RenderTransform?.Clone() as Transform - }; - - // 设置位置,稍微偏移以避免重叠 - InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(image) + 20); - InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(image) + 20); - - // 添加鼠标事件处理,使图片可以被选择 - clonedImage.MouseDown += UIElement_MouseDown; - clonedImage.IsManipulationEnabled = true; - - // 添加到画布 - inkCanvas.Children.Add(clonedImage); - - // 选择新克隆的图片 - DeselectUIElement(); - SelectUIElement(clonedImage); - - // 提交到时间机器以支持撤销 - timeMachine.CommitElementInsertHistory(clonedImage); - } - catch (Exception ex) - { - // 记录错误但不中断程序 - System.Diagnostics.Debug.WriteLine($"克隆图片时发生错误: {ex.Message}"); - } - } - - /// - /// 克隆图片到新页面 - /// - /// 要克隆的图片 - private void CloneImageToNewBoard(Image image) - { - if (image == null) return; - - try - { - // 创建图片的副本 - var clonedImage = new Image - { - Source = image.Source, - Width = image.Width, - Height = image.Height, - Stretch = image.Stretch, - RenderTransform = image.RenderTransform?.Clone() as Transform - }; - - // 设置位置,稍微偏移以避免重叠 - InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(image) + 20); - InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(image) + 20); - - // 添加鼠标事件处理,使图片可以被选择 - clonedImage.MouseDown += UIElement_MouseDown; - clonedImage.IsManipulationEnabled = true; - - // 创建新页面 - BtnWhiteBoardAdd_Click(null, null); - - // 添加到新页面的画布 - inkCanvas.Children.Add(clonedImage); - - // 选择新克隆的图片 - DeselectUIElement(); - SelectUIElement(clonedImage); - - // 提交到时间机器以支持撤销 - timeMachine.CommitElementInsertHistory(clonedImage); - } - catch (Exception ex) - { - // 记录错误但不中断程序 - System.Diagnostics.Debug.WriteLine($"克隆图片到新页面时发生错误: {ex.Message}"); - } - } - - /// - /// 缩放图片 - /// - /// 要缩放的图片 - /// 缩放因子(大于1为放大,小于1为缩小) - private void ScaleImage(Image image, double scaleFactor) - { - if (image == null) return; - - try - { - // 获取当前的变换 - var transformGroup = image.RenderTransform as TransformGroup ?? new TransformGroup(); - - // 查找现有的缩放变换 - ScaleTransform scaleTransform = null; - foreach (Transform transform in transformGroup.Children) - { - if (transform is ScaleTransform st) - { - scaleTransform = st; - break; - } - } - - // 如果没有缩放变换,创建一个新的 - if (scaleTransform == null) - { - scaleTransform = new ScaleTransform(); - transformGroup.Children.Add(scaleTransform); - } - - // 设置缩放中心为图片中心 - scaleTransform.CenterX = image.ActualWidth / 2; - scaleTransform.CenterY = image.ActualHeight / 2; - - // 应用缩放因子 - scaleTransform.ScaleX *= scaleFactor; - scaleTransform.ScaleY *= scaleFactor; - - // 应用变换 - image.RenderTransform = transformGroup; - - // 提交到时间机器以支持撤销 - // 注意:缩放操作目前不支持撤销,因为需要更复杂的历史记录机制 - } - catch (Exception ex) - { - // 记录错误但不中断程序 - System.Diagnostics.Debug.WriteLine($"缩放图片时发生错误: {ex.Message}"); - } - } - - /// - /// 删除图片 - /// - /// 要删除的图片 - private void DeleteImage(Image image) - { - if (image == null) return; - - try - { - // 从画布中移除图片 - if (inkCanvas.Children.Contains(image)) - { - inkCanvas.Children.Remove(image); - - // 取消选择 - DeselectUIElement(); - - // 提交到时间机器以支持撤销 - timeMachine.CommitElementRemoveHistory(image); - } - } - catch (Exception ex) - { - // 记录错误但不中断程序 - System.Diagnostics.Debug.WriteLine($"删除图片时发生错误: {ex.Message}"); - } - } - - #endregion - - private void CenterAndScaleElement(FrameworkElement element) - { - double maxWidth = SystemParameters.PrimaryScreenWidth / 2; - double maxHeight = SystemParameters.PrimaryScreenHeight / 2; - - double scaleX = maxWidth / element.Width; - double scaleY = maxHeight / element.Height; - double scale = Math.Min(scaleX, scaleY); - - // 直接设置元素的大小,而不使用RenderTransform - double newWidth = element.Width * scale; - double newHeight = element.Height * scale; - - element.Width = newWidth; - element.Height = newHeight; - - // 计算居中位置 - double canvasWidth = inkCanvas.ActualWidth; - double canvasHeight = inkCanvas.ActualHeight; - double centerX = (canvasWidth - newWidth) / 2; - double centerY = (canvasHeight - newHeight) / 2; - - // 直接设置位置,而不使用RenderTransform - InkCanvas.SetLeft(element, centerX); - InkCanvas.SetTop(element, centerY); - - // 清除任何现有的RenderTransform - element.RenderTransform = Transform.Identity; - } - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_Eraser.cs b/Ink Canvas/MainWindow_cs/MW_Eraser.cs deleted file mode 100644 index 3b0fc6f8..00000000 --- a/Ink Canvas/MainWindow_cs/MW_Eraser.cs +++ /dev/null @@ -1,735 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using Ink_Canvas.Helpers; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - - // 新橡皮擦系统的核心变量 - public bool isUsingAdvancedEraser; - private IncrementalStrokeHitTester advancedHitTester; - - // 橡皮擦配置 - public double currentEraserSize = 64; - public bool isCurrentEraserCircle; - public bool isUsingStrokeEraser; - - // 视觉反馈相关 - private Matrix eraserTransformMatrix; - private Point lastEraserPosition; - private bool isEraserVisible; - - // 性能优化相关 - private DateTime lastEraserUpdate = DateTime.Now; - private const double ERASER_UPDATE_INTERVAL = 16.67; // 约60FPS - - // 锁定笔画的GUID(如果不存在则创建一个默认值) - private static readonly Guid IsLockGuid = new Guid("12345678-1234-1234-1234-123456789ABC"); - - // 橡皮擦视觉反馈控件 - private DrawingVisual eraserVisual = new DrawingVisual(); - private VisualCanvas eraserOverlayCanvas = null; - private Border eraserVisualBorder; // 用于显示橡皮擦视觉反馈的Border - - // 兼容性属性:模拟原有的EraserOverlay_DrawingVisual - private VisualCanvas EraserOverlay_DrawingVisual => eraserOverlayCanvas; - - // 兼容性保持 - [Obsolete("使用 isUsingAdvancedEraser 替代")] - public bool isUsingGeometryEraser - { - get => isUsingAdvancedEraser; - set => isUsingAdvancedEraser = value; - } - - [Obsolete("使用 currentEraserSize 替代")] - public double eraserWidth - { - get => currentEraserSize; - set => currentEraserSize = value; - } - - [Obsolete("使用 isCurrentEraserCircle 替代")] - public bool isEraserCircleShape - { - get => isCurrentEraserCircle; - set => isCurrentEraserCircle = value; - } - - [Obsolete("使用 isUsingStrokeEraser 替代")] - public bool isUsingStrokesEraser - { - get => isUsingStrokeEraser; - set => isUsingStrokeEraser = value; - } - - [Obsolete("使用 eraserTransformMatrix 替代")] - private Matrix scaleMatrix - { - get => eraserTransformMatrix; - set => eraserTransformMatrix = value; - } - - /// - /// 新橡皮擦覆盖层加载事件处理 - /// - private void EraserOverlay_Loaded(object sender, RoutedEventArgs e) { - var border = (Border)sender; - - // 初始化覆盖层 - InitializeEraserOverlay(border); - - Trace.WriteLine("Advanced Eraser: Overlay loaded and initialized"); - } - - /// - /// 开始高级橡皮擦操作 - /// - private void StartAdvancedEraserOperation(object sender) { - if (isUsingAdvancedEraser) return; - - // 设置操作状态 - isUsingAdvancedEraser = true; - isEraserVisible = true; - - // 更新橡皮擦尺寸 - UpdateEraserSize(); - - // 获取inkCanvas引用 - var inkCanvas = FindName("inkCanvas") as InkCanvas; - if (inkCanvas == null) return; - - // 根据橡皮擦形状创建碰撞检测器 - StylusShape eraserShape = CreateEraserShape(); - advancedHitTester = inkCanvas.Strokes.GetIncrementalStrokeHitTester(eraserShape); - advancedHitTester.StrokeHit += OnAdvancedEraserStrokeHit; - - // 初始化变换矩阵 - InitializeEraserTransform(); - } - - /// - /// 创建橡皮擦形状 - /// - private StylusShape CreateEraserShape() - { - if (isCurrentEraserCircle) { - return new EllipseStylusShape(currentEraserSize, currentEraserSize); - } - - // 矩形橡皮擦,使用与原来相同的逻辑 - return new RectangleStylusShape(currentEraserSize, currentEraserSize / 0.6); - } - - /// - /// 初始化橡皮擦变换矩阵 - /// - private void InitializeEraserTransform() { - eraserTransformMatrix = new Matrix(); - - if (isCurrentEraserCircle) { - // 圆形橡皮擦:等比例缩放 - var scale = currentEraserSize / 56.0; // 基于56x56的基准尺寸 - eraserTransformMatrix.ScaleAt(scale, scale, 0, 0); - } else { - // 矩形橡皮擦:保持传统比例 - var scaleX = currentEraserSize / 38.0; - var scaleY = (currentEraserSize * 56 / 38) / 56.0; - eraserTransformMatrix.ScaleAt(scaleX, scaleY, 0, 0); - } - } - - /// - /// 更新橡皮擦尺寸 - /// - private void UpdateEraserSize() { - // 使用与原来相同的逻辑计算橡皮擦尺寸 - double k = 1.0; - - switch (Settings.Canvas.EraserSize) { - case 0: k = Settings.Canvas.EraserShapeType == 0 ? 0.5 : 0.7; break; - case 1: k = Settings.Canvas.EraserShapeType == 0 ? 0.8 : 0.9; break; - case 2: k = 1.0; break; - case 3: k = Settings.Canvas.EraserShapeType == 0 ? 1.25 : 1.2; break; - case 4: k = Settings.Canvas.EraserShapeType == 0 ? 1.5 : 1.3; break; - } - - // 更新形状类型 - isCurrentEraserCircle = (Settings.Canvas.EraserShapeType == 0); - - // 根据形状类型设置尺寸 - if (isCurrentEraserCircle) { - currentEraserSize = k * 90; // 圆形橡皮擦 - } else { - currentEraserSize = k * 90 * 0.6; // 矩形橡皮擦宽度 - } - } - - /// - /// 结束高级橡皮擦操作 - /// - private void EndAdvancedEraserOperation(object sender) { - if (!isUsingAdvancedEraser) return; - - // 重置操作状态 - isUsingAdvancedEraser = false; - isEraserVisible = false; - - // 释放鼠标捕获 - if (sender is Border border) { - border.ReleaseMouseCapture(); - } - - // 隐藏橡皮擦视觉反馈 - HideEraserFeedback(); - - // 结束碰撞检测 - if (advancedHitTester != null) { - advancedHitTester.EndHitTesting(); - advancedHitTester = null; - } - - // 提交橡皮擦历史记录 - CommitEraserHistory(); - } - - /// - /// 隐藏橡皮擦视觉反馈 - /// - private void HideEraserFeedback() { - try { - if (eraserVisualBorder != null) { - eraserVisualBorder.Visibility = Visibility.Collapsed; - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error hiding feedback - {ex.Message}"); - } - } - - /// - /// 提交橡皮擦历史记录 - /// - private void CommitEraserHistory() { - try { - if (ReplacedStroke != null || AddedStroke != null) { - timeMachine.CommitStrokeEraseHistory(ReplacedStroke, AddedStroke); - AddedStroke = null; - ReplacedStroke = null; - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error committing history - {ex.Message}"); - } - } - - /// - /// 高级橡皮擦笔画碰撞事件处理 - /// - private void OnAdvancedEraserStrokeHit(object sender, StrokeHitEventArgs args) { - try { - var inkCanvas = FindName("inkCanvas") as InkCanvas; - if (inkCanvas == null) return; - - var eraseResult = args.GetPointEraseResults(); - var strokeToReplace = new StrokeCollection { args.HitStroke }; - - // 过滤锁定的笔画 - var filteredToReplace = strokeToReplace.Where(stroke => !stroke.ContainsPropertyData(IsLockGuid)); - var filteredToReplaceArray = filteredToReplace as Stroke[] ?? filteredToReplace.ToArray(); - - if (!filteredToReplaceArray.Any()) return; - - var filteredResult = eraseResult.Where(stroke => !stroke.ContainsPropertyData(IsLockGuid)); - var filteredResultArray = filteredResult as Stroke[] ?? filteredResult.ToArray(); - - // 执行笔画替换或删除 - if (filteredResultArray.Any()) { - inkCanvas.Strokes.Replace( - new StrokeCollection(filteredToReplaceArray), - new StrokeCollection(filteredResultArray) - ); - } else { - inkCanvas.Strokes.Remove(new StrokeCollection(filteredToReplaceArray)); - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error in stroke hit - {ex.Message}"); - } - } - - /// - /// 更新高级橡皮擦位置 - /// - private void UpdateAdvancedEraserPosition(object sender, Point position) { - // 移除isUsingAdvancedEraser检查,让视觉反馈始终更新 - // if (!isUsingAdvancedEraser) return; - - // 性能优化:限制更新频率 - var now = DateTime.Now; - if ((now - lastEraserUpdate).TotalMilliseconds < ERASER_UPDATE_INTERVAL) { - return; - } - lastEraserUpdate = now; - - // 更新位置 - lastEraserPosition = position; - - // 更新视觉反馈(始终执行) - UpdateEraserVisualFeedback(position); - - // 只有在实际使用橡皮擦时才处理擦除 - if (isUsingAdvancedEraser) { - // 处理不同的橡皮擦模式 - if (isUsingStrokeEraser) { - ProcessStrokeEraserAtPosition(position); - } else { - ProcessGeometryEraserAtPosition(position); - } - } - } - - /// - /// 在指定位置处理笔画橡皮擦 - /// - private void ProcessStrokeEraserAtPosition(Point position) { - try { - var inkCanvas = FindName("inkCanvas") as InkCanvas; - if (inkCanvas == null) return; - - var hitStrokes = inkCanvas.Strokes.HitTest(position) - .Where(stroke => !stroke.ContainsPropertyData(IsLockGuid)); - var strokesArray = hitStrokes as Stroke[] ?? hitStrokes.ToArray(); - - if (strokesArray.Any()) { - inkCanvas.Strokes.Remove(new StrokeCollection(strokesArray)); - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error in stroke eraser - {ex.Message}"); - } - } - - /// - /// 在指定位置处理几何橡皮擦 - /// - private void ProcessGeometryEraserAtPosition(Point position) { - try { - if (advancedHitTester != null) { - advancedHitTester.AddPoint(position); - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error in geometry eraser - {ex.Message}"); - } - } - - /// - /// 更新橡皮擦视觉反馈 - /// - private void UpdateEraserVisualFeedback(Point position) { - try { - // 获取或创建橡皮擦视觉反馈Border - if (eraserVisualBorder == null) { - eraserVisualBorder = new Border { - Background = new SolidColorBrush(Colors.Transparent), - BorderBrush = new SolidColorBrush(Colors.Transparent), - BorderThickness = new Thickness(0), - IsHitTestVisible = false, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top, - Opacity = 1 - }; - Panel.SetZIndex(eraserVisualBorder, 1001); - - // 将Border添加到InkCanvasGridForInkReplay中 - var inkCanvasGrid = FindName("InkCanvasGridForInkReplay") as Grid; - if (inkCanvasGrid != null) { - inkCanvasGrid.Children.Add(eraserVisualBorder); - Trace.WriteLine("Advanced Eraser: Visual feedback border added to grid"); - } else { - Trace.WriteLine("Advanced Eraser: Failed to find InkCanvasGridForInkReplay"); - return; // 如果找不到Grid,直接返回 - } - } - - if (eraserVisualBorder != null) { - // 创建橡皮擦视觉反馈 - var eraserImage = CreateEraserVisualImage(); - - // 清除Border的内容并添加新的图像 - eraserVisualBorder.Child = eraserImage; - - // 更新橡皮擦位置和大小 - if (isCurrentEraserCircle) { - var radius = currentEraserSize / 2; - eraserVisualBorder.Width = currentEraserSize; - eraserVisualBorder.Height = currentEraserSize; - - // 使用Margin来定位,因为Border在Grid中 - eraserVisualBorder.Margin = new Thickness( - position.X - radius, - position.Y - radius, - 0, 0); - } else { - // 矩形橡皮擦,使用与原来相同的逻辑 - var height = currentEraserSize / 0.6; - eraserVisualBorder.Width = currentEraserSize; - eraserVisualBorder.Height = height; - - // 使用Margin来定位,因为Border在Grid中 - eraserVisualBorder.Margin = new Thickness( - position.X - currentEraserSize / 2, - position.Y - height / 2, - 0, 0); - } - - eraserVisualBorder.Visibility = Visibility.Visible; - Trace.WriteLine($"Advanced Eraser: Visual feedback updated to ({position.X:F1}, {position.Y:F1})"); - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error updating visual feedback - {ex.Message}"); - } - } - - /// - /// 创建橡皮擦视觉图像 - /// - private Image CreateEraserVisualImage() { - try { - // 根据橡皮擦形状选择对应的DrawingGroup资源 - string resourceKey = isCurrentEraserCircle ? "EraserCircleDrawingGroup" : "EraserDrawingGroup"; - - // 尝试从资源字典中获取DrawingGroup - var drawingGroup = TryFindResource(resourceKey) as DrawingGroup; - if (drawingGroup == null) { - // 如果找不到资源,创建默认的橡皮擦图像 - return CreateDefaultEraserImage(); - } - - // 创建变换后的DrawingGroup - var transformedGroup = new DrawingGroup(); - transformedGroup.Children.Add(drawingGroup); - - // 应用缩放变换 - var transform = new ScaleTransform(); - if (isCurrentEraserCircle) { - var scale = currentEraserSize / 56.0; // 基于56x56的基准尺寸 - transform.ScaleX = scale; - transform.ScaleY = scale; - } else { - var scaleX = currentEraserSize / 38.0; - var scaleY = (currentEraserSize / 0.6) / 56.0; - transform.ScaleX = scaleX; - transform.ScaleY = scaleY; - } - transformedGroup.Transform = transform; - - // 创建DrawingImage - var drawingImage = new DrawingImage(transformedGroup); - - // 创建Image控件 - var image = new Image { - Source = drawingImage, - Stretch = Stretch.None - }; - RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality); - - return image; - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error creating eraser visual image - {ex.Message}"); - return CreateDefaultEraserImage(); - } - } - - /// - /// 创建默认的橡皮擦图像(当资源不可用时) - /// - private Image CreateDefaultEraserImage() { - try { - // 创建一个简单的几何图形作为默认橡皮擦 - Geometry geometry; - if (isCurrentEraserCircle) { - geometry = new EllipseGeometry(new Point(28, 28), 28, 28); - } else { - geometry = new RectangleGeometry(new Rect(0, 0, 38, 56)); - } - - var brush = new SolidColorBrush(Colors.LightGray); - var pen = new Pen(new SolidColorBrush(Colors.DarkGray), 1); - - var geometryDrawing = new GeometryDrawing(brush, pen, geometry); - var drawingGroup = new DrawingGroup(); - drawingGroup.Children.Add(geometryDrawing); - - // 应用缩放变换 - var transform = new ScaleTransform(); - if (isCurrentEraserCircle) { - var scale = currentEraserSize / 56.0; - transform.ScaleX = scale; - transform.ScaleY = scale; - } else { - var scaleX = currentEraserSize / 38.0; - var scaleY = (currentEraserSize / 0.6) / 56.0; - transform.ScaleX = scaleX; - transform.ScaleY = scaleY; - } - drawingGroup.Transform = transform; - - var drawingImage = new DrawingImage(drawingGroup); - var image = new Image { - Source = drawingImage, - Stretch = Stretch.None - }; - - return image; - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error creating default eraser image - {ex.Message}"); - return null; - } - } - - /// - /// 兼容性方法:旧版橡皮擦几何碰撞处理 - /// - [Obsolete("使用 OnAdvancedEraserStrokeHit 替代")] - private void EraserGeometry_StrokeHit(object sender, StrokeHitEventArgs args) { - OnAdvancedEraserStrokeHit(sender, args); - } - - /// - /// 兼容性方法:旧版橡皮擦移动处理 - /// - [Obsolete("使用 UpdateAdvancedEraserPosition 替代")] - private void EraserOverlay_PointerMove(object sender, Point pt) { - UpdateAdvancedEraserPosition(sender, pt); - } - - /// - /// 兼容性方法:旧版橡皮擦按下处理 - /// - [Obsolete("使用 StartAdvancedEraserOperation 替代")] - private void EraserOverlay_PointerDown(object sender) { - StartAdvancedEraserOperation(sender); - } - - /// - /// 兼容性方法:旧版橡皮擦抬起处理 - /// - [Obsolete("使用 EndAdvancedEraserOperation 替代")] - private void EraserOverlay_PointerUp(object sender) { - EndAdvancedEraserOperation(sender); - } - - /// - /// 获取当前橡皮擦状态信息(用于调试) - /// - public string GetEraserStatusInfo() { - return "Advanced Eraser Status:\n" + - $"- Active: {isUsingAdvancedEraser}\n" + - $"- Size: {currentEraserSize:F1}\n" + - $"- Shape: {(isCurrentEraserCircle ? "Circle" : "Rectangle")}\n" + - $"- Mode: {(isUsingStrokeEraser ? "Stroke" : "Geometry")}\n" + - $"- Visible: {isEraserVisible}\n" + - $"- Last Position: ({lastEraserPosition.X:F1}, {lastEraserPosition.Y:F1})"; - } - - /// - /// 重置橡皮擦状态 - /// - public void ResetEraserState() { - isUsingAdvancedEraser = false; - isEraserVisible = false; - lastEraserPosition = new Point(); - - if (advancedHitTester != null) { - advancedHitTester.EndHitTesting(); - advancedHitTester = null; - } - - HideEraserFeedback(); - - // 清理视觉反馈Border - if (eraserVisualBorder != null) { - var inkCanvasGrid = FindName("InkCanvasGridForInkReplay") as Grid; - if (inkCanvasGrid != null) { - inkCanvasGrid.Children.Remove(eraserVisualBorder); - } - eraserVisualBorder = null; - } - } - - /// - /// 应用高级橡皮擦形状到InkCanvas - /// - public void ApplyAdvancedEraserShape() { - try { - var inkCanvas = FindName("inkCanvas") as InkCanvas; - if (inkCanvas == null) return; - - // 更新橡皮擦尺寸和形状 - UpdateEraserSize(); - - // 创建橡皮擦形状 - StylusShape eraserShape = CreateEraserShape(); - - // 应用到InkCanvas - inkCanvas.EraserShape = eraserShape; - - Trace.WriteLine($"Advanced Eraser: Applied shape - Size: {currentEraserSize}, Circle: {isCurrentEraserCircle}"); - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error applying shape - {ex.Message}"); - - // 回退到传统方法 - try { - ApplyCurrentEraserShape(); - } catch (Exception fallbackEx) { - Trace.WriteLine($"Advanced Eraser: Fallback also failed - {fallbackEx.Message}"); - } - } - } - - /// - /// 启用高级橡皮擦系统 - /// - public void EnableAdvancedEraserSystem() { - try { - // 获取橡皮擦覆盖层 - var eraserOverlay = FindName("AdvancedEraserOverlay") as Border; - if (eraserOverlay != null) { - // 启用覆盖层的交互 - eraserOverlay.IsHitTestVisible = true; - - // 确保覆盖层在橡皮擦模式下启用 - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - eraserOverlay.IsHitTestVisible = true; - Trace.WriteLine("Advanced Eraser: Overlay enabled for eraser mode"); - } - - // 设置覆盖层的大小以覆盖整个InkCanvas - var inkCanvasControl = FindName("inkCanvas") as InkCanvas; - if (inkCanvasControl != null) { - eraserOverlay.Width = inkCanvasControl.ActualWidth; - eraserOverlay.Height = inkCanvasControl.ActualHeight; - Trace.WriteLine($"Advanced Eraser: Overlay size set to {eraserOverlay.Width}x{eraserOverlay.Height}"); - } - - Trace.WriteLine("Advanced Eraser: System enabled successfully"); - } else { - Trace.WriteLine("Advanced Eraser: Failed to find eraser overlay"); - } - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error enabling system - {ex.Message}"); - } - } - - /// - /// 初始化橡皮擦覆盖层 - /// - private void InitializeEraserOverlay(Border overlay) { - try { - // 设置覆盖层的基本属性 - overlay.Background = new SolidColorBrush(Colors.Transparent); - overlay.IsHitTestVisible = false; // 默认禁用,只在橡皮擦模式下启用 - - // 绑定事件处理 - overlay.MouseDown += (sender, e) => { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - overlay.CaptureMouse(); - StartAdvancedEraserOperation(sender); - } - }; - - overlay.MouseUp += (sender, e) => { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - overlay.ReleaseMouseCapture(); - EndAdvancedEraserOperation(sender); - } - }; - - overlay.MouseMove += (sender, e) => { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - var position = e.GetPosition((UIElement)FindName("inkCanvas")); - Trace.WriteLine($"Advanced Eraser: Mouse move event triggered at ({position.X:F1}, {position.Y:F1})"); - UpdateAdvancedEraserPosition(sender, position); - } else { - Trace.WriteLine($"Advanced Eraser: Mouse move ignored - not in eraser mode, current mode: {inkCanvas.EditingMode}"); - } - }; - - // 触控笔事件 - overlay.StylusDown += (sender, e) => { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - e.Handled = true; - if (e.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) { - overlay.CaptureStylus(); - } - StartAdvancedEraserOperation(sender); - } - }; - - overlay.StylusUp += (sender, e) => { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - e.Handled = true; - if (e.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) { - overlay.ReleaseStylusCapture(); - } - EndAdvancedEraserOperation(sender); - } - }; - - overlay.StylusMove += (sender, e) => { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - e.Handled = true; - var position = e.GetPosition((UIElement)FindName("inkCanvas")); - UpdateAdvancedEraserPosition(sender, position); - Trace.WriteLine($"Advanced Eraser: Stylus move at ({position.X:F1}, {position.Y:F1})"); - } - }; - - Trace.WriteLine("Advanced Eraser: Overlay initialized successfully"); - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error initializing overlay - {ex.Message}"); - } - } - - /// - /// 禁用高级橡皮擦系统 - /// - public void DisableAdvancedEraserSystem() { - try { - // 重置橡皮擦状态 - ResetEraserState(); - - // 获取橡皮擦覆盖层并禁用 - var eraserOverlay = FindName("AdvancedEraserOverlay") as Border; - if (eraserOverlay != null) { - eraserOverlay.IsHitTestVisible = false; - } - - // 确保视觉反馈被隐藏 - HideEraserFeedback(); - - Trace.WriteLine("Advanced Eraser: System disabled successfully"); - } catch (Exception ex) { - Trace.WriteLine($"Advanced Eraser: Error disabling system - {ex.Message}"); - } - } - - /// - /// 切换橡皮擦形状(圆形/矩形) - /// - public void ToggleEraserShape() { - isCurrentEraserCircle = !isCurrentEraserCircle; - - // 更新设置 - Settings.Canvas.EraserShapeType = isCurrentEraserCircle ? 0 : 1; - - // 应用新形状 - ApplyAdvancedEraserShape(); - - Trace.WriteLine($"Advanced Eraser: Toggled to {(isCurrentEraserCircle ? "Circle" : "Rectangle")}"); - } - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_Icons.cs b/Ink Canvas/MainWindow_cs/MW_Icons.cs deleted file mode 100644 index 71cb076d..00000000 --- a/Ink Canvas/MainWindow_cs/MW_Icons.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Ink_Canvas { - public static class XamlGraphicsIconGeometries { - public static string LinedCursorIcon = - "F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z"; - - public static string LinedPenIcon = - "F1 M24,24z M0,0z M16.996,2.34419L21.6823,7.00397C21.8941,7.23343 22,7.49819 22,7.79825 22,8.09831 21.8941,8.35425 21.6823,8.56606L10.8271,19.4212 4.57877,13.1994 15.4339,2.34419C15.6457,2.11473 15.9017,2 16.2018,2 16.5195,2 16.7842,2.11473 16.996,2.34419z M9.63571,20.5862L9.50333,20.6391 2.6725,21.9894C2.47834,22.0247 2.31066,21.9718 2.16946,21.8306 2.02825,21.707 1.97529,21.5481 2.01059,21.354L3.38736,14.5232C3.38736,14.4879,3.40502,14.4349,3.44032,14.3643L9.63571,20.5862z"; - - public static string SolidPenIcon = - "F1 M24,24z M0,0z M16.996,2.34419L21.6823,7.00397C21.8941,7.23343 22,7.49819 22,7.79825 22,8.09831 21.8941,8.35425 21.6823,8.56606L10.8271,19.4212 4.57877,13.1994 15.4339,2.34419C15.6457,2.11473 15.9017,2 16.2018,2 16.5195,2 16.7842,2.11473 16.996,2.34419z M9.63571,20.5862L9.50333,20.6391 2.6725,21.9894C2.47834,22.0247 2.31066,21.9718 2.16946,21.8306 2.02825,21.707 1.97529,21.5481 2.01059,21.354L3.38736,14.5232C3.38736,14.4879,3.40502,14.4349,3.44032,14.3643L9.63571,20.5862z"; - - public static string LinedEraserStrokeIcon = - "F1 M24,24z M0,0z M19.0625,7.99501C19.2863,7.99501 19.4861,8.06695 19.662,8.21083 19.8538,8.35471 19.9897,8.53856 20.0696,8.76237 20.4054,9.94539 20.7011,11.2563 20.9569,12.6951 21.1967,14.0859 21.3646,15.4368 21.4605,16.7477L21.4845,17.2993C21.5005,17.5551 21.5084,17.9068 21.5084,18.3544 21.5084,19.2017 21.2926,19.8412 20.861,20.2728 20.4453,20.7044 19.7099,21.0482 18.6548,21.3039 18.3191,21.3679 17.7836,20.4327 17.0482,18.4983 16.5845,20.8643 16.1129,22.0313 15.6333,21.9994 13.9227,21.9674 12.468,21.8954 11.269,21.7835L10.8134,21.7356C9.83817,21.6077 8.86297,21.4398 7.88778,21.232 7.1524,21.0721 6.32109,20.8563 5.39386,20.5845 4.61051,20.3447 3.97904,20.089 3.49944,19.8172 3.01984,19.5454 2.70809,19.2337 2.56421,18.882 2.40434,18.4823 2.50827,18.0746 2.87596,17.659L3.09176,17.3952 3.18769,17.2273 3.23565,17.1074 3.37954,16.8197 3.47544,16.5559 3.6433,15.9324 3.73923,15.6207 3.85912,15.1411 4.17088,13.5824 4.57853,11.1844 4.98621,9.05013C5.03417,8.79435 5.16203,8.55455 5.36986,8.33073 5.59367,8.10692 5.8255,7.99501 6.0653,7.99501L19.0625,7.99501z M14.4823,2C14.7861,2 15.0418,2.08793 15.2496,2.26378 15.4575,2.43963 15.5614,2.64746 15.5614,2.88726L15.5614,4.99751 19.0625,4.99751C19.3343,4.99751 19.5661,5.10142 19.7579,5.30925 19.9498,5.50109 20.0457,5.73289 20.0457,6.00467 20.0457,6.27644 19.9498,6.51624 19.7579,6.72407 19.5661,6.91591 19.3343,7.01183 19.0625,7.01183L6.0653,7.01183C5.79352,7.01183 5.55372,6.91591 5.34589,6.72407 5.15405,6.51624 5.05814,6.27644 5.05814,6.00467 5.05814,5.73289 5.15405,5.50109 5.34589,5.30925 5.55372,5.10142 5.79352,4.99751 6.0653,4.99751L9.56638,4.99751 9.56638,2.88726C9.56638,2.66345 9.65432,2.47161 9.83017,2.31174 10.022,2.15187 10.2538,2.05595 10.5256,2.02398L10.6455,2 14.4823,2z"; - - public static string SolidEraserStrokeIcon = - "F1 M24,24z M0,0z M19.0625,7.99501C19.2863,7.99501 19.4861,8.06695 19.662,8.21083 19.8538,8.35471 19.9897,8.53856 20.0696,8.76237 20.4054,9.94539 20.7011,11.2563 20.9569,12.6951 21.1967,14.0859 21.3646,15.4368 21.4605,16.7477L21.4845,17.2993C21.5005,17.5551 21.5084,17.9068 21.5084,18.3544 21.5084,19.2017 21.2926,19.8412 20.861,20.2728 20.4453,20.7044 19.7099,21.0482 18.6548,21.3039 18.3191,21.3679 17.7836,20.4327 17.0482,18.4983 16.5845,20.8643 16.1129,22.0313 15.6333,21.9994 13.9227,21.9674 12.468,21.8954 11.269,21.7835L10.8134,21.7356C9.83817,21.6077 8.86297,21.4398 7.88778,21.232 7.1524,21.0721 6.32109,20.8563 5.39386,20.5845 4.61051,20.3447 3.97904,20.089 3.49944,19.8172 3.01984,19.5454 2.70809,19.2337 2.56421,18.882 2.40434,18.4823 2.50827,18.0746 2.87596,17.659L3.09176,17.3952 3.18769,17.2273 3.23565,17.1074 3.37954,16.8197 3.47544,16.5559 3.6433,15.9324 3.73923,15.6207 3.85912,15.1411 4.17088,13.5824 4.57853,11.1844 4.98621,9.05013C5.03417,8.79435 5.16203,8.55455 5.36986,8.33073 5.59367,8.10692 5.8255,7.99501 6.0653,7.99501L19.0625,7.99501z M14.4823,2C14.7861,2 15.0418,2.08793 15.2496,2.26378 15.4575,2.43963 15.5614,2.64746 15.5614,2.88726L15.5614,4.99751 19.0625,4.99751C19.3343,4.99751 19.5661,5.10142 19.7579,5.30925 19.9498,5.50109 20.0457,5.73289 20.0457,6.00467 20.0457,6.27644 19.9498,6.51624 19.7579,6.72407 19.5661,6.91591 19.3343,7.01183 19.0625,7.01183L6.0653,7.01183C5.79352,7.01183 5.55372,6.91591 5.34589,6.72407 5.15405,6.51624 5.05814,6.27644 5.05814,6.00467 5.05814,5.73289 5.15405,5.50109 5.34589,5.30925 5.55372,5.10142 5.79352,4.99751 6.0653,4.99751L9.56638,4.99751 9.56638,2.88726C9.56638,2.66345 9.65432,2.47161 9.83017,2.31174 10.022,2.15187 10.2538,2.05595 10.5256,2.02398L10.6455,2 14.4823,2z"; - - public static string LinedEraserCircleIcon = - "F1 M24,24z M0,0z M15.0665,2.29557L21.6921,8.92118C21.8892,9.11823 21.9877,9.36453 21.9877,9.6601 21.9877,9.93924 21.8892,10.1773 21.6921,10.3744L10.3621,21.6798C10.165,21.8933 9.92694,22 9.6478,22 9.36865,22 9.12235,21.8933 8.90888,21.6798L2.30789,15.0788C2.11085,14.8654 2.01233,14.619 2.01233,14.3399 2.01233,14.0608 2.11085,13.8227 2.30789,13.6256L13.6133,2.29557C13.8103,2.09852 14.0485,2 14.3276,2 14.6232,2 14.8695,2.09852 15.0665,2.29557z M8.19458,11.5813C8.09606,11.4828 7.97292,11.4335 7.82514,11.4335 7.69377,11.4335 7.57883,11.4828 7.48031,11.5813L5.28818,13.7734C5.18965,13.8719 5.14041,13.9951 5.14041,14.1429 5.14041,14.2906 5.18965,14.4138 5.28818,14.5123L9.47539,18.6995C9.59033,18.8144 9.71347,18.8719 9.84483,18.8719 9.99261,18.8719 10.1158,18.8144 10.2143,18.6995L12.4064,16.5074C12.5049,16.4089 12.5542,16.2939 12.5542,16.1626 12.5542,16.0148 12.5049,15.8916 12.4064,15.7931L8.19458,11.5813z"; - - public static string SolidEraserCircleIcon = - "F1 M24,24z M0,0z M15.0665,2.29557L21.6921,8.92118C21.8892,9.11823 21.9877,9.36453 21.9877,9.6601 21.9877,9.93924 21.8892,10.1773 21.6921,10.3744L10.3621,21.6798C10.165,21.8933 9.92694,22 9.6478,22 9.36865,22 9.12235,21.8933 8.90888,21.6798L2.30789,15.0788C2.11085,14.8654 2.01233,14.619 2.01233,14.3399 2.01233,14.0608 2.11085,13.8227 2.30789,13.6256L13.6133,2.29557C13.8103,2.09852 14.0485,2 14.3276,2 14.6232,2 14.8695,2.09852 15.0665,2.29557z M8.19458,11.5813C8.09606,11.4828 7.97292,11.4335 7.82514,11.4335 7.69377,11.4335 7.57883,11.4828 7.48031,11.5813L5.28818,13.7734C5.18965,13.8719 5.14041,13.9951 5.14041,14.1429 5.14041,14.2906 5.18965,14.4138 5.28818,14.5123L9.47539,18.6995C9.59033,18.8144 9.71347,18.8719 9.84483,18.8719 9.99261,18.8719 10.1158,18.8144 10.2143,18.6995L12.4064,16.5074C12.5049,16.4089 12.5542,16.2939 12.5542,16.1626 12.5542,16.0148 12.5049,15.8916 12.4064,15.7931L8.19458,11.5813z"; - - public static string LinedLassoSelectIcon = - "F0 M24,24z M0,0z M21.1749,3.19033C21.2959,3.31432,21.3512,3.45083,21.3512,3.62667L21.3512,15.7344 18.9141,14.0608 18.9141,5.43716 5.30084,5.43716 5.30084,19.0505 14.7641,19.0505 15.0947,21.4876 3.49029,21.4876C3.31451,21.4876 3.178,21.4323 3.05397,21.3113 2.92046,21.1636 2.86368,21.0107 2.86368,20.8334L2.86368,3.62667C2.86368,3.44751 2.92108,3.30918 3.04695,3.18331 3.17285,3.0574 3.31118,3 3.49029,3L20.697,3C20.8743,3,21.0272,3.0568,21.1749,3.19033z M15.042,13.7475L16.02,20.0637C16.0562,20.2901,16.1015,20.6026,16.1559,21.001L16.3052,21.9247C16.3234,22.0424 16.3686,22.1239 16.4411,22.1692 16.5226,22.2144 16.6086,22.2235 16.6992,22.1963 16.7897,22.1601 16.8667,22.0877 16.9301,21.979L18.2205,19.7242 20.421,22.8755C20.4663,22.9389 20.5251,22.9796 20.5976,22.9978 20.6791,23.0068 20.7515,22.9887 20.8149,22.9434L21.7522,22.2914C21.8156,22.2461 21.8518,22.1873 21.8609,22.1148 21.879,22.0333 21.8654,21.9609 21.8201,21.8975L19.7555,18.9499 22.7981,18.9499C22.9249,18.9499 23.0154,18.9137 23.0698,18.8412 23.1332,18.7597 23.1512,18.6692 23.1241,18.5696 23.0969,18.47 23.0336,18.3839 22.934,18.3115 22.0556,17.7048 20.8693,16.8898 19.3751,15.8665 17.7542,14.7708 16.509,13.9377 15.6396,13.3672 15.531,13.2947 15.4224,13.2585 15.3137,13.2585 15.205,13.2585 15.1235,13.2992 15.0692,13.3807 15.0149,13.4622 15.0058,13.5845 15.042,13.7475z"; - - public static string SolidLassoSelectIcon = - "F0 M24,24z M0,0z M21.1749,3.19033C21.2959,3.31432,21.3512,3.45083,21.3512,3.62667L21.3512,15.7344 18.9141,14.0608 18.9141,5.43716 5.30084,5.43716 5.30084,19.0505 14.7641,19.0505 15.0947,21.4876 3.49029,21.4876C3.31451,21.4876 3.178,21.4323 3.05397,21.3113 2.92046,21.1636 2.86368,21.0107 2.86368,20.8334L2.86368,3.62667C2.86368,3.44751 2.92108,3.30918 3.04695,3.18331 3.17285,3.0574 3.31118,3 3.49029,3L20.697,3C20.8743,3,21.0272,3.0568,21.1749,3.19033z M15.042,13.7475L16.02,20.0637C16.0562,20.2901,16.1015,20.6026,16.1559,21.001L16.3052,21.9247C16.3234,22.0424 16.3686,22.1239 16.4411,22.1692 16.5226,22.2144 16.6086,22.2235 16.6992,22.1963 16.7897,22.1601 16.8667,22.0877 16.9301,21.979L18.2205,19.7242 20.421,22.8755C20.4663,22.9389 20.5251,22.9796 20.5976,22.9978 20.6791,23.0068 20.7515,22.9887 20.8149,22.9434L21.7522,22.2914C21.8156,22.2461 21.8518,22.1873 21.8609,22.1148 21.879,22.0333 21.8654,21.9609 21.8201,21.8975L19.7555,18.9499 22.7981,18.9499C22.9249,18.9499 23.0154,18.9137 23.0698,18.8412 23.1332,18.7597 23.1512,18.6692 23.1241,18.5696 23.0969,18.47 23.0336,18.3839 22.934,18.3115 22.0556,17.7048 20.8693,16.8898 19.3751,15.8665 17.7542,14.7708 16.509,13.9377 15.6396,13.3672 15.531,13.2947 15.4224,13.2585 15.3137,13.2585 15.205,13.2585 15.1235,13.2992 15.0692,13.3807 15.0149,13.4622 15.0058,13.5845 15.042,13.7475z"; - - public static string DisabledGestureIcon = - "F0 M24,24z M0,0z M7.82154,10.0753L7.82154,3.74613C7.82154,3.06603 8.08946,2.40655 8.57377,1.92224 9.05808,1.43793 9.70726,1.17001 10.3977,1.17001 11.0881,1.17001 11.7372,1.43793 12.2216,1.92224 12.7059,2.40655 12.9738,3.05573 12.9738,3.74613L12.9738,6.37308C13.1415,6.33947 13.3139,6.32225 13.489,6.32225 14.1794,6.32225 14.8286,6.59016 15.3129,7.07447 15.4484,7.21001 15.567,7.35845 15.6675,7.5171 15.9551,7.40916 16.2634,7.35269 16.5803,7.35269 17.2707,7.35269 17.9199,7.62061 18.4042,8.10492 18.5461,8.24683 18.6695,8.4029 18.7729,8.57001 19.6856,8.26338 20.7674,8.45871 21.4647,9.15599 21.949,9.6403 22.2169,10.2998 22.2169,10.9799L22.2169,15.6169C22.2169,17.5438 21.4647,19.3574 20.1045,20.7176 18.7443,22.0778 16.9307,22.83 15.0038,22.83L13.149,22.83 13.1799,22.8094 12.8398,22.8094C11.7682,22.7579 10.7068,22.4694 9.75878,21.9541 8.70773,21.3874 7.81124,20.563 7.15175,19.5738L6.94566,19.2647C6.60562,18.7494 5.49273,16.8019 3.52458,13.3087 3.19484,12.7213 3.1021,12.0412 3.27727,11.3818 3.45245,10.7326 3.86463,10.1761 4.44168,9.83608 5.00842,9.49604 5.66791,9.35177 6.31709,9.43421 6.86548,9.50385 7.39181,9.7279 7.82154,10.0753z M10.037,3.38547C10.1297,3.28243 10.2637,3.23091 10.3977,3.23091 10.5316,3.23091 10.6656,3.29273 10.7583,3.38547 10.8614,3.47821 10.9129,3.61217 10.9129,3.74613L10.9129,11.4745C10.9129,12.0412 11.3766,12.5049 11.9433,12.5049 12.5101,12.5049 12.9738,12.0412 12.9738,11.4745L12.9738,8.89836C12.9738,8.7644 13.0356,8.63045 13.1283,8.53771 13.2211,8.43466 13.355,8.38314 13.489,8.38314 13.623,8.38314 13.7569,8.44497 13.8497,8.53771 13.9527,8.63045 14.0042,8.7644 14.0042,8.89836L14.0042,11.4745C14.0042,12.0412 14.4679,12.5049 15.0347,12.5049 15.6014,12.5049 16.0651,12.0412 16.0651,11.4745L16.0651,9.92881C16.0651,9.79485 16.1269,9.66089 16.2197,9.56815 16.3124,9.46511 16.4464,9.41359 16.5803,9.41359 16.7143,9.41359 16.8483,9.47541 16.941,9.56815 17.044,9.66089 17.0956,9.79485 17.0956,9.92881L17.0956,10.5869C17.0752,10.7163 17.0646,10.8477 17.0646,10.9799 17.0646,11.0661 17.0754,11.1499 17.0956,11.2301L17.0956,11.4745C17.0956,12.0412 17.5593,12.5049 18.126,12.5049 18.6928,12.5049 19.1565,12.0412 19.1565,11.4745L19.1565,10.8128C19.1834,10.7399 19.2266,10.6727 19.2801,10.6192 19.4759,10.4234 19.8159,10.4234 20.0117,10.6192 20.1148,10.712 20.1663,10.8459 20.1663,10.9799L20.1663,15.6169C20.1663,16.9977 19.6408,18.296 18.6618,19.2647 17.6829,20.2333 16.3949,20.7691 15.0141,20.7691L13.1593,20.7691C12.3143,20.7691 11.4796,20.5527 10.7274,20.1509 9.98548,19.749 9.3363,19.1616 8.8726,18.4506L8.66651,18.1415C8.35737,17.6675 7.23419,15.7096 5.31756,12.2988 5.24543,12.1752 5.23512,12.0412 5.26604,11.9073 5.30725,11.7733 5.38969,11.6703 5.50304,11.5981 5.66791,11.4951 5.874,11.4539 6.06978,11.4745 6.26557,11.5054 6.45105,11.5878 6.59531,11.7321L8.11007,13.2469C8.49419,13.631 9.10425,13.648 9.50833,13.2978 9.73651,13.1084 9.88244,12.8229 9.88244,12.5049L9.88244,3.74613C9.88244,3.61217,9.94426,3.47821,10.037,3.38547z M2.99905,6.31195L1.78313,4.65293 2.61779,4.04497C3.46275,3.4267,4.37985,2.89087,5.33817,2.46838L6.27587,2.0459 7.12084,3.93162 6.18313,4.3541C5.35878,4.72506,4.56533,5.17846,3.83372,5.71429L2.99905,6.32225 2.99905,6.31195z M18.2806,5.20935L19.1565,5.75549 20.259,4.01404 19.3831,3.4679C18.1157,2.67446,16.7452,2.0768,15.3026,1.68523L14.303,1.41731 13.7672,3.40607 14.7667,3.67399C16.0033,4.00373,17.1883,4.51895,18.2806,5.20935z"; - - public static string EnabledGestureIcon = - "F1 M24,24z M0,0z M7.29844,9.85586L7.29844,3.52668C7.29844,2.84658 7.56636,2.1871 8.05067,1.70279 8.53498,1.21848 9.18416,0.950562 9.87456,0.950562 10.565,0.950562 11.2141,1.21848 11.6984,1.70279 12.1828,2.1871 12.4507,2.83628 12.4507,3.52668L12.4507,6.15363C12.6184,6.12002 12.7908,6.10279 12.9659,6.10279 13.6563,6.10279 14.3055,6.37071 14.7898,6.85502 14.9253,6.99055 15.0439,7.139 15.1444,7.29765 15.432,7.18971 15.7403,7.13324 16.0572,7.13324 16.7476,7.13324 17.3968,7.40116 17.8811,7.88547 18.023,8.02738 18.1464,8.18344 18.2498,8.35055 19.1625,8.04393 20.2443,8.23925 20.9416,8.93654 21.4259,9.42085 21.6938,10.0803 21.6938,10.7604L21.6938,14.2958C21.1174,13.741,20.4192,13.3118,19.6432,13.0524L19.6432,10.7604C19.6432,10.6265 19.5917,10.4925 19.4886,10.3998 19.2928,10.204 18.9528,10.204 18.757,10.3998 18.7035,10.4532 18.6603,10.5204 18.6334,10.5934L18.6334,11.255C18.6334,11.8218 18.1697,12.2855 17.6029,12.2855 17.0362,12.2855 16.5725,11.8218 16.5725,11.255L16.5725,11.0106C16.5523,10.9304 16.5415,10.8466 16.5415,10.7604 16.5415,10.6283 16.5521,10.4969 16.5725,10.3674L16.5725,9.70936C16.5725,9.5754 16.5209,9.44144 16.4179,9.3487 16.3252,9.25596 16.1912,9.19413 16.0572,9.19413 15.9233,9.19413 15.7893,9.24566 15.6966,9.3487 15.6038,9.44144 15.542,9.5754 15.542,9.70936L15.542,11.255C15.542,11.8218 15.0783,12.2855 14.5116,12.2855 13.9448,12.2855 13.4811,11.8218 13.4811,11.255L13.4811,8.67891C13.4811,8.54495 13.4296,8.41099 13.3266,8.31825 13.2338,8.22551 13.0999,8.16369 12.9659,8.16369 12.8319,8.16369 12.698,8.21521 12.6052,8.31825 12.5125,8.41099 12.4507,8.54495 12.4507,8.67891L12.4507,11.255C12.4507,11.8218 11.987,12.2855 11.4202,12.2855 10.8535,12.2855 10.3898,11.8218 10.3898,11.255L10.3898,3.52668C10.3898,3.39272 10.3383,3.25876 10.2352,3.16602 10.1425,3.07328 10.0085,3.01145 9.87456,3.01145 9.7406,3.01145 9.60664,3.06298 9.5139,3.16602 9.42116,3.25876 9.35933,3.39272 9.35933,3.52668L9.35933,12.2855C9.35933,12.6034 9.21341,12.8889 8.98523,13.0783 8.58114,13.4285 7.97109,13.4115 7.58697,13.0274L6.07221,11.5127C5.92795,11.3684 5.74247,11.286 5.54668,11.255 5.3509,11.2344 5.14481,11.2756 4.97994,11.3787 4.86659,11.4508 4.78415,11.5539 4.74293,11.6878 4.71202,11.8218 4.72232,11.9557 4.79446,12.0794 6.71109,15.4902 7.83427,17.448 8.14341,17.922L8.3495,18.2312C8.8132,18.9422 9.46238,19.5295 10.2043,19.9314 10.9565,20.3333 11.7912,20.5497 12.6362,20.5497L12.9829,20.5497C13.3696,21.3681 13.9542,22.0748 14.6748,22.608 14.6102,22.6097 14.5455,22.6106 14.4807,22.6106L12.6258,22.6106 12.6568,22.59 12.3167,22.59C11.2451,22.5384 10.1837,22.2499 9.23568,21.7347 8.18463,21.1679 7.28814,20.3436 6.62865,19.3544L6.42256,19.0452C6.08251,18.53 4.96963,16.5824 3.00148,13.0892 2.67174,12.5019 2.579,11.8218 2.75417,11.1623 2.92935,10.5131 3.34153,9.95668 3.91858,9.61663 4.48532,9.27658 5.14481,9.13232 5.79399,9.21476 6.34238,9.28439 6.86871,9.50845 7.29844,9.85586z M2.47595,6.0925L1.26003,4.43348 2.09469,3.82551C2.93965,3.20725,3.85675,2.67141,4.81507,2.24893L5.75277,1.82645 6.59774,3.71216 5.66003,4.13465C4.83567,4.50561,4.04223,4.959,3.31061,5.49484L2.47595,6.1028 2.47595,6.0925z M17.7575,4.9899L18.6334,5.53604 19.7359,3.79458 18.86,3.24845C17.5926,2.455,16.2221,1.85734,14.7795,1.46577L13.7799,1.19786 13.2441,3.18662 14.2436,3.45454C15.4802,3.78428,16.6652,4.2995,17.7575,4.9899z"; - - public static string EnabledGestureIconBadgeCheck = - "M22.74,18.2234C22.74,20.8888 20.5793,23.0494 17.914,23.0494 15.2487,23.0494 13.088,20.8888 13.088,18.2234 13.088,15.5581 15.2487,13.3975 17.914,13.3975 20.5793,13.3975 22.74,15.5581 22.74,18.2234z M21.1673,15.8009C21.4651,16.0889,21.473,16.5637,21.1851,16.8614L17.5425,20.6282C17.4012,20.7743 17.2066,20.8568 17.0034,20.8568 16.8001,20.8568 16.6055,20.7743 16.4642,20.6282L14.6429,18.7448C14.355,18.447 14.3629,17.9722 14.6607,17.6843 14.9585,17.3963 15.4333,17.4043 15.7212,17.7021L17.0034,19.0279 20.1068,15.8187C20.3947,15.5209,20.8695,15.513,21.1673,15.8009z"; - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_Notification.cs b/Ink Canvas/MainWindow_cs/MW_Notification.cs deleted file mode 100644 index aa375b82..00000000 --- a/Ink Canvas/MainWindow_cs/MW_Notification.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Windows; -using Ink_Canvas.Helpers; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - private int lastNotificationShowTime; - private int notificationShowTime = 2500; - - public static void ShowNewMessage(string notice, bool isShowImmediately = true) { - (Application.Current?.Windows.Cast().FirstOrDefault(window => window is MainWindow) as MainWindow) - ?.ShowNotification(notice, isShowImmediately); - } - - public void ShowNotification(string notice, bool isShowImmediately = true) { - try { - lastNotificationShowTime = Environment.TickCount; - - TextBlockNotice.Text = notice; - AnimationsHelper.ShowWithSlideFromBottomAndFade(GridNotifications); - - new Thread(() => { - Thread.Sleep(notificationShowTime + 300); - if (Environment.TickCount - lastNotificationShowTime >= notificationShowTime) - Application.Current.Dispatcher.Invoke(() => { - AnimationsHelper.HideWithSlideAndFade(GridNotifications); - }); - }).Start(); - } - catch { } - } - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs deleted file mode 100644 index 85e658aa..00000000 --- a/Ink Canvas/MainWindow_cs/MW_PPT.cs +++ /dev/null @@ -1,1067 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Threading; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern; -using Microsoft.Office.Core; -using Microsoft.Office.Interop.PowerPoint; -using Application = System.Windows.Application; -using File = System.IO.File; -using MessageBox = System.Windows.MessageBox; -using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs; -using MouseEventArgs = System.Windows.Input.MouseEventArgs; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - #region Win32 API Declarations - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - [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")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool IsWindowVisible(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool IsIconic(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool IsZoomed(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern IntPtr GetForegroundWindow(); - - [DllImport("user32.dll")] - private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); - - [DllImport("user32.dll")] - private static extern bool IsWindow(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern bool GetWindowRect(IntPtr hWnd, out ForegroundWindowInfo.RECT lpRect); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); - - private const int GWL_STYLE = -16; - private const int WS_VISIBLE = 0x10000000; - private const int WS_MINIMIZE = 0x20000000; - private const uint GW_HWNDNEXT = 2; - private const uint GW_HWNDPREV = 3; - - private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); - #endregion - - #region PPT Application Variables - public static Microsoft.Office.Interop.PowerPoint.Application pptApplication; - public static Presentation presentation; - public static Slides slides; - public static Slide slide; - public static int slidescount; - #endregion - - #region PPT State Management - private bool wasFloatingBarFoldedWhenEnterSlideShow; - private static bool hasShownWpsForceCloseWarning = false; - private bool isEnteredSlideShowEndEvent; //防止重复调用本函数导致墨迹保存失效 - private bool isPresentationHaveBlackSpace; - private string pptName; - private bool _isPptClickingBtnTurned; - #endregion - - #region PPT Managers - private PPTManager _pptManager; - private PPTInkManager _pptInkManager; - private PPTUIManager _pptUIManager; - #endregion - - #region PPT Manager Initialization - private void InitializePPTManagers() - { - try - { - // 初始化PPT管理器 - _pptManager = new PPTManager(); - _pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS; - - // 注册事件 - _pptManager.PPTConnectionChanged += OnPPTConnectionChanged; - _pptManager.SlideShowBegin += OnPPTSlideShowBegin; - _pptManager.SlideShowNextSlide += OnPPTSlideShowNextSlide; - _pptManager.SlideShowEnd += OnPPTSlideShowEnd; - _pptManager.PresentationOpen += OnPPTPresentationOpen; - _pptManager.PresentationClose += OnPPTPresentationClose; - - // 初始化墨迹管理器 - _pptInkManager = new PPTInkManager(); - _pptInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint; - _pptInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation; - - // 初始化UI管理器 - _pptUIManager = new PPTUIManager(this); - _pptUIManager.ShowPPTButton = Settings.PowerPointSettings.ShowPPTButton; - _pptUIManager.PPTButtonsDisplayOption = Settings.PowerPointSettings.PPTButtonsDisplayOption; - _pptUIManager.PPTSButtonsOption = Settings.PowerPointSettings.PPTSButtonsOption; - _pptUIManager.PPTBButtonsOption = Settings.PowerPointSettings.PPTBButtonsOption; - _pptUIManager.PPTLSButtonPosition = Settings.PowerPointSettings.PPTLSButtonPosition; - _pptUIManager.PPTRSButtonPosition = Settings.PowerPointSettings.PPTRSButtonPosition; - _pptUIManager.EnablePPTButtonPageClickable = Settings.PowerPointSettings.EnablePPTButtonPageClickable; - - LogHelper.WriteLogToFile("PPT管理器初始化完成", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"PPT管理器初始化失败: {ex}", LogHelper.LogType.Error); - } - } - - private void StartPPTMonitoring() - { - if (Settings.PowerPointSettings.PowerPointSupport) - { - _pptManager?.StartMonitoring(); - LogHelper.WriteLogToFile("PPT监控已启动", LogHelper.LogType.Event); - } - } - - private void StopPPTMonitoring() - { - _pptManager?.StopMonitoring(); - LogHelper.WriteLogToFile("PPT监控已停止", LogHelper.LogType.Event); - } - - private void DisposePPTManagers() - { - try - { - _pptManager?.Dispose(); - _pptInkManager?.Dispose(); - _pptManager = null; - _pptInkManager = null; - _pptUIManager = null; - LogHelper.WriteLogToFile("PPT管理器已释放", LogHelper.LogType.Event); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"释放PPT管理器失败: {ex}", LogHelper.LogType.Error); - } - } - #endregion - - #region New PPT Event Handlers - private void OnPPTConnectionChanged(bool isConnected) - { - try - { - Application.Current.Dispatcher.InvokeAsync(() => - { - _pptUIManager?.UpdateConnectionStatus(isConnected); - - if (isConnected) - { - LogHelper.WriteLogToFile("PPT连接已建立", LogHelper.LogType.Event); - } - else - { - LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event); - // 清理墨迹管理器 - _pptInkManager?.ClearAllStrokes(); - } - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理PPT连接状态变化失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnPPTPresentationOpen(Presentation pres) - { - try - { - Application.Current.Dispatcher.InvokeAsync(() => - { - // 初始化墨迹管理器 - _pptInkManager?.InitializePresentation(pres); - - // 处理跳转到首页或上次播放页的逻辑 - HandlePresentationOpenNavigation(pres); - - // 检查隐藏幻灯片 - if (Settings.PowerPointSettings.IsNotifyHiddenPage) - { - CheckAndNotifyHiddenSlides(pres); - } - - // 检查自动播放设置 - if (Settings.PowerPointSettings.IsNotifyAutoPlayPresentation) - { - CheckAndNotifyAutoPlaySettings(pres); - } - - _pptUIManager?.UpdateConnectionStatus(true); - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理演示文稿打开事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnPPTPresentationClose(Presentation pres) - { - try - { - Application.Current.Dispatcher.InvokeAsync(() => - { - // 保存所有墨迹 - _pptInkManager?.SaveAllStrokesToFile(pres); - - // 清理墨迹管理器 - _pptInkManager?.ClearAllStrokes(); - - _pptUIManager?.UpdateConnectionStatus(false); - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private async void OnPPTSlideShowBegin(SlideShowWindow wn) - { - try - { - // 记录进入放映时浮动栏收纳状态 - wasFloatingBarFoldedWhenEnterSlideShow = isFloatingBarFolded; - - if (Settings.Automation.IsAutoFoldInPPTSlideShow && !isFloatingBarFolded) - FoldFloatingBar_MouseUp(new object(), null); - else if (isFloatingBarFolded) - await UnFoldFloatingBar(new object()); - - isStopInkReplay = true; - - await Application.Current.Dispatcher.InvokeAsync(() => - { - // 处理跳转到首页 - if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter) - { - _pptManager?.TryNavigateToSlide(1); - } - - // 更新UI状态 - var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; - var totalSlides = _pptManager?.SlidesCount ?? 0; - _pptUIManager?.UpdateSlideShowStatus(true, currentSlide, totalSlides); - - // 设置浮动栏透明度和边距 - _pptUIManager?.SetFloatingBarOpacity(Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue); - _pptUIManager?.SetMainPanelMargin(new Thickness(10, 10, 10, 10)); - - // 显示侧边栏退出按钮 - _pptUIManager?.UpdateSidebarExitButtons(true); - - // 处理画板显示 - if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow && - !Settings.Automation.IsAutoFoldInPPTSlideShow && - GridTransparencyFakeBackground.Background == Brushes.Transparent && !isFloatingBarFolded) - { - BtnHideInkCanvas_Click(BtnHideInkCanvas, null); - } - - if (currentMode != 0) - { - ImageBlackboard_MouseUp(null, null); - BtnHideInkCanvas_Click(BtnHideInkCanvas, null); - } - - BorderFloatingBarMainControls.Visibility = Visibility.Visible; - - if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow && - !Settings.Automation.IsAutoFoldInPPTSlideShow) - BtnColorRed_Click(null, null); - - isEnteredSlideShowEndEvent = false; - - // 加载当前页墨迹 - LoadCurrentSlideInk(currentSlide); - }); - - if (!isFloatingBarFolded) - { - new Thread(() => - { - Thread.Sleep(100); - Application.Current.Dispatcher.Invoke(() => - { - ViewboxFloatingBarMarginAnimation(60); - }); - }).Start(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片放映开始事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private void OnPPTSlideShowNextSlide(SlideShowWindow wn) - { - try - { - Application.Current.Dispatcher.InvokeAsync(() => - { - var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; - var totalSlides = _pptManager?.SlidesCount ?? 0; - - // 保存上一页墨迹并加载当前页墨迹 - SwitchSlideInk(currentSlide); - - // 更新UI - _pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides); - - LogHelper.WriteLogToFile($"幻灯片切换到第{currentSlide}页", LogHelper.LogType.Trace); - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片切换事件失败: {ex}", LogHelper.LogType.Error); - } - } - - private async void OnPPTSlideShowEnd(Presentation pres) - { - try - { - // 处理浮动栏状态 - if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow) - { - if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null); - } - else - { - if (isFloatingBarFolded) await UnFoldFloatingBar(new object()); - } - - if (isEnteredSlideShowEndEvent) return; - isEnteredSlideShowEndEvent = true; - - // 保存所有墨迹 - _pptInkManager?.SaveAllStrokesToFile(pres); - - await Application.Current.Dispatcher.InvokeAsync(() => - { - try - { - isPresentationHaveBlackSpace = false; - - // 恢复主题 - if (BtnSwitchTheme.Content.ToString() == "深色") - { - BtnExit.Foreground = Brushes.Black; - ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light; - } - - // 更新UI状态 - _pptUIManager?.UpdateSlideShowStatus(false); - _pptUIManager?.UpdateSidebarExitButtons(false); - _pptUIManager?.SetMainPanelMargin(new Thickness(10, 10, 10, 55)); - _pptUIManager?.SetFloatingBarOpacity(Settings.Appearance.ViewboxFloatingBarOpacityValue); - - if (currentMode != 0) - { - CloseWhiteboardImmediately(); - currentMode = 0; - } - - ClearStrokes(true); - // 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹 - // 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上) - TimeMachineHistories[0] = null; - - if (GridTransparencyFakeBackground.Background != Brushes.Transparent) - BtnHideInkCanvas_Click(BtnHideInkCanvas, null); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片放映结束UI更新失败: {ex}", LogHelper.LogType.Error); - } - }); - - await Task.Delay(150); - await Application.Current.Dispatcher.InvokeAsync(() => - { - ViewboxFloatingBarMarginAnimation(100, true); - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理幻灯片放映结束事件失败: {ex}", LogHelper.LogType.Error); - } - } - #endregion - - #region Helper Methods - private void HandlePresentationOpenNavigation(Presentation pres) - { - try - { - if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter) - { - _pptManager?.TryNavigateToSlide(1); - } - else if (Settings.PowerPointSettings.IsNotifyPreviousPage) - { - ShowPreviousPageNotification(pres); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"处理演示文稿导航失败: {ex}", LogHelper.LogType.Error); - } - } - - private void ShowPreviousPageNotification(Presentation pres) - { - try - { - if (pres == null) return; - - var presentationPath = pres.FullName; - var fileHash = GetFileHash(presentationPath); - var folderName = pres.Name + "_" + pres.Slides.Count + "_" + fileHash; - var folderPath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "Auto Saved - Presentations", folderName); - var positionFile = Path.Combine(folderPath, "Position"); - - if (!File.Exists(positionFile)) return; - - if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0) - { - new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => - { - _pptManager?.TryNavigateToSlide(page); - }).ShowDialog(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"显示上次播放页通知失败: {ex}", LogHelper.LogType.Error); - } - } - - private void CheckAndNotifyHiddenSlides(Presentation pres) - { - try - { - bool hasHiddenSlides = false; - if (pres?.Slides != null) - { - foreach (Slide slide in pres.Slides) - { - if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue) - { - hasHiddenSlides = true; - break; - } - } - } - - if (hasHiddenSlides && !IsShowingRestoreHiddenSlidesWindow) - { - IsShowingRestoreHiddenSlidesWindow = true; - new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?", - () => - { - try - { - if (pres?.Slides != null) - { - foreach (Slide slide in pres.Slides) - { - if (slide.SlideShowTransition.Hidden == MsoTriState.msoTrue) - slide.SlideShowTransition.Hidden = MsoTriState.msoFalse; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"取消隐藏幻灯片失败: {ex}", LogHelper.LogType.Error); - } - finally - { - IsShowingRestoreHiddenSlidesWindow = false; - } - }, - () => { IsShowingRestoreHiddenSlidesWindow = false; }, - () => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查隐藏幻灯片失败: {ex}", LogHelper.LogType.Error); - } - } - - private void CheckAndNotifyAutoPlaySettings(Presentation pres) - { - try - { - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) return; - - bool hasSlideTimings = false; - if (pres?.Slides != null) - { - foreach (Slide slide in pres.Slides) - { - if (slide.SlideShowTransition.AdvanceOnTime == MsoTriState.msoTrue && - slide.SlideShowTransition.AdvanceTime > 0) - { - hasSlideTimings = true; - break; - } - } - } - - if (hasSlideTimings && !IsShowingAutoplaySlidesWindow) - { - IsShowingAutoplaySlidesWindow = true; - new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?", - () => - { - try - { - if (pres != null) - { - pres.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowManualAdvance; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"设置手动播放模式失败: {ex}", LogHelper.LogType.Error); - } - finally - { - IsShowingAutoplaySlidesWindow = false; - } - }, - () => { IsShowingAutoplaySlidesWindow = false; }, - () => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"检查自动播放设置失败: {ex}", LogHelper.LogType.Error); - } - } - - private void LoadCurrentSlideInk(int slideIndex) - { - try - { - var strokes = _pptInkManager?.LoadSlideStrokes(slideIndex); - if (strokes != null) - { - inkCanvas.Strokes.Clear(); - inkCanvas.Strokes.Add(strokes); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载当前页墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - - private void SwitchSlideInk(int newSlideIndex) - { - try - { - var newStrokes = _pptInkManager?.SwitchToSlide(newSlideIndex, inkCanvas.Strokes); - if (newStrokes != null) - { - inkCanvas.Strokes.Clear(); - inkCanvas.Strokes.Add(newStrokes); - } - - // 设置墨迹锁定 - _pptInkManager?.LockInkForSlide(newSlideIndex); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error); - } - } - - private string GetFileHash(string filePath) - { - try - { - if (string.IsNullOrEmpty(filePath)) return "unknown"; - - using (var md5 = MD5.Create()) - { - byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath)); - return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error); - return "error"; - } - } - #endregion - - private void BtnCheckPPT_Click(object sender, RoutedEventArgs e) { - try { - // 使用新的PPT管理器进行连接检查 - if (_pptManager == null) - { - InitializePPTManagers(); - } - - // 手动触发一次连接检查 - _pptManager?.StartMonitoring(); - - // 等待一小段时间让连接建立 - Task.Delay(500).ContinueWith(_ => - { - Application.Current.Dispatcher.Invoke(() => - { - if (_pptManager?.IsConnected == true) - { - LogHelper.WriteLogToFile("手动PPT连接检查成功", LogHelper.LogType.Event); - } - else - { - MessageBox.Show("未找到幻灯片"); - LogHelper.WriteLogToFile("手动PPT连接检查失败", LogHelper.LogType.Warning); - } - }); - }); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"手动检查PPT应用程序失败: {ex}", LogHelper.LogType.Error); - _pptUIManager?.UpdateConnectionStatus(false); - MessageBox.Show("未找到幻灯片"); - } - } - - private void ToggleSwitchSupportWPS_Toggled(object sender, RoutedEventArgs e) { - if (!isLoaded) return; - - Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn; - - // 更新PPT管理器的WPS支持设置 - if (_pptManager != null) - { - _pptManager.IsSupportWPS = Settings.PowerPointSettings.IsSupportWPS; - } - - SaveSettingsToFile(); - } - - private static bool isWPSSupportOn => Settings.PowerPointSettings.IsSupportWPS; - - public static bool IsShowingRestoreHiddenSlidesWindow; - private static bool IsShowingAutoplaySlidesWindow; - - - - - - - - - - - - - - - - - - - - private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e) { - Application.Current.Dispatcher.Invoke(() => { - try - { - _isPptClickingBtnTurned = true; - - // 保存当前页墨迹 - var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; - if (currentSlide > 0) - { - _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); - } - - // 保存截图(如果启用) - if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber && - Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint) - { - var presentationName = _pptManager?.GetPresentationName() ?? ""; - SaveScreenShot(true, $"{presentationName}/{currentSlide}"); - } - - // 执行翻页 - if (_pptManager?.TryNavigatePrevious() == true) - { - // 翻页成功,等待事件处理墨迹切换 - LogHelper.WriteLogToFile("成功切换到上一页", LogHelper.LogType.Trace); - } - else - { - LogHelper.WriteLogToFile("切换到上一页失败", LogHelper.LogType.Warning); - _pptUIManager?.UpdateConnectionStatus(false); - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"PPT上一页操作异常: {ex}", LogHelper.LogType.Error); - _pptUIManager?.UpdateConnectionStatus(false); - } - }); - } - - private void BtnPPTSlidesDown_Click(object sender, RoutedEventArgs e) { - Application.Current.Dispatcher.Invoke(() => { - try - { - _isPptClickingBtnTurned = true; - - // 保存当前页墨迹 - var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; - if (currentSlide > 0) - { - _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); - } - - // 保存截图(如果启用) - if (inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber && - Settings.PowerPointSettings.IsAutoSaveScreenShotInPowerPoint) - { - var presentationName = _pptManager?.GetPresentationName() ?? ""; - SaveScreenShot(true, $"{presentationName}/{currentSlide}"); - } - - // 执行翻页 - if (_pptManager?.TryNavigateNext() == true) - { - // 翻页成功,等待事件处理墨迹切换 - LogHelper.WriteLogToFile("成功切换到下一页", LogHelper.LogType.Trace); - } - else - { - LogHelper.WriteLogToFile("切换到下一页失败", LogHelper.LogType.Warning); - _pptUIManager?.UpdateConnectionStatus(false); - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"PPT下一页操作异常: {ex}", LogHelper.LogType.Error); - _pptUIManager?.UpdateConnectionStatus(false); - } - }); - } - - private async void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e) - { - lastBorderMouseDownObject = sender; - if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return; - if (sender == PPTLSPageButton) - { - PPTLSPageButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRSPageButton) - { - PPTRSPageButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTLBPageButton) - { - PPTLBPageButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRBPageButton) - { - PPTRBPageButtonFeedbackBorder.Opacity = 0.15; - } - } - - private async void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e) - { - lastBorderMouseDownObject = null; - if (sender == PPTLSPageButton) - { - PPTLSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSPageButton) - { - PPTRSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBPageButton) - { - PPTLBPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPageButton) - { - PPTRBPageButtonFeedbackBorder.Opacity = 0; - } - } - - private async void PPTNavigationBtn_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - if (sender == PPTLSPageButton) - { - PPTLSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRSPageButton) - { - PPTRSPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTLBPageButton) - { - PPTLBPageButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPageButton) - { - PPTRBPageButtonFeedbackBorder.Opacity = 0; - } - - if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return; - - // 使用新的PPT管理器检查连接状态 - if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true) - { - LogHelper.WriteLogToFile("PPT未连接或未在放映状态,无法执行页码点击操作", LogHelper.LogType.Warning); - return; - } - - try - { - GridTransparencyFakeBackground.Opacity = 1; - GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF")); - CursorIcon_Click(null, null); - - // 使用新的PPT管理器显示导航 - if (_pptManager.TryShowSlideNavigation()) - { - LogHelper.WriteLogToFile("成功显示PPT幻灯片导航", LogHelper.LogType.Trace); - } - else - { - LogHelper.WriteLogToFile("显示PPT幻灯片导航失败", LogHelper.LogType.Warning); - } - - // 控制居中 - if (!isFloatingBarFolded) { - await Task.Delay(100); - ViewboxFloatingBarMarginAnimation(60); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"PPT翻页控件操作失败: {ex}", LogHelper.LogType.Error); - } - } - - private void BtnPPTSlideShow_Click(object sender, RoutedEventArgs e) { - new Thread(() => { - try { - if (_pptManager?.TryStartSlideShow() != true) - { - LogHelper.WriteLogToFile("启动幻灯片放映失败", LogHelper.LogType.Warning); - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"启动幻灯片放映异常: {ex}", LogHelper.LogType.Error); - } - }).Start(); - } - - private async void BtnPPTSlideShowEnd_Click(object sender, RoutedEventArgs e) { - try - { - // 保存当前页墨迹 - var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0; - if (currentSlide > 0) - { - Application.Current.Dispatcher.Invoke(() => { - _pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes); - timeMachine.ClearStrokeHistory(); - }); - } - - // 结束放映 - if (_pptManager?.TryEndSlideShow() == true) - { - LogHelper.WriteLogToFile("成功结束幻灯片放映", LogHelper.LogType.Event); - } - else - { - LogHelper.WriteLogToFile("结束幻灯片放映失败", LogHelper.LogType.Warning); - - // 手动更新UI状态,防止事件未触发 - await Application.Current.Dispatcher.InvokeAsync(() => { - _pptUIManager?.UpdateSlideShowStatus(false); - _pptUIManager?.UpdateSidebarExitButtons(false); - LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace); - }); - } - - HideSubPanels("cursor"); - await Task.Delay(150); - ViewboxFloatingBarMarginAnimation(100, true); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"结束PPT放映操作异常: {ex}", LogHelper.LogType.Error); - - // 确保UI状态正确 - await Application.Current.Dispatcher.InvokeAsync(() => { - _pptUIManager?.UpdateSlideShowStatus(false); - _pptUIManager?.UpdateSidebarExitButtons(false); - }); - } - } - - private void GridPPTControlPrevious_MouseDown(object sender, MouseButtonEventArgs e) - { - lastBorderMouseDownObject = sender; - if (sender == PPTLSPreviousButtonBorder) { - PPTLSPreviousButtonFeedbackBorder.Opacity = 0.15; - } else if (sender == PPTRSPreviousButtonBorder) { - PPTRSPreviousButtonFeedbackBorder.Opacity = 0.15; - } else if (sender == PPTLBPreviousButtonBorder) - { - PPTLBPreviousButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRBPreviousButtonBorder) - { - PPTRBPreviousButtonFeedbackBorder.Opacity = 0.15; - } - } - private void GridPPTControlPrevious_MouseLeave(object sender, MouseEventArgs e) - { - lastBorderMouseDownObject = null; - if (sender == PPTLSPreviousButtonBorder) { - PPTLSPreviousButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTRSPreviousButtonBorder) { - PPTRSPreviousButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTLBPreviousButtonBorder) - { - PPTLBPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPreviousButtonBorder) - { - PPTRBPreviousButtonFeedbackBorder.Opacity = 0; - } - } - private void GridPPTControlPrevious_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - if (sender == PPTLSPreviousButtonBorder) { - PPTLSPreviousButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTRSPreviousButtonBorder) { - PPTRSPreviousButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTLBPreviousButtonBorder) - { - PPTLBPreviousButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBPreviousButtonBorder) - { - PPTRBPreviousButtonFeedbackBorder.Opacity = 0; - } - BtnPPTSlidesUp_Click(BtnPPTSlidesUp, null); - } - - - private void GridPPTControlNext_MouseDown(object sender, MouseButtonEventArgs e) { - lastBorderMouseDownObject = sender; - if (sender == PPTLSNextButtonBorder) { - PPTLSNextButtonFeedbackBorder.Opacity = 0.15; - } else if (sender == PPTRSNextButtonBorder) { - PPTRSNextButtonFeedbackBorder.Opacity = 0.15; - } else if (sender == PPTLBNextButtonBorder) - { - PPTLBNextButtonFeedbackBorder.Opacity = 0.15; - } - else if (sender == PPTRBNextButtonBorder) - { - PPTRBNextButtonFeedbackBorder.Opacity = 0.15; - } - } - private void GridPPTControlNext_MouseLeave(object sender, MouseEventArgs e) - { - lastBorderMouseDownObject = null; - if (sender == PPTLSNextButtonBorder) { - PPTLSNextButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTRSNextButtonBorder) { - PPTRSNextButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTLBNextButtonBorder) - { - PPTLBNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBNextButtonBorder) - { - PPTRBNextButtonFeedbackBorder.Opacity = 0; - } - } - private void GridPPTControlNext_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - if (sender == PPTLSNextButtonBorder) { - PPTLSNextButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTRSNextButtonBorder) { - PPTRSNextButtonFeedbackBorder.Opacity = 0; - } else if (sender == PPTLBNextButtonBorder) - { - PPTLBNextButtonFeedbackBorder.Opacity = 0; - } - else if (sender == PPTRBNextButtonBorder) - { - PPTRBNextButtonFeedbackBorder.Opacity = 0; - } - BtnPPTSlidesDown_Click(BtnPPTSlidesDown, null); - } - - private void ImagePPTControlEnd_MouseUp(object sender, MouseButtonEventArgs e) { - BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null); - } - - - - - - - - - - - - - - - - - - - - - - - - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs deleted file mode 100644 index 16809605..00000000 --- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs +++ /dev/null @@ -1,635 +0,0 @@ -using Ink_Canvas.Helpers; -using System; -using System.IO; -using System.Windows; -using System.Windows.Ink; -using System.Windows.Input; -using File = System.IO.File; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using OpenFileDialog = Microsoft.Win32.OpenFileDialog; -using System.Collections.Generic; -using System.Windows.Controls; -using Newtonsoft.Json; - -namespace Ink_Canvas { - // 1. 定义元素信息结构 - public class CanvasElementInfo - { - public string Type { get; set; } // "Image" - public string SourcePath { get; set; } - public double Left { get; set; } - public double Top { get; set; } - public double Width { get; set; } - public double Height { get; set; } - public string Stretch { get; set; } = "Fill"; // 默认为Fill - } - public partial class MainWindow : Window { - private void SymbolIconSaveStrokes_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return; - - AnimationsHelper.HideWithSlideAndFade(BorderTools); - AnimationsHelper.HideWithSlideAndFade(BoardBorderTools); - - GridNotifications.Visibility = Visibility.Collapsed; - - SaveInkCanvasStrokes(true, true); - } - - private void SaveInkCanvasStrokes(bool newNotice = true, bool saveByUser = false) { - try { - var savePath = Settings.Automation.AutoSavedStrokesLocation - + (saveByUser ? @"\User Saved - " : @"\Auto Saved - ") - + (currentMode == 0 ? "Annotation Strokes" : "BlackBoard Strokes"); - if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath); - string savePathWithName; - if (currentMode != 0) // 黑板模式下 - savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + " Page-" + - CurrentWhiteboardIndex + " StrokesCount-" + inkCanvas.Strokes.Count + ".icstk"; - else - //savePathWithName = savePath + @"\" + DateTime.Now.ToString("u").Replace(':', '-') + ".icstk"; - savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + ".icstk"; - - if (Settings.Automation.IsSaveFullPageStrokes) - { - // 全页面保存模式 - 检查是否存在多页面墨迹 - bool hasMultiplePages = false; - List allPageStrokes = new List(); - - // 检查PPT放映模式下的多页面墨迹 - if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _pptManager?.IsConnected == true) - { - hasMultiplePages = true; - // 收集PPT放映模式下的所有页面墨迹 - var totalSlides = _pptManager.SlidesCount; - var currentSlide = _pptManager.GetCurrentSlideNumber(); - - for (int i = 1; i <= totalSlides; i++) - { - var slideStrokes = _pptInkManager?.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) - { - // 多页面墨迹保存为压缩包 - string zipFileName = Path.ChangeExtension(savePathWithName, "zip"); - SaveMultiPageStrokesAsZip(allPageStrokes, zipFileName, newNotice); - } - else - { - // 单页面墨迹保存为图像 - SaveSinglePageStrokesAsImage(savePathWithName, newNotice); - } - } - else - { - // 常规保存模式 - 仅保存墨迹对象 - var fs = new FileStream(savePathWithName, FileMode.Create); - inkCanvas.Strokes.Save(fs); - fs.Close(); - // 保存元素信息 - var elementInfos = new List(); - 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(savePathWithName, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Formatting.Indented)); - if (newNotice) ShowNotification("墨迹成功保存至 " + savePathWithName); - } - } - catch (Exception ex) { - ShowNotification("墨迹保存失败"); - LogHelper.WriteLogToFile("墨迹保存失败 | " + ex.ToString(), LogHelper.LogType.Error); - } - } - - /// - /// 将多页面墨迹保存为压缩包 - /// - private void SaveMultiPageStrokesAsZip(List allPageStrokes, string zipFileName, bool newNotice) - { - try - { - // 创建临时目录来存放文件 - string tempDir = Path.Combine(Path.GetTempPath(), $"InkCanvas_MultiPage_{DateTime.Now:yyyyMMdd_HHmmss}"); - Directory.CreateDirectory(tempDir); - - try - { - // 保存所有页面的文件到临时目录 - for (int i = 0; i < allPageStrokes.Count; i++) - { - var strokes = allPageStrokes[i]; - if (strokes.Count > 0) - { - // 保存墨迹文件 - string strokeFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.icstk"); - using (var fs = new FileStream(strokeFileName, FileMode.Create)) - { - strokes.Save(fs); - } - - // 保存页面图像 - string imageFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.png"); - using (var fs = new FileStream(imageFileName, FileMode.Create)) - { - SavePageAsImage(strokes, fs); - } - } - } - - // 保存元数据信息 - string metadataFile = Path.Combine(tempDir, "metadata.txt"); - using (var writer = new StreamWriter(metadataFile, false, System.Text.Encoding.UTF8)) - { - writer.WriteLine($"保存时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); - writer.WriteLine($"总页数: {allPageStrokes.Count}"); - writer.WriteLine($"模式: {(currentMode == 0 ? "PPT放映" : "白板")}"); - 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} 条墨迹"); - } - } - - // 使用.NET Framework内置的压缩功能创建ZIP文件 - if (File.Exists(zipFileName)) - File.Delete(zipFileName); - - // 使用System.IO.Compression.FileSystem来创建ZIP - System.IO.Compression.ZipFile.CreateFromDirectory(tempDir, zipFileName); - - if (newNotice) ShowNotification($"多页面墨迹成功保存至压缩包 {zipFileName}"); - } - finally - { - // 清理临时目录 - try - { - if (Directory.Exists(tempDir)) - Directory.Delete(tempDir, true); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理临时目录失败: {ex.ToString()}", LogHelper.LogType.Warning); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存多页面墨迹压缩包失败: {ex.ToString()}", LogHelper.LogType.Error); - throw; - } - } - - /// - /// 将单页面墨迹保存为图像 - /// - private void SaveSinglePageStrokesAsImage(string savePathWithName, bool newNotice) - { - // 全页面保存模式 - 保存整个墨迹页面的图像 - var bitmap = new System.Drawing.Bitmap( - (int)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, - (int)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height); - - using (var g = System.Drawing.Graphics.FromImage(bitmap)) - { - // 创建黑色或透明背景 - System.Drawing.Color bgColor = Settings.Canvas.UsingWhiteboard - ? System.Drawing.Color.White - : System.Drawing.Color.FromArgb(22, 41, 36); // 黑板背景色 - g.Clear(bgColor); - - // 将InkCanvas墨迹渲染到Visual - var visual = new DrawingVisual(); - using (var dc = visual.RenderOpen()) - { - // 创建一个VisualBrush,使用inkCanvas作为源 - var visualBrush = new VisualBrush(inkCanvas); - // 绘制矩形并填充为inkCanvas的内容 - dc.DrawRectangle(visualBrush, null, new Rect(0, 0, inkCanvas.ActualWidth, inkCanvas.ActualHeight)); - } - - // 创建适合墨迹画布尺寸的渲染位图 - var rtb = new RenderTargetBitmap( - (int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight, - 96, 96, - PixelFormats.Pbgra32); - rtb.Render(visual); - - // 转换为GDI+ Bitmap并保存 - var encoder = new PngBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(rtb)); - - using (var ms = new MemoryStream()) - { - encoder.Save(ms); - ms.Seek(0, SeekOrigin.Begin); - var imgBitmap = new System.Drawing.Bitmap(ms); - - // 将生成的墨迹图像绘制到屏幕截图上 - // 居中绘制,确保墨迹位于屏幕中央 - int x = (bitmap.Width - imgBitmap.Width) / 2; - int y = (bitmap.Height - imgBitmap.Height) / 2; - g.DrawImage(imgBitmap, x, y); - - // 保存为PNG - string imagePathWithName = Path.ChangeExtension(savePathWithName, "png"); - bitmap.Save(imagePathWithName, System.Drawing.Imaging.ImageFormat.Png); - - // 仍然保存墨迹文件以兼容旧版本 - var fs = new FileStream(savePathWithName, FileMode.Create); - inkCanvas.Strokes.Save(fs); - fs.Close(); - } - } - - // 显示提示 - if (newNotice) ShowNotification("墨迹成功全页面保存至 " + Path.ChangeExtension(savePathWithName, "png")); - } - - /// - /// 将指定墨迹集合保存为图像到指定流 - /// - private void SavePageAsImage(StrokeCollection strokes, Stream outputStream) - { - try - { - // 创建临时InkCanvas来渲染墨迹 - var tempCanvas = new InkCanvas(); - tempCanvas.Strokes = strokes; - tempCanvas.Width = inkCanvas.ActualWidth; - tempCanvas.Height = inkCanvas.ActualHeight; - - // 创建渲染位图 - var rtb = new RenderTargetBitmap( - (int)tempCanvas.Width, (int)tempCanvas.Height, - 96, 96, - PixelFormats.Pbgra32); - rtb.Render(tempCanvas); - - // 保存为PNG - var encoder = new PngBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(rtb)); - encoder.Save(outputStream); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"保存页面图像失败: {ex.ToString()}", LogHelper.LogType.Error); - throw; - } - } - - private void SymbolIconOpenStrokes_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - AnimationsHelper.HideWithSlideAndFade(BorderTools); - AnimationsHelper.HideWithSlideAndFade(BoardBorderTools); - - var openFileDialog = new OpenFileDialog(); - openFileDialog.InitialDirectory = Settings.Automation.AutoSavedStrokesLocation; - openFileDialog.Title = "打开墨迹文件"; - openFileDialog.Filter = "Ink Canvas Strokes File (*.icstk)|*.icstk|ICC压缩包 (*.zip)|*.zip"; - if (openFileDialog.ShowDialog() != true) return; - LogHelper.WriteLogToFile($"Strokes Insert: Name: {openFileDialog.FileName}", - LogHelper.LogType.Event); - - try { - string fileExtension = Path.GetExtension(openFileDialog.FileName).ToLower(); - - if (fileExtension == ".zip") { - // 处理ICC压缩包 - OpenICCZipFile(openFileDialog.FileName); - } else { - // 处理单个墨迹文件 - OpenSingleStrokeFile(openFileDialog.FileName); - } - - if (inkCanvas.Visibility != Visibility.Visible) SymbolIconCursor_Click(sender, null); - } - catch (Exception ex) { - ShowNotification("墨迹打开失败"); - LogHelper.WriteLogToFile($"墨迹打开失败: {ex.ToString()}", LogHelper.LogType.Error); - } - } - - /// - /// 打开ICC创建的.zip压缩包 - /// - private void OpenICCZipFile(string zipFilePath) { - try { - // 创建临时目录来解压文件 - string tempDir = Path.Combine(Path.GetTempPath(), $"InkCanvas_Open_{DateTime.Now:yyyyMMdd_HHmmss}"); - Directory.CreateDirectory(tempDir); - - try { - // 解压ZIP文件 - System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempDir); - - // 读取元数据文件 - string metadataFile = Path.Combine(tempDir, "metadata.txt"); - if (!File.Exists(metadataFile)) { - throw new Exception("压缩包中未找到元数据文件"); - } - - var metadata = ReadMetadataFile(metadataFile); - - // 根据元数据信息决定恢复模式 - bool isPPTMode = metadata.ContainsKey("模式") && metadata["模式"].Contains("PPT放映"); - bool isWhiteboardMode = metadata.ContainsKey("模式") && metadata["模式"].Contains("白板"); - - // 检查当前是否处于PPT模式 - bool isCurrentlyInPPTMode = BtnPPTSlideShowEnd.Visibility == Visibility.Visible && pptApplication != null; - - // 检查当前是否处于白板模式 - bool isCurrentlyInWhiteboardMode = currentMode != 0; - - // 严格模式隔离:只在对应模式下恢复对应墨迹 - if (isPPTMode && isCurrentlyInPPTMode) { - // 只在PPT放映模式下恢复PPT墨迹 - RestorePPTStrokesFromZip(tempDir, metadata); - } else if (isWhiteboardMode && isCurrentlyInWhiteboardMode) { - // 只在白板模式下恢复白板墨迹 - RestoreWhiteboardStrokesFromZip(tempDir, metadata); - } else { - // 模式不匹配时,显示提示信息 - string savedMode = isPPTMode ? "PPT放映" : (isWhiteboardMode ? "白板" : "未知"); - string currentMode = isCurrentlyInPPTMode ? "PPT放映" : (isCurrentlyInWhiteboardMode ? "白板" : "桌面"); - ShowNotification($"墨迹保存模式({savedMode})与当前模式({currentMode})不匹配,无法恢复墨迹"); - LogHelper.WriteLogToFile($"模式不匹配:保存模式={savedMode},当前模式={currentMode}", LogHelper.LogType.Warning); - } - - ShowNotification($"成功打开ICC压缩包,共{(metadata.ContainsKey("总页数") ? metadata["总页数"] : "0")}页"); - } - finally { - // 清理临时目录 - try { - if (Directory.Exists(tempDir)) - Directory.Delete(tempDir, true); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"清理临时目录失败: {ex.ToString()}", LogHelper.LogType.Warning); - } - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"打开ICC压缩包失败: {ex.ToString()}", LogHelper.LogType.Error); - throw; - } - } - - /// - /// 读取元数据文件 - /// - private Dictionary ReadMetadataFile(string metadataPath) { - var metadata = new Dictionary(); - - using (var reader = new StreamReader(metadataPath, System.Text.Encoding.UTF8)) { - string line; - while ((line = reader.ReadLine()) != null) { - if (line.Contains(":")) { - var parts = line.Split(new[] { ':' }, 2); - if (parts.Length == 2) { - metadata[parts[0].Trim()] = parts[1].Trim(); - } - } - } - } - - return metadata; - } - - /// - /// 从ZIP文件恢复PPT墨迹 - /// - private void RestorePPTStrokesFromZip(string tempDir, Dictionary metadata) { - try { - // 确保当前处于PPT放映模式 - if (BtnPPTSlideShowEnd.Visibility != Visibility.Visible || pptApplication == null) { - throw new InvalidOperationException("当前不在PPT放映模式,无法恢复PPT墨迹"); - } - - // 检查PPT文件路径是否匹配 - if (metadata.ContainsKey("PPT文件路径")) - { - string savedPptPath = metadata["PPT文件路径"]; - string currentPptPath = pptApplication.SlideShowWindows[1].Presentation.FullName; - - if (!string.IsNullOrEmpty(savedPptPath) && !string.IsNullOrEmpty(currentPptPath)) - { - // 使用文件路径哈希值进行比较,避免路径格式差异 - string savedHash = GetFileHash(savedPptPath); - string currentHash = GetFileHash(currentPptPath); - - if (savedHash != currentHash) - { - throw new InvalidOperationException($"墨迹文件与当前PPT文件不匹配。保存的PPT: {savedPptPath},当前PPT: {currentPptPath}"); - } - } - } - - // 清空当前墨迹 - ClearStrokes(true); - timeMachine.ClearStrokeHistory(); - - // 重置PPT墨迹存储 - _pptInkManager?.ClearAllStrokes(); - - // 读取所有页面的墨迹文件 - var files = Directory.GetFiles(tempDir, "page_*.icstk"); - foreach (var file in files) { - 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)) { - var strokes = new StrokeCollection(fs); - if (strokes.Count > 0) { - _pptInkManager?.SaveCurrentSlideStrokes(pageNumber, strokes); - } - } - } - } - - // 恢复当前页面的墨迹 - if (_pptManager?.IsInSlideShow == true) { - int currentSlide = _pptManager.GetCurrentSlideNumber(); - var currentStrokes = _pptInkManager?.LoadSlideStrokes(currentSlide); - if (currentStrokes != null && currentStrokes.Count > 0) { - inkCanvas.Strokes.Add(currentStrokes); - } - } - - LogHelper.WriteLogToFile($"成功恢复PPT墨迹,共{files.Length}页"); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"恢复PPT墨迹失败: {ex.ToString()}", LogHelper.LogType.Error); - throw; - } - } - - /// - /// 从ZIP文件恢复白板墨迹 - /// - private void RestoreWhiteboardStrokesFromZip(string tempDir, Dictionary metadata) { - try { - // 确保当前处于白板模式 - if (currentMode == 0) { - throw new InvalidOperationException("当前不在白板模式,无法恢复白板墨迹"); - } - - // 清空当前墨迹 - ClearStrokes(true); - timeMachine.ClearStrokeHistory(); - - // 读取总页数 - int totalPages = 1; - if (metadata.ContainsKey("总页数") && int.TryParse(metadata["总页数"], out int parsedPages)) { - totalPages = parsedPages; - } - - // 重置白板状态 - WhiteboardTotalCount = totalPages; - CurrentWhiteboardIndex = 1; - - // 清空历史记录 - for (int i = 0; i < TimeMachineHistories.Length; i++) { - TimeMachineHistories[i] = null; - } - - // 读取所有页面的墨迹文件 - var files = Directory.GetFiles(tempDir, "page_*.icstk"); - foreach (var file in files) { - 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)) { - var strokes = new StrokeCollection(fs); - if (strokes.Count > 0) { - // 创建历史记录 - var history = new TimeMachineHistory(strokes, TimeMachineHistoryType.UserInput, false); - TimeMachineHistories[pageNumber] = new TimeMachineHistory[] { history }; - } - } - } - } - - // 恢复第一页的墨迹 - if (TimeMachineHistories[1] != null) { - RestoreStrokes(); - } - - // 更新UI显示 - UpdateIndexInfoDisplay(); - - LogHelper.WriteLogToFile($"成功恢复白板墨迹,共{totalPages}页"); - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"恢复白板墨迹失败: {ex.ToString()}", LogHelper.LogType.Error); - throw; - } - } - - /// - /// 打开单个墨迹文件 - /// - private void OpenSingleStrokeFile(string filePath) { - var fileStreamHasNoStroke = false; - using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { - var strokes = new StrokeCollection(fs); - fileStreamHasNoStroke = strokes.Count == 0; - if (!fileStreamHasNoStroke) { - ClearStrokes(true); - timeMachine.ClearStrokeHistory(); - inkCanvas.Strokes.Add(strokes); - LogHelper.NewLog($"Strokes Insert: Strokes Count: {inkCanvas.Strokes.Count.ToString()}"); - } - } - - // 恢复元素信息 - var elementsFile = Path.ChangeExtension(filePath, ".elements.json"); - if (File.Exists(elementsFile)) - { - var elementInfos = JsonConvert.DeserializeObject>(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(info.Stretch, out var stretch) ? stretch : Stretch.Fill - }; - InkCanvas.SetLeft(img, info.Left); - InkCanvas.SetTop(img, info.Top); - inkCanvas.Children.Add(img); - } - } - } - - if (fileStreamHasNoStroke) - using (var ms = new MemoryStream(File.ReadAllBytes(filePath))) { - ms.Seek(0, SeekOrigin.Begin); - var strokes = new StrokeCollection(ms); - ClearStrokes(true); - timeMachine.ClearStrokeHistory(); - inkCanvas.Strokes.Add(strokes); - LogHelper.NewLog($"Strokes Insert (2): Strokes Count: {strokes.Count.ToString()}"); - } - } - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs deleted file mode 100644 index d33e88de..00000000 --- a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs +++ /dev/null @@ -1,337 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.IO; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Forms; -using System.Windows.Media.Imaging; -using Ink_Canvas.Helpers; -using Ink_Canvas.Helpers; -using Application = System.Windows.Application; -using Clipboard = System.Windows.Clipboard; -using Size = System.Drawing.Size; - -namespace Ink_Canvas { - // 截图结果结构体 - public struct ScreenshotResult - { - public System.Drawing.Rectangle Area; - public List Path; - - public ScreenshotResult(System.Drawing.Rectangle area, List path = null) - { - Area = area; - Path = path; - } - } - - public partial class MainWindow : Window { - private void SaveScreenShot(bool isHideNotification, string fileName = null) { - var savePath = Settings.Automation.IsSaveScreenshotsInDateFolders - ? GetDateFolderPath(fileName) - : GetDefaultFolderPath(); - - CaptureAndSaveScreenshot(savePath, isHideNotification); - - if (Settings.Automation.IsAutoSaveStrokesAtScreenshot) - SaveInkCanvasStrokes(false); - } - - private void SaveScreenShotToDesktop() { - var desktopPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), - $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"); - - CaptureAndSaveScreenshot(desktopPath, false); - - if (Settings.Automation.IsAutoSaveStrokesAtScreenshot) - SaveInkCanvasStrokes(false); - } - - // 提取公共的截图和保存逻辑 - private void CaptureAndSaveScreenshot(string savePath, bool isHideNotification) { - var rc = SystemInformation.VirtualScreen; - - using (var bitmap = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb)) - using (var memoryGraphics = Graphics.FromImage(bitmap)) { - memoryGraphics.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy); - - // 确保目录存在 - var directory = Path.GetDirectoryName(savePath); - if (!Directory.Exists(directory)) { - Directory.CreateDirectory(directory); - } - - bitmap.Save(savePath, ImageFormat.Png); - } - - if (!isHideNotification) { - ShowNotification($"截图成功保存至 {savePath}"); - } - } - - // 获取日期文件夹路径 - private string GetDateFolderPath(string fileName) { - if (string.IsNullOrWhiteSpace(fileName)) { - fileName = DateTime.Now.ToString("HH-mm-ss"); - } - - var basePath = Settings.Automation.AutoSavedStrokesLocation; - var dateFolder = DateTime.Now.ToString("yyyyMMdd"); - - return Path.Combine( - basePath, - "Auto Saved - Screenshots", - dateFolder, - $"{fileName}.png"); - } - - // 获取默认文件夹路径 - private string GetDefaultFolderPath() { - var basePath = Settings.Automation.AutoSavedStrokesLocation; - var screenshotsFolder = Path.Combine(basePath, "Auto Saved - Screenshots"); - - if (!Directory.Exists(screenshotsFolder)) { - Directory.CreateDirectory(screenshotsFolder); - } - - return Path.Combine( - screenshotsFolder, - $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"); - } - - // 截图并复制到剪贴板 - private async Task CaptureScreenshotToClipboard() { - try { - // 隐藏主窗口以避免截图包含窗口本身 - var originalVisibility = this.Visibility; - this.Visibility = Visibility.Hidden; - - // 等待窗口隐藏 - await Task.Delay(200); - - // 启动区域选择截图 - var screenshotResult = await ShowScreenshotSelector(); - - // 恢复窗口显示 - this.Visibility = originalVisibility; - - if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0) - { - // 截取选定区域 - using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area)) - { - if (originalBitmap != null) - { - Bitmap finalBitmap = originalBitmap; - - // 如果有路径信息,应用形状遮罩 - if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) - { - finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); - } - - // 将截图复制到剪贴板 - CopyBitmapToClipboard(finalBitmap); - - // 等待窗口完全显示后自动粘贴 - await Task.Delay(100); - await AutoPasteScreenshot(); - } - } - } - else - { - ShowNotification("截图已取消"); - } - } - catch (Exception ex) { - ShowNotification($"截图失败: {ex.Message}"); - this.Visibility = Visibility.Visible; - } - } - - // 显示截图区域选择器 - private async Task ShowScreenshotSelector() - { - ScreenshotResult? result = null; - - try - { - await Application.Current.Dispatcher.InvokeAsync(() => - { - var selectorWindow = new ScreenshotSelectorWindow(); - if (selectorWindow.ShowDialog() == true) - { - result = new ScreenshotResult( - selectorWindow.SelectedArea.Value, - selectorWindow.SelectedPath - ); - } - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"显示截图选择器失败: {ex.Message}", LogHelper.LogType.Error); - } - - return result; - } - - // 截取指定屏幕区域 - private Bitmap CaptureScreenArea(System.Drawing.Rectangle area) - { - try - { - // 确保区域在有效范围内 - var virtualScreen = SystemInformation.VirtualScreen; - - // 调整区域边界,确保不超出屏幕范围 - int x = Math.Max(area.X, virtualScreen.X); - int y = Math.Max(area.Y, virtualScreen.Y); - int right = Math.Min(area.Right, virtualScreen.Right); - int bottom = Math.Min(area.Bottom, virtualScreen.Bottom); - - int width = Math.Max(1, right - x); - int height = Math.Max(1, bottom - y); - - var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); - using (var graphics = Graphics.FromImage(bitmap)) - { - // 设置高质量渲染 - graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; - - // 截取屏幕区域 - graphics.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); - } - - LogHelper.WriteLogToFile($"成功截取区域: X={x}, Y={y}, Width={width}, Height={height}", LogHelper.LogType.Info); - return bitmap; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"截取屏幕区域失败: {ex.Message}", LogHelper.LogType.Error); - return null; - } - } - - // 自动粘贴截图到画布 - private async Task AutoPasteScreenshot() { - try { - // 只在白板模式下自动粘贴 - if (currentMode == 1) { - await PasteImageFromClipboard(); - ShowNotification("截图已自动插入到画布"); - } else { - ShowNotification("截图已复制到剪贴板,可在白板模式下粘贴"); - } - } - catch (Exception ex) { - ShowNotification($"自动粘贴截图失败: {ex.Message}"); - LogHelper.WriteLogToFile($"自动粘贴截图失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - // 将Bitmap复制到剪贴板 - private void CopyBitmapToClipboard(Bitmap bitmap) { - try { - // 将System.Drawing.Bitmap转换为WPF BitmapSource - var bitmapSource = ConvertBitmapToBitmapSource(bitmap); - - // 复制到剪贴板 - Clipboard.SetImage(bitmapSource); - } - catch (Exception ex) { - ShowNotification($"复制到剪贴板失败: {ex.Message}"); - } - } - - // 应用形状遮罩到截图 - private Bitmap ApplyShapeMask(Bitmap bitmap, List path, System.Drawing.Rectangle area) - { - try - { - // 获取DPI缩放比例 - var dpiScale = GetDpiScale(); - var virtualScreen = SystemInformation.VirtualScreen; - - // 创建结果位图 - var resultBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb); - using (var resultGraphics = Graphics.FromImage(resultBitmap)) - { - // 设置高质量渲染 - resultGraphics.SmoothingMode = SmoothingMode.AntiAlias; - resultGraphics.CompositingQuality = CompositingQuality.HighQuality; - - // 创建路径 - using (var pathGraphics = new GraphicsPath()) - { - // 转换WPF坐标到GDI+坐标,考虑DPI缩放和屏幕偏移 - var points = new PointF[path.Count]; - for (int i = 0; i < path.Count; i++) - { - // 将WPF坐标转换为实际屏幕坐标,然后相对于截图区域计算偏移 - double screenX = (path[i].X * dpiScale) + virtualScreen.Left; - double screenY = (path[i].Y * dpiScale) + virtualScreen.Top; - - // 计算相对于截图区域的坐标 - float relativeX = (float)(screenX - area.X); - float relativeY = (float)(screenY - area.Y); - - points[i] = new PointF(relativeX, relativeY); - } - - // 添加路径 - pathGraphics.AddPolygon(points); - - // 设置裁剪区域为路径内部 - resultGraphics.SetClip(pathGraphics); - - // 在裁剪区域内绘制原始图像 - resultGraphics.DrawImage(bitmap, 0, 0); - } - } - - return resultBitmap; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用形状遮罩失败: {ex.Message}", LogHelper.LogType.Error); - return bitmap; // 如果失败,返回原始图像 - } - } - - // 获取DPI缩放比例 - private double GetDpiScale() - { - var source = PresentationSource.FromVisual(this); - if (source?.CompositionTarget != null) - { - return source.CompositionTarget.TransformToDevice.M11; - } - return 1.0; // 默认DPI - } - - // 将System.Drawing.Bitmap转换为WPF BitmapSource - private BitmapSource ConvertBitmapToBitmapSource(Bitmap bitmap) { - using (var memory = new MemoryStream()) { - bitmap.Save(memory, ImageFormat.Png); - memory.Position = 0; - - var bitmapImage = new BitmapImage(); - bitmapImage.BeginInit(); - bitmapImage.StreamSource = memory; - bitmapImage.CacheOption = BitmapCacheOption.OnLoad; - bitmapImage.EndInit(); - bitmapImage.Freeze(); - - return bitmapImage; - } - } - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs b/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs deleted file mode 100644 index d9b86693..00000000 --- a/Ink Canvas/MainWindow_cs/MW_SelectionGestures.cs +++ /dev/null @@ -1,988 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Shapes; -using System.Windows.Threading; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; -using Point = System.Windows.Point; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - #region Floating Control - - private object lastBorderMouseDownObject; - - private void Border_MouseDown(object sender, MouseButtonEventArgs e) { - // 如果发送者是 RandomDrawPanel 或 SingleDrawPanel,且它们被隐藏,则不处理事件 - if (sender is SimpleStackPanel panel) { - if ((panel == RandomDrawPanel || panel == SingleDrawPanel) && - panel.Visibility != Visibility.Visible) { - return; - } - } - lastBorderMouseDownObject = sender; - } - - private bool isStrokeSelectionCloneOn; - - private void BorderStrokeSelectionClone_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - if (isStrokeSelectionCloneOn) { - BorderStrokeSelectionClone.Background = Brushes.Transparent; - - isStrokeSelectionCloneOn = false; - } - else { - BorderStrokeSelectionClone.Background = new SolidColorBrush(StringToColor("#FF1ED760")); - - isStrokeSelectionCloneOn = true; - } - } - - private void BorderStrokeSelectionCloneToNewBoard_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - var strokes = inkCanvas.GetSelectedStrokes(); - inkCanvas.Select(new StrokeCollection()); - strokes = strokes.Clone(); - BtnWhiteBoardAdd_Click(null, null); - inkCanvas.Strokes.Add(strokes); - } - - private void BorderStrokeSelectionDelete_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - SymbolIconDelete_MouseUp(sender, e); - } - - private void GridPenWidthDecrease_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - ChangeStrokeThickness(0.8); - } - - private void GridPenWidthIncrease_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - ChangeStrokeThickness(1.25); - } - - private void ChangeStrokeThickness(double multipler) { - foreach (var stroke in inkCanvas.GetSelectedStrokes()) { - var newWidth = stroke.DrawingAttributes.Width * multipler; - var newHeight = stroke.DrawingAttributes.Height * multipler; - if (!(newWidth >= DrawingAttributes.MinWidth) || !(newWidth <= DrawingAttributes.MaxWidth) - || !(newHeight >= DrawingAttributes.MinHeight) || - !(newHeight <= DrawingAttributes.MaxHeight)) continue; - stroke.DrawingAttributes.Width = newWidth; - stroke.DrawingAttributes.Height = newHeight; - } - if (DrawingAttributesHistory.Count > 0) - { - - timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory); - DrawingAttributesHistory = new Dictionary>(); - foreach (var item in DrawingAttributesHistoryFlag) - { - item.Value.Clear(); - } - } - } - - private void GridPenWidthRestore_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - foreach (var stroke in inkCanvas.GetSelectedStrokes()) { - stroke.DrawingAttributes.Width = inkCanvas.DefaultDrawingAttributes.Width; - stroke.DrawingAttributes.Height = inkCanvas.DefaultDrawingAttributes.Height; - } - } - - private void ImageFlipHorizontal_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - var m = new Matrix(); - - // 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 = new Point(inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Width / 2, - inkCanvas.GetSelectionBounds().Top + inkCanvas.GetSelectionBounds().Height / 2); - center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点 - - // Update matrix to reflect translation/rotation - m.ScaleAt(-1, 1, center.X, center.Y); // 缩放 - - var targetStrokes = inkCanvas.GetSelectedStrokes(); - foreach (var stroke in targetStrokes) stroke.Transform(m, false); - - if (DrawingAttributesHistory.Count > 0) - { - //var collecion = new StrokeCollection(); - //foreach (var item in DrawingAttributesHistory) - //{ - // collecion.Add(item.Key); - //} - timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory); - DrawingAttributesHistory = new Dictionary>(); - foreach (var item in DrawingAttributesHistoryFlag) - { - item.Value.Clear(); - } - } - - //updateBorderStrokeSelectionControlLocation(); - } - - private void ImageFlipVertical_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - var m = new Matrix(); - - // 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 = new Point(inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Width / 2, - inkCanvas.GetSelectionBounds().Top + inkCanvas.GetSelectionBounds().Height / 2); - center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点 - - // Update matrix to reflect translation/rotation - m.ScaleAt(1, -1, center.X, center.Y); // 缩放 - - var targetStrokes = inkCanvas.GetSelectedStrokes(); - foreach (var stroke in targetStrokes) stroke.Transform(m, false); - - if (DrawingAttributesHistory.Count > 0) - { - timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory); - DrawingAttributesHistory = new Dictionary>(); - foreach (var item in DrawingAttributesHistoryFlag) - { - item.Value.Clear(); - } - } - } - -// ... existing code ... - private void ImageRotate45_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - var m = new Matrix(); - - // 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 = new Point(inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Width / 2, - inkCanvas.GetSelectionBounds().Top + inkCanvas.GetSelectionBounds().Height / 2); - center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点 - - // Update matrix to reflect translation/rotation - m.RotateAt(45, center.X, center.Y); // 顺时针旋转45度 - - var targetStrokes = inkCanvas.GetSelectedStrokes(); - foreach (var stroke in targetStrokes) stroke.Transform(m, false); - - if (DrawingAttributesHistory.Count > 0) - { - timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory); - DrawingAttributesHistory = new Dictionary>(); - foreach (var item in DrawingAttributesHistoryFlag) - { - item.Value.Clear(); - } - } - } - - private void ImageRotate90_MouseUp(object sender, MouseButtonEventArgs e) { - if (lastBorderMouseDownObject != sender) return; - - var m = new Matrix(); - - // 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 = new Point(inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Width / 2, - inkCanvas.GetSelectionBounds().Top + inkCanvas.GetSelectionBounds().Height / 2); - center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点 - - // Update matrix to reflect translation/rotation - m.RotateAt(90, center.X, center.Y); // 旋转 - - var targetStrokes = inkCanvas.GetSelectedStrokes(); - foreach (var stroke in targetStrokes) stroke.Transform(m, false); - - if (DrawingAttributesHistory.Count > 0) - { - var collecion = new StrokeCollection(); - foreach (var item in DrawingAttributesHistory) - { - collecion.Add(item.Key); - } - timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory); - DrawingAttributesHistory = new Dictionary>(); - foreach (var item in DrawingAttributesHistoryFlag) - { - item.Value.Clear(); - } - } - } - - #endregion - - private bool isGridInkCanvasSelectionCoverMouseDown; - private StrokeCollection StrokesSelectionClone = new StrokeCollection(); - - private void GridInkCanvasSelectionCover_MouseDown(object sender, MouseButtonEventArgs e) { - isGridInkCanvasSelectionCoverMouseDown = true; - } - - private void GridInkCanvasSelectionCover_MouseUp(object sender, MouseButtonEventArgs e) { - if (!isGridInkCanvasSelectionCoverMouseDown) return; - isGridInkCanvasSelectionCoverMouseDown = false; - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - } - - private void BtnSelect_Click(object sender, RoutedEventArgs e) { - ExitMultiTouchModeIfNeeded(); - forceEraser = true; - drawingShapeMode = 0; - inkCanvas.IsManipulationEnabled = false; - if (inkCanvas.EditingMode == InkCanvasEditingMode.Select) { - if (inkCanvas.GetSelectedStrokes().Count == inkCanvas.Strokes.Count) { - inkCanvas.EditingMode = InkCanvasEditingMode.Ink; - inkCanvas.EditingMode = InkCanvasEditingMode.Select; - } - else { - var selectedStrokes = new StrokeCollection(); - foreach (var stroke in inkCanvas.Strokes) - if (stroke.GetBounds().Width > 0 && stroke.GetBounds().Height > 0) - selectedStrokes.Add(stroke); - inkCanvas.Select(selectedStrokes); - } - } - else { - inkCanvas.EditingMode = InkCanvasEditingMode.Select; - } - } - - private double BorderStrokeSelectionControlWidth = 490.0; - private double BorderStrokeSelectionControlHeight = 80.0; - private bool isProgramChangeStrokeSelection; - - private void inkCanvas_SelectionChanged(object sender, EventArgs e) { - if (isProgramChangeStrokeSelection) return; - if (inkCanvas.GetSelectedStrokes().Count == 0) { - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - // 当没有选中笔画时,检查是否有选中的UIElement - CheckUIElementSelection(); - } - else { - GridInkCanvasSelectionCover.Visibility = Visibility.Visible; - BorderStrokeSelectionClone.Background = Brushes.Transparent; - isStrokeSelectionCloneOn = false; - updateBorderStrokeSelectionControlLocation(); - // 当选中笔画时,取消UIElement选择 - DeselectUIElement(); - } - } - - private void CheckUIElementSelection() - { - // 检查InkCanvas中的UIElement是否被选中 - var selectedElements = inkCanvas.GetSelectedElements(); - if (selectedElements.Count > 0) - { - var element = selectedElements[0]; - SelectUIElement(element); - } - else - { - DeselectUIElement(); - } - } - - private void updateBorderStrokeSelectionControlLocation() { - var borderLeft = (inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Right - - BorderStrokeSelectionControlWidth) / 2; - var borderTop = inkCanvas.GetSelectionBounds().Bottom + 1; - if (borderLeft < 0) borderLeft = 0; - if (borderTop < 0) borderTop = 0; - if (Width - borderLeft < BorderStrokeSelectionControlWidth || double.IsNaN(borderLeft)) - borderLeft = Width - BorderStrokeSelectionControlWidth; - if (Height - borderTop < BorderStrokeSelectionControlHeight || double.IsNaN(borderTop)) - borderTop = Height - BorderStrokeSelectionControlHeight; - - if (borderTop > 60) borderTop -= 60; - BorderStrokeSelectionControl.Margin = new Thickness(borderLeft, borderTop, 0, 0); - } - - private void GridInkCanvasSelectionCover_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { - e.Mode = ManipulationModes.All; - } - - private void GridInkCanvasSelectionCover_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { - if (StrokeManipulationHistory?.Count > 0) - { - timeMachine.CommitStrokeManipulationHistory(StrokeManipulationHistory); - foreach (var item in StrokeManipulationHistory) - { - StrokeInitialHistory[item.Key] = item.Value.Item2; - } - StrokeManipulationHistory = null; - } - if (DrawingAttributesHistory.Count > 0) - { - timeMachine.CommitStrokeDrawingAttributesHistory(DrawingAttributesHistory); - DrawingAttributesHistory = new Dictionary>(); - foreach (var item in DrawingAttributesHistoryFlag) - { - item.Value.Clear(); - } - } - } - - private void GridInkCanvasSelectionCover_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { - try { - if (dec.Count >= 1) { - bool disableScale = dec.Count >= 3; - var md = e.DeltaManipulation; - var trans = md.Translation; // 获得位移矢量 - var rotate = md.Rotation; // 获得旋转角度 - var scale = md.Scale; // 获得缩放倍数 - - var m = new Matrix(); - - // 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 = new Point(inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Width / 2, - inkCanvas.GetSelectionBounds().Top + inkCanvas.GetSelectionBounds().Height / 2); - center = m.Transform(center); // 转换为矩阵缩放和旋转的中心点 - - // Update matrix to reflect translation/rotation - m.Translate(trans.X, trans.Y); // 移动 - if (!disableScale) - m.ScaleAt(scale.X, scale.Y, center.X, center.Y); // 缩放 - - var strokes = inkCanvas.GetSelectedStrokes(); - if (StrokesSelectionClone.Count != 0) - strokes = StrokesSelectionClone; - else if (Settings.Gesture.IsEnableTwoFingerRotationOnSelection) - m.RotateAt(rotate, center.X, center.Y); // 旋转 - foreach (var stroke in strokes) { - stroke.Transform(m, false); - - try { - stroke.DrawingAttributes.Width *= md.Scale.X; - stroke.DrawingAttributes.Height *= md.Scale.Y; - } - catch { } - } - - updateBorderStrokeSelectionControlLocation(); - } - } - catch { } - } - - private void GridInkCanvasSelectionCover_TouchDown(object sender, TouchEventArgs e) { } - - private void GridInkCanvasSelectionCover_TouchUp(object sender, TouchEventArgs e) { } - - private Point lastTouchPointOnGridInkCanvasCover = new Point(0, 0); - - private void GridInkCanvasSelectionCover_PreviewTouchDown(object sender, TouchEventArgs e) { - dec.Add(e.TouchDevice.Id); - //设备1个的时候,记录中心点 - if (dec.Count == 1) { - var touchPoint = e.GetTouchPoint(null); - centerPoint = touchPoint.Position; - lastTouchPointOnGridInkCanvasCover = touchPoint.Position; - - if (isStrokeSelectionCloneOn) { - var strokes = inkCanvas.GetSelectedStrokes(); - isProgramChangeStrokeSelection = true; - inkCanvas.Select(new StrokeCollection()); - StrokesSelectionClone = strokes.Clone(); - inkCanvas.Select(strokes); - isProgramChangeStrokeSelection = false; - inkCanvas.Strokes.Add(StrokesSelectionClone); - } - else { - // 新增:启动套索选择模式 - inkCanvas.EditingMode = InkCanvasEditingMode.Select; - inkCanvas.Select(new StrokeCollection()); - } - } - } - - private void GridInkCanvasSelectionCover_PreviewTouchUp(object sender, TouchEventArgs e) { - dec.Remove(e.TouchDevice.Id); - if (dec.Count >= 1) return; - isProgramChangeStrokeSelection = false; - if (lastTouchPointOnGridInkCanvasCover == e.GetTouchPoint(null).Position) { - if (!(lastTouchPointOnGridInkCanvasCover.X < inkCanvas.GetSelectionBounds().Left) && - !(lastTouchPointOnGridInkCanvasCover.Y < inkCanvas.GetSelectionBounds().Top) && - !(lastTouchPointOnGridInkCanvasCover.X > inkCanvas.GetSelectionBounds().Right) && - !(lastTouchPointOnGridInkCanvasCover.Y > inkCanvas.GetSelectionBounds().Bottom)) return; - inkCanvas.Select(new StrokeCollection()); - StrokesSelectionClone = new StrokeCollection(); - } - else if (inkCanvas.GetSelectedStrokes().Count == 0) { - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - StrokesSelectionClone = new StrokeCollection(); - } - else { - GridInkCanvasSelectionCover.Visibility = Visibility.Visible; - StrokesSelectionClone = new StrokeCollection(); - } - } - - private void LassoSelect_Click(object sender, RoutedEventArgs e) { - ExitMultiTouchModeIfNeeded(); - forceEraser = false; - forcePointEraser = false; - drawingShapeMode = 0; - inkCanvas.EditingMode = InkCanvasEditingMode.Select; - SetCursorBasedOnEditingMode(inkCanvas); - } - - private void BtnLassoSelect_Click(object sender, RoutedEventArgs e) { - ExitMultiTouchModeIfNeeded(); - forceEraser = false; - forcePointEraser = false; - drawingShapeMode = 0; - inkCanvas.EditingMode = InkCanvasEditingMode.Select; - inkCanvas.IsManipulationEnabled = true; - SetCursorBasedOnEditingMode(inkCanvas); - } - - #region UIElement Selection and Resize - - private UIElement selectedUIElement; - private System.Windows.Controls.Canvas resizeHandlesCanvas; - private readonly List resizeHandles = new List(); - private bool isResizing; - private ResizeDirection currentResizeDirection = ResizeDirection.None; - private Point resizeStartPoint; - private Rect originalElementBounds; - - // 图片工具栏相关 - private Border borderImageSelectionControl; - private double BorderImageSelectionControlWidth = 490.0; // 6个按钮 + 分隔线的实际宽度 - private double BorderImageSelectionControlHeight = 80.0; - - // 元素变化监听相关 - private DispatcherTimer elementUpdateTimer; - private Rect lastElementBounds; - - private enum ResizeDirection - { - None, - TopLeft, - TopCenter, - TopRight, - MiddleLeft, - MiddleRight, - BottomLeft, - BottomCenter, - BottomRight - } - - private void InitializeUIElementSelection() - { - // 创建拖拽手柄画布 - if (resizeHandlesCanvas == null) - { - resizeHandlesCanvas = new System.Windows.Controls.Canvas - { - Background = Brushes.Transparent, - IsHitTestVisible = true, - Visibility = Visibility.Collapsed - }; - - // 将手柄画布添加到主网格中,确保它在InkCanvas之上 - var mainGrid = inkCanvas.Parent as Grid; - if (mainGrid != null) - { - mainGrid.Children.Add(resizeHandlesCanvas); - Panel.SetZIndex(resizeHandlesCanvas, 1000); // 确保在最上层 - } - } - - // 初始化图片工具栏引用 - if (borderImageSelectionControl == null) - { - borderImageSelectionControl = FindName("BorderImageSelectionControl") as Border; - } - - // 创建8个拖拽手柄 - CreateResizeHandles(); - } - - private void CreateResizeHandles() - { - resizeHandles.Clear(); - resizeHandlesCanvas.Children.Clear(); - - var directions = new[] - { - ResizeDirection.TopLeft, ResizeDirection.TopCenter, ResizeDirection.TopRight, - ResizeDirection.MiddleLeft, ResizeDirection.MiddleRight, - ResizeDirection.BottomLeft, ResizeDirection.BottomCenter, ResizeDirection.BottomRight - }; - - foreach (var direction in directions) - { - var handle = new Rectangle - { - Width = 12, - Height = 12, - Fill = Brushes.White, - Stroke = Brushes.DodgerBlue, - StrokeThickness = 2, - Cursor = GetCursorForDirection(direction), - Tag = direction - }; - - handle.MouseDown += ResizeHandle_MouseDown; - handle.MouseMove += ResizeHandle_MouseMove; - handle.MouseUp += ResizeHandle_MouseUp; - - resizeHandles.Add(handle); - resizeHandlesCanvas.Children.Add(handle); - } - } - - private Cursor GetCursorForDirection(ResizeDirection direction) - { - switch (direction) - { - case ResizeDirection.TopLeft: - case ResizeDirection.BottomRight: - return Cursors.SizeNWSE; - case ResizeDirection.TopRight: - case ResizeDirection.BottomLeft: - return Cursors.SizeNESW; - case ResizeDirection.TopCenter: - case ResizeDirection.BottomCenter: - return Cursors.SizeNS; - case ResizeDirection.MiddleLeft: - case ResizeDirection.MiddleRight: - return Cursors.SizeWE; - default: - return Cursors.Arrow; - } - } - - private void SelectUIElement(UIElement element) - { - if (selectedUIElement == element) return; - - // 取消之前的选择 - DeselectUIElement(); - - // 清除笔画选择 - if (inkCanvas.GetSelectedStrokes().Count > 0) - { - isProgramChangeStrokeSelection = true; - inkCanvas.Select(new StrokeCollection()); - isProgramChangeStrokeSelection = false; - } - - selectedUIElement = element; - - if (element != null) - { - // 初始化选择系统(如果还没有初始化) - if (resizeHandlesCanvas == null) - { - InitializeUIElementSelection(); - } - - // 显示拖拽手柄(所有UI元素都需要) - ShowResizeHandles(); - - // 根据元素类型显示特定的工具栏 - if (element is Image) - { - ShowImageToolbar(); - } - - // 监听元素的布局变化,以便实时更新手柄位置 - StartMonitoringElementChanges(element); - } - } - - private void DeselectUIElement() - { - // 停止监听之前选中元素的变化 - StopMonitoringElementChanges(); - - selectedUIElement = null; - HideResizeHandles(); - HideImageToolbar(); - } - - private void ShowResizeHandles() - { - if (selectedUIElement == null || resizeHandlesCanvas == null) return; - - var bounds = GetUIElementBounds(selectedUIElement); - UpdateResizeHandlesPosition(bounds); - resizeHandlesCanvas.Visibility = Visibility.Visible; - } - - private void HideResizeHandles() - { - if (resizeHandlesCanvas != null) - { - resizeHandlesCanvas.Visibility = Visibility.Collapsed; - } - } - - private void ShowImageToolbar() - { - if (selectedUIElement == null || borderImageSelectionControl == null) return; - - var bounds = GetUIElementBounds(selectedUIElement); - UpdateImageToolbarPosition(bounds); - borderImageSelectionControl.Visibility = Visibility.Visible; - } - - private void HideImageToolbar() - { - if (borderImageSelectionControl != null) - { - borderImageSelectionControl.Visibility = Visibility.Collapsed; - } - } - - private void UpdateImageToolbarPosition(Rect bounds) - { - if (borderImageSelectionControl == null) return; - - // 计算工具栏位置,类似于墨迹选择工具栏的逻辑 - var toolbarX = bounds.X + bounds.Width / 2 - BorderImageSelectionControlWidth / 2; - var toolbarY = bounds.Y + bounds.Height + 10; // 在图片下方10像素处 - - // 确保工具栏不会超出画布边界 - if (toolbarX < 0) toolbarX = 0; - if (toolbarX + BorderImageSelectionControlWidth > inkCanvas.ActualWidth) - toolbarX = inkCanvas.ActualWidth - BorderImageSelectionControlWidth; - - if (toolbarY + BorderImageSelectionControlHeight > inkCanvas.ActualHeight) - toolbarY = bounds.Y - BorderImageSelectionControlHeight - 10; // 如果下方空间不够,显示在上方 - - borderImageSelectionControl.Margin = new Thickness(toolbarX, toolbarY, 0, 0); - } - - private Rect GetUIElementBounds(UIElement element) - { - if (element is FrameworkElement fe) - { - var left = InkCanvas.GetLeft(element); - var top = InkCanvas.GetTop(element); - - if (double.IsNaN(left)) left = 0; - if (double.IsNaN(top)) top = 0; - - var width = fe.ActualWidth > 0 ? fe.ActualWidth : fe.Width; - var height = fe.ActualHeight > 0 ? fe.ActualHeight : fe.Height; - - // 检查是否有RenderTransform - if (fe.RenderTransform != null && fe.RenderTransform != Transform.Identity) - { - try - { - // 如果有变换,使用变换后的边界 - var transform = element.TransformToAncestor(inkCanvas); - var elementBounds = new Rect(0, 0, width, height); - var transformedBounds = transform.TransformBounds(elementBounds); - return transformedBounds; - } - catch - { - // 变换失败时回退到简单计算 - return new Rect(left, top, width, height); - } - } - else - { - // 没有变换时直接使用位置和大小 - return new Rect(left, top, width, height); - } - } - - return new Rect(0, 0, 0, 0); - } - - private void UpdateResizeHandlesPosition(Rect bounds) - { - if (resizeHandles.Count != 8) return; - - var handleSize = 12.0; - var halfHandle = handleSize / 2; - - // 计算手柄位置 - var positions = new[] - { - new Point(bounds.Left - halfHandle, bounds.Top - halfHandle), // TopLeft - new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Top - halfHandle), // TopCenter - new Point(bounds.Right - halfHandle, bounds.Top - halfHandle), // TopRight - new Point(bounds.Left - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleLeft - new Point(bounds.Right - halfHandle, bounds.Top + bounds.Height / 2 - halfHandle), // MiddleRight - new Point(bounds.Left - halfHandle, bounds.Bottom - halfHandle), // BottomLeft - new Point(bounds.Left + bounds.Width / 2 - halfHandle, bounds.Bottom - halfHandle), // BottomCenter - new Point(bounds.Right - halfHandle, bounds.Bottom - halfHandle) // BottomRight - }; - - for (int i = 0; i < resizeHandles.Count && i < positions.Length; i++) - { - System.Windows.Controls.Canvas.SetLeft(resizeHandles[i], positions[i].X); - System.Windows.Controls.Canvas.SetTop(resizeHandles[i], positions[i].Y); - } - } - - private void ResizeHandle_MouseDown(object sender, MouseButtonEventArgs e) - { - if (selectedUIElement == null) return; - - var handle = sender as Rectangle; - if (handle?.Tag is ResizeDirection direction) - { - isResizing = true; - currentResizeDirection = direction; - resizeStartPoint = e.GetPosition(inkCanvas); - originalElementBounds = GetUIElementBounds(selectedUIElement); - - handle.CaptureMouse(); - e.Handled = true; - } - } - - private void ResizeHandle_MouseMove(object sender, MouseEventArgs e) - { - if (!isResizing || selectedUIElement == null) return; - - var currentPoint = e.GetPosition(inkCanvas); - var deltaX = currentPoint.X - resizeStartPoint.X; - var deltaY = currentPoint.Y - resizeStartPoint.Y; - - ResizeUIElement(deltaX, deltaY); - e.Handled = true; - } - - private void ResizeHandle_MouseUp(object sender, MouseButtonEventArgs e) - { - if (isResizing) - { - isResizing = false; - currentResizeDirection = ResizeDirection.None; - - var handle = sender as Rectangle; - handle?.ReleaseMouseCapture(); - e.Handled = true; - } - } - - private void ResizeUIElement(double deltaX, double deltaY) - { - if (selectedUIElement == null) return; - - var newBounds = originalElementBounds; - const double minSize = 20.0; - - switch (currentResizeDirection) - { - case ResizeDirection.TopLeft: - var newWidth = originalElementBounds.Width - deltaX; - var newHeight = originalElementBounds.Height - deltaY; - if (newWidth >= minSize && newHeight >= minSize) - { - newBounds.X = originalElementBounds.X + deltaX; - newBounds.Y = originalElementBounds.Y + deltaY; - newBounds.Width = newWidth; - newBounds.Height = newHeight; - } - break; - - case ResizeDirection.TopCenter: - var newHeightTC = originalElementBounds.Height - deltaY; - if (newHeightTC >= minSize) - { - newBounds.Y = originalElementBounds.Y + deltaY; - newBounds.Height = newHeightTC; - } - break; - - case ResizeDirection.TopRight: - var newWidthTR = originalElementBounds.Width + deltaX; - var newHeightTR = originalElementBounds.Height - deltaY; - if (newWidthTR >= minSize && newHeightTR >= minSize) - { - newBounds.Y = originalElementBounds.Y + deltaY; - newBounds.Width = newWidthTR; - newBounds.Height = newHeightTR; - } - break; - - case ResizeDirection.MiddleLeft: - var newWidthML = originalElementBounds.Width - deltaX; - if (newWidthML >= minSize) - { - newBounds.X = originalElementBounds.X + deltaX; - newBounds.Width = newWidthML; - } - break; - - case ResizeDirection.MiddleRight: - var newWidthMR = originalElementBounds.Width + deltaX; - if (newWidthMR >= minSize) - { - newBounds.Width = newWidthMR; - } - break; - - case ResizeDirection.BottomLeft: - var newWidthBL = originalElementBounds.Width - deltaX; - var newHeightBL = originalElementBounds.Height + deltaY; - if (newWidthBL >= minSize && newHeightBL >= minSize) - { - newBounds.X = originalElementBounds.X + deltaX; - newBounds.Width = newWidthBL; - newBounds.Height = newHeightBL; - } - break; - - case ResizeDirection.BottomCenter: - var newHeightBC = originalElementBounds.Height + deltaY; - if (newHeightBC >= minSize) - { - newBounds.Height = newHeightBC; - } - break; - - case ResizeDirection.BottomRight: - var newWidthBR = originalElementBounds.Width + deltaX; - var newHeightBR = originalElementBounds.Height + deltaY; - if (newWidthBR >= minSize && newHeightBR >= minSize) - { - newBounds.Width = newWidthBR; - newBounds.Height = newHeightBR; - } - break; - } - - // 应用新的尺寸和位置 - ApplyUIElementBounds(selectedUIElement, newBounds); - - // 更新手柄位置 - UpdateResizeHandlesPosition(newBounds); - - // 如果是图片,也更新工具栏位置 - if (selectedUIElement is Image) - { - UpdateImageToolbarPosition(newBounds); - } - } - - private void ApplyUIElementBounds(UIElement element, Rect bounds) - { - if (element is FrameworkElement fe) - { - // 清除RenderTransform,避免与直接设置Width/Height冲突 - fe.RenderTransform = Transform.Identity; - - // 直接设置位置和大小 - InkCanvas.SetLeft(element, bounds.X); - InkCanvas.SetTop(element, bounds.Y); - fe.Width = bounds.Width; - fe.Height = bounds.Height; - } - } - - private void UIElement_MouseDown(object sender, MouseButtonEventArgs e) - { - if (inkCanvas.EditingMode == InkCanvasEditingMode.Select) - { - var element = sender as UIElement; - if (element != null) - { - // 切换到选择模式并选择这个元素 - inkCanvas.Select(new[] { element }); - SelectUIElement(element); - e.Handled = true; - } - } - } - - private void StartMonitoringElementChanges(UIElement element) - { - // 停止之前的监听 - StopMonitoringElementChanges(); - - if (element == null) return; - - // 记录初始边界 - lastElementBounds = GetUIElementBounds(element); - - // 创建定时器,定期检查元素边界变化 - elementUpdateTimer = new DispatcherTimer - { - Interval = TimeSpan.FromMilliseconds(16) // 约60FPS的更新频率 - }; - - elementUpdateTimer.Tick += (sender, e) => - { - if (selectedUIElement == null) - { - StopMonitoringElementChanges(); - return; - } - - var currentBounds = GetUIElementBounds(selectedUIElement); - - // 检查边界是否发生变化 - if (!AreRectsEqual(lastElementBounds, currentBounds)) - { - lastElementBounds = currentBounds; - - // 更新手柄位置 - UpdateResizeHandlesPosition(currentBounds); - - // 如果是图片,也更新工具栏位置 - if (selectedUIElement is Image) - { - UpdateImageToolbarPosition(currentBounds); - } - } - }; - - elementUpdateTimer.Start(); - } - - private void StopMonitoringElementChanges() - { - if (elementUpdateTimer != null) - { - elementUpdateTimer.Stop(); - elementUpdateTimer = null; - } - } - - private bool AreRectsEqual(Rect rect1, Rect rect2) - { - const double tolerance = 0.1; // 允许的误差范围 - return Math.Abs(rect1.X - rect2.X) < tolerance && - Math.Abs(rect1.Y - rect2.Y) < tolerance && - Math.Abs(rect1.Width - rect2.Width) < tolerance && - Math.Abs(rect1.Height - rect2.Height) < tolerance; - } - - #endregion - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs deleted file mode 100644 index 25e34f29..00000000 --- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs +++ /dev/null @@ -1,1946 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using Ink_Canvas.Helpers; -using Point = System.Windows.Point; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - private StrokeCollection newStrokes = new StrokeCollection(); - private List circles = new List(); - private const double LINE_STRAIGHTEN_THRESHOLD = 0.20; // 默认灵敏度阈值,与UI默认值对应 - - // 矩形参考线系统 - private List rectangleGuideLines = new List(); - 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 bool IsHorizontal { get; set; } - public bool IsVertical { get; set; } - - public RectangleGuideLine(Stroke stroke, Point start, Point end) - { - OriginalStroke = stroke; - StartPoint = start; - EndPoint = end; - CreatedTime = DateTime.Now; - - // 计算角度 - double deltaX = end.X - start.X; - double deltaY = end.Y - start.Y; - Angle = Math.Atan2(deltaY, deltaX); - - // 判断是否为水平或垂直线 - double angleDegrees = Math.Abs(Angle * 180.0 / Math.PI); - IsHorizontal = angleDegrees < RECTANGLE_ANGLE_THRESHOLD || angleDegrees > (180 - RECTANGLE_ANGLE_THRESHOLD); - IsVertical = Math.Abs(angleDegrees - 90) < RECTANGLE_ANGLE_THRESHOLD; - } - } - - private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e) { - // 标记是否进行了直线拉直 - bool wasStraightened = false; - - // 禁用原有的FitToCurve,使用新的高级贝塞尔曲线平滑 - if (Settings.Canvas.FitToCurve) drawingAttributes.FitToCurve = false; - - try { - inkCanvas.Opacity = 1; - - // 应用屏蔽压感功能 - 如果启用,所有笔画都使用统一粗细 - if (Settings.Canvas.DisablePressure) { - var uniformPoints = new StylusPointCollection(); - foreach (StylusPoint point in e.Stroke.StylusPoints) { - StylusPoint newPoint = new StylusPoint(point.X, point.Y, 0.5f); // 统一压感值为0.5 - uniformPoints.Add(newPoint); - } - 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; - break; - } - } - - // 如果是触屏输入,则应用模拟压感 - if (isTouchInput) { - switch (Settings.Canvas.InkStyle) { - case 1: - if (penType == 0) - try { - var stylusPoints = new StylusPointCollection(); - var n = e.Stroke.StylusPoints.Count - 1; - - for (var i = 0; i <= n; i++) { - var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(), - e.Stroke.StylusPoints[i].ToPoint(), - e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint()); - var point = new StylusPoint(); - if (speed >= 0.25) - point.PressureFactor = (float)(0.5 - 0.3 * (Math.Min(speed, 1.5) - 0.3) / 1.2); - else if (speed >= 0.05) - point.PressureFactor = (float)0.5; - else - point.PressureFactor = (float)(0.5 + 0.4 * (0.05 - speed) / 0.05); - - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - - e.Stroke.StylusPoints = stylusPoints; - } - catch { } - break; - case 0: - if (penType == 0) - try { - var stylusPoints = new StylusPointCollection(); - var n = e.Stroke.StylusPoints.Count - 1; - var pressure = 0.1; - var x = 10; - if (n == 1) return; - if (n >= x) { - for (var i = 0; i < n - x; i++) { - var point = new StylusPoint(); - - point.PressureFactor = (float)0.5; - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - - for (var i = n - x; i <= n; i++) { - var point = new StylusPoint(); - - point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure); - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - } - else { - for (var i = 0; i <= n; i++) { - var point = new StylusPoint(); - - point.PressureFactor = (float)(0.4 * (n - i) / n + pressure); - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - } - - e.Stroke.StylusPoints = stylusPoints; - } - catch { } - break; - } - } - } - - // Apply line straightening and endpoint snapping if ink-to-shape is enabled - - if (Settings.InkToShape.IsInkToShapeEnabled) { - // 检查是否启用了直线自动拉直功能 - if (Settings.Canvas.AutoStraightenLine && IsPotentialStraightLine(e.Stroke)) { - // Get start and end points of the stroke - Point startPoint = e.Stroke.StylusPoints[0].ToPoint(); - Point endPoint = e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint(); - - // 先完成所有直线判定,再考虑端点吸附 - // 读取实际的灵敏度设置值 - double sensitivity = Settings.InkToShape.LineStraightenSensitivity; - Debug.WriteLine($"当前灵敏度值: {sensitivity}"); - - // 判断是否应该拉直线条 - bool shouldStraighten = ShouldStraightenLine(e.Stroke); - - // 输出一些调试信息,帮助理解灵敏度设置的效果 - Debug.WriteLine($"LineStraightenSensitivity: {Settings.InkToShape.LineStraightenSensitivity}, ShouldStraighten: {shouldStraighten}"); - - // 只有当确定要拉直线条时,才检查端点吸附 - if (shouldStraighten && Settings.Canvas.LineEndpointSnapping) { - // 只有在启用了形状识别(矩形或三角形)时才执行端点吸附 - if (Settings.InkToShape.IsInkToShapeRectangle || Settings.InkToShape.IsInkToShapeTriangle) { - Point[] snappedPoints = GetSnappedEndpoints(startPoint, endPoint); - if (snappedPoints != null) { - startPoint = snappedPoints[0]; - endPoint = snappedPoints[1]; - } - } - } - - // 如果确定要拉直,则创建直线 - if (shouldStraighten) { - StylusPointCollection straightLinePoints = CreateStraightLine(startPoint, endPoint); - Stroke straightStroke = new Stroke(straightLinePoints) { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - - // Replace the original stroke with the straightened one - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(e.Stroke); - inkCanvas.Strokes.Add(straightStroke); - _currentCommitType = CommitReason.UserInput; - - // We can't modify e.Stroke directly, but we need to update newStrokes - // to ensure proper shape recognition for the straightened line - if (newStrokes.Contains(e.Stroke)) { - newStrokes.Remove(e.Stroke); - newStrokes.Add(straightStroke); - } - - wasStraightened = true; // 标记已进行直线拉直 - } - } - } - - if (Settings.InkToShape.IsInkToShapeEnabled && !Environment.Is64BitProcess) { - void InkToShapeProcess() { - try { - newStrokes.Add(e.Stroke); - if (newStrokes.Count > 4) newStrokes.RemoveAt(0); - for (var i = 0; i < newStrokes.Count; i++) - if (!inkCanvas.Strokes.Contains(newStrokes[i])) - newStrokes.RemoveAt(i--); - - for (var i = 0; i < circles.Count; i++) - if (!inkCanvas.Strokes.Contains(circles[i].Stroke)) - circles.RemoveAt(i); - - // 处理矩形参考线系统 - ProcessRectangleGuideLines(e.Stroke); - - var strokeReco = new StrokeCollection(); - var result = InkRecognizeHelper.RecognizeShape(newStrokes); - for (var i = newStrokes.Count - 1; i >= 0; i--) { - strokeReco.Add(newStrokes[i]); - var newResult = InkRecognizeHelper.RecognizeShape(strokeReco); - if (newResult.InkDrawingNode.GetShapeName() == "Circle" || - newResult.InkDrawingNode.GetShapeName() == "Ellipse") { - result = newResult; - break; - } - //Label.Visibility = Visibility.Visible; - //Label.Content = circles.Count.ToString() + "\n" + newResult.InkDrawingNode.GetShapeName(); - } - - if (result.InkDrawingNode.GetShapeName() == "Circle" && - Settings.InkToShape.IsInkToShapeRounded) { - var shape = result.InkDrawingNode.GetShape(); - if (shape.Width > 75) { - foreach (var circle in circles) - //判断是否画同心圆 - if (Math.Abs(result.Centroid.X - circle.Centroid.X) / shape.Width < 0.12 && - Math.Abs(result.Centroid.Y - circle.Centroid.Y) / shape.Width < 0.12) { - result.Centroid = circle.Centroid; - break; - } - else { - var d = (result.Centroid.X - circle.Centroid.X) * - (result.Centroid.X - circle.Centroid.X) + - (result.Centroid.Y - circle.Centroid.Y) * - (result.Centroid.Y - circle.Centroid.Y); - d = Math.Sqrt(d); - //判断是否画外切圆 - var x = shape.Width / 2.0 + circle.R - d; - if (Math.Abs(x) / shape.Width < 0.1) { - var sinTheta = (result.Centroid.Y - circle.Centroid.Y) / d; - var cosTheta = (result.Centroid.X - circle.Centroid.X) / d; - var newX = result.Centroid.X + x * cosTheta; - var newY = result.Centroid.Y + x * sinTheta; - result.Centroid = new Point(newX, newY); - } - - //判断是否画外切圆 - x = Math.Abs(circle.R - shape.Width / 2.0) - d; - if (Math.Abs(x) / shape.Width < 0.1) { - var sinTheta = (result.Centroid.Y - circle.Centroid.Y) / d; - var cosTheta = (result.Centroid.X - circle.Centroid.X) / d; - var newX = result.Centroid.X + x * cosTheta; - var newY = result.Centroid.Y + x * sinTheta; - result.Centroid = new Point(newX, newY); - } - } - - var iniP = new Point(result.Centroid.X - shape.Width / 2, - result.Centroid.Y - shape.Height / 2); - var endP = new Point(result.Centroid.X + shape.Width / 2, - result.Centroid.Y + shape.Height / 2); - var pointList = GenerateEllipseGeometry(iniP, endP); - var point = new StylusPointCollection(pointList); - var stroke = new Stroke(point) { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - circles.Add(new Circle(result.Centroid, shape.Width / 2.0, stroke)); - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); - inkCanvas.Strokes.Add(stroke); - _currentCommitType = CommitReason.UserInput; - newStrokes = new StrokeCollection(); - } - } - else if (result.InkDrawingNode.GetShapeName().Contains("Ellipse") && - Settings.InkToShape.IsInkToShapeRounded) { - var shape = result.InkDrawingNode.GetShape(); - //var shape1 = result.InkDrawingNode.GetShape(); - //shape1.Fill = Brushes.Gray; - //Canvas.Children.Add(shape1); - var p = result.InkDrawingNode.HotPoints; - var a = GetDistance(p[0], p[2]) / 2; //长半轴 - var b = GetDistance(p[1], p[3]) / 2; //短半轴 - if (a < b) { - var t = a; - a = b; - b = t; - } - - result.Centroid = new Point((p[0].X + p[2].X) / 2, (p[0].Y + p[2].Y) / 2); - var needRotation = true; - - if (shape.Width > 75 || (shape.Height > 75 && p.Count == 4)) { - var iniP = new Point(result.Centroid.X - shape.Width / 2, - result.Centroid.Y - shape.Height / 2); - var endP = new Point(result.Centroid.X + shape.Width / 2, - result.Centroid.Y + shape.Height / 2); - - foreach (var circle in circles) - //判断是否画同心椭圆 - if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 && - Math.Abs(result.Centroid.Y - circle.Centroid.Y) / a < 0.2) { - result.Centroid = circle.Centroid; - iniP = new Point(result.Centroid.X - shape.Width / 2, - result.Centroid.Y - shape.Height / 2); - endP = new Point(result.Centroid.X + shape.Width / 2, - result.Centroid.Y + shape.Height / 2); - - //再判断是否与圆相切 - if (Math.Abs(a - circle.R) / a < 0.2) { - if (shape.Width >= shape.Height) { - iniP.X = result.Centroid.X - circle.R; - endP.X = result.Centroid.X + circle.R; - iniP.Y = result.Centroid.Y - b; - endP.Y = result.Centroid.Y + b; - } - else { - iniP.Y = result.Centroid.Y - circle.R; - endP.Y = result.Centroid.Y + circle.R; - iniP.X = result.Centroid.X - a; - endP.X = result.Centroid.X + a; - } - } - - break; - } - else if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2) { - var sinTheta = Math.Abs(circle.Centroid.Y - result.Centroid.Y) / - circle.R; - var cosTheta = Math.Sqrt(1 - sinTheta * sinTheta); - var newA = circle.R * cosTheta; - if (circle.R * sinTheta / circle.R < 0.9 && a / b > 2 && - Math.Abs(newA - a) / newA < 0.3) { - iniP.X = circle.Centroid.X - newA; - endP.X = circle.Centroid.X + newA; - iniP.Y = result.Centroid.Y - newA / 5; - endP.Y = result.Centroid.Y + newA / 5; - - var topB = endP.Y - iniP.Y; - - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); - newStrokes = new StrokeCollection(); - - var _pointList = GenerateEllipseGeometry(iniP, endP, false); - var _point = new StylusPointCollection(_pointList); - var _stroke = new Stroke(_point) { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - var _dashedLineStroke = - GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, false); - var strokes = new StrokeCollection { - _stroke, - _dashedLineStroke - }; - inkCanvas.Strokes.Add(strokes); - _currentCommitType = CommitReason.UserInput; - return; - } - } - else if (Math.Abs(result.Centroid.Y - circle.Centroid.Y) / a < 0.2) { - var cosTheta = Math.Abs(circle.Centroid.X - result.Centroid.X) / - circle.R; - var sinTheta = Math.Sqrt(1 - cosTheta * cosTheta); - var newA = circle.R * sinTheta; - if (circle.R * sinTheta / circle.R < 0.9 && a / b > 2 && - Math.Abs(newA - a) / newA < 0.3) { - iniP.X = result.Centroid.X - newA / 5; - endP.X = result.Centroid.X + newA / 5; - iniP.Y = circle.Centroid.Y - newA; - endP.Y = circle.Centroid.Y + newA; - needRotation = false; - } - } - - //纠正垂直与水平关系 - var newPoints = FixPointsDirection(p[0], p[2]); - p[0] = newPoints[0]; - p[2] = newPoints[1]; - newPoints = FixPointsDirection(p[1], p[3]); - p[1] = newPoints[0]; - p[3] = newPoints[1]; - - var pointList = GenerateEllipseGeometry(iniP, endP); - var point = new StylusPointCollection(pointList); - var stroke = new Stroke(point) { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - - if (needRotation) { - var m = new Matrix(); - var fe = e.Source as FrameworkElement; - var tanTheta = (p[2].Y - p[0].Y) / (p[2].X - p[0].X); - var theta = Math.Atan(tanTheta); - m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y); - stroke.Transform(m, false); - } - - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); - inkCanvas.Strokes.Add(stroke); - _currentCommitType = CommitReason.UserInput; - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - newStrokes = new StrokeCollection(); - } - } - else if (result.InkDrawingNode.GetShapeName().Contains("Triangle") && - Settings.InkToShape.IsInkToShapeTriangle) { - var shape = result.InkDrawingNode.GetShape(); - var p = result.InkDrawingNode.HotPoints; - if ((Math.Max(Math.Max(p[0].X, p[1].X), p[2].X) - - Math.Min(Math.Min(p[0].X, p[1].X), p[2].X) >= 100 || - Math.Max(Math.Max(p[0].Y, p[1].Y), p[2].Y) - - Math.Min(Math.Min(p[0].Y, p[1].Y), p[2].Y) >= 100) && - result.InkDrawingNode.HotPoints.Count == 3) { - //纠正垂直与水平关系 - var newPoints = FixPointsDirection(p[0], p[1]); - p[0] = newPoints[0]; - p[1] = newPoints[1]; - newPoints = FixPointsDirection(p[0], p[2]); - p[0] = newPoints[0]; - p[2] = newPoints[1]; - newPoints = FixPointsDirection(p[1], p[2]); - p[1] = newPoints[0]; - p[2] = newPoints[1]; - - var pointList = p.ToList(); - //pointList.Add(p[0]); - var point = new StylusPointCollection(pointList); - var stroke = new Stroke(GenerateFakePressureTriangle(point)) { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); - inkCanvas.Strokes.Add(stroke); - _currentCommitType = CommitReason.UserInput; - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - newStrokes = new StrokeCollection(); - } - } - else if ((result.InkDrawingNode.GetShapeName().Contains("Rectangle") || - result.InkDrawingNode.GetShapeName().Contains("Diamond") || - result.InkDrawingNode.GetShapeName().Contains("Parallelogram") || - result.InkDrawingNode.GetShapeName().Contains("Square") || - result.InkDrawingNode.GetShapeName().Contains("Trapezoid")) && - Settings.InkToShape.IsInkToShapeRectangle) { - var shape = result.InkDrawingNode.GetShape(); - var p = result.InkDrawingNode.HotPoints; - if ((Math.Max(Math.Max(Math.Max(p[0].X, p[1].X), p[2].X), p[3].X) - - Math.Min(Math.Min(Math.Min(p[0].X, p[1].X), p[2].X), p[3].X) >= 100 || - Math.Max(Math.Max(Math.Max(p[0].Y, p[1].Y), p[2].Y), p[3].Y) - - Math.Min(Math.Min(Math.Min(p[0].Y, p[1].Y), p[2].Y), p[3].Y) >= 100) && - result.InkDrawingNode.HotPoints.Count == 4) { - //纠正垂直与水平关系 - var newPoints = FixPointsDirection(p[0], p[1]); - p[0] = newPoints[0]; - p[1] = newPoints[1]; - newPoints = FixPointsDirection(p[1], p[2]); - p[1] = newPoints[0]; - p[2] = newPoints[1]; - newPoints = FixPointsDirection(p[2], p[3]); - p[2] = newPoints[0]; - p[3] = newPoints[1]; - newPoints = FixPointsDirection(p[3], p[0]); - p[3] = newPoints[0]; - p[0] = newPoints[1]; - - var pointList = p.ToList(); - pointList.Add(p[0]); - var point = new StylusPointCollection(pointList); - var stroke = new Stroke(GenerateFakePressureRectangle(point)) { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); - inkCanvas.Strokes.Add(stroke); - _currentCommitType = CommitReason.UserInput; - GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; - newStrokes = new StrokeCollection(); - } - } - } - catch { } - } - - InkToShapeProcess(); - } - - foreach (var stylusPoint in e.Stroke.StylusPoints) - //LogHelper.WriteLogToFile(stylusPoint.PressureFactor.ToString(), LogHelper.LogType.Info); - // 检查是否是压感笔书写 - //if (stylusPoint.PressureFactor != 0.5 && stylusPoint.PressureFactor != 0) - if ((stylusPoint.PressureFactor > 0.501 || stylusPoint.PressureFactor < 0.5) && - stylusPoint.PressureFactor != 0) - return; - - try { - if (e.Stroke.StylusPoints.Count > 3) { - var random = new Random(); - var _speed = GetPointSpeed( - e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint(), - e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint(), - e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint()); - - RandWindow.randSeed = (int)(_speed * 100000 * 1000); - } - } - catch { } - - switch (Settings.Canvas.InkStyle) { - case 1: - if (penType == 0) - try { - var stylusPoints = new StylusPointCollection(); - var n = e.Stroke.StylusPoints.Count - 1; - var s = ""; - - for (var i = 0; i <= n; i++) { - var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(), - e.Stroke.StylusPoints[i].ToPoint(), - e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint()); - s += speed + "\t"; - var point = new StylusPoint(); - if (speed >= 0.25) - point.PressureFactor = (float)(0.5 - 0.3 * (Math.Min(speed, 1.5) - 0.3) / 1.2); - else if (speed >= 0.05) - point.PressureFactor = (float)0.5; - else - point.PressureFactor = (float)(0.5 + 0.4 * (0.05 - speed) / 0.05); - - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - - e.Stroke.StylusPoints = stylusPoints; - } - catch { } - - break; - case 0: - if (penType == 0) - try { - var stylusPoints = new StylusPointCollection(); - var n = e.Stroke.StylusPoints.Count - 1; - var pressure = 0.1; - var x = 10; - if (n == 1) return; - if (n >= x) { - for (var i = 0; i < n - x; i++) { - var point = new StylusPoint(); - - point.PressureFactor = (float)0.5; - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - - for (var i = n - x; i <= n; i++) { - var point = new StylusPoint(); - - point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure); - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - } - else { - for (var i = 0; i <= n; i++) { - var point = new StylusPoint(); - - point.PressureFactor = (float)(0.4 * (n - i) / n + pressure); - point.X = e.Stroke.StylusPoints[i].X; - point.Y = e.Stroke.StylusPoints[i].Y; - stylusPoints.Add(point); - } - } - - e.Stroke.StylusPoints = stylusPoints; - } - catch { } - - break; - } - } - catch { } - - // 应用高级贝塞尔曲线平滑(仅在未进行直线拉直时) - if (Settings.Canvas.UseAdvancedBezierSmoothing && !wasStraightened) - { - try - { - // 检查原始笔画是否仍然存在于画布中 - if (inkCanvas.Strokes.Contains(e.Stroke)) - { - // 使用新的异步墨迹平滑管理器 - if (Settings.Canvas.UseAsyncInkSmoothing && _inkSmoothingManager != null) - { - // 异步处理 - _ = ProcessStrokeAsync(e.Stroke); - } - else - { - // 同步处理(向后兼容) - var smoothedStroke = _inkSmoothingManager?.SmoothStroke(e.Stroke) ?? e.Stroke; - - if (smoothedStroke != e.Stroke) - { - // 替换原始笔画 - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(e.Stroke); - inkCanvas.Strokes.Add(smoothedStroke); - _currentCommitType = CommitReason.UserInput; - } - } - } - } - catch (Exception ex) - { - // 如果高级平滑失败,回退到原始笔画 - Debug.WriteLine($"高级贝塞尔曲线平滑失败: {ex.Message}"); - } - } - else if (Settings.Canvas.FitToCurve && !wasStraightened) - { - drawingAttributes.FitToCurve = true; - } - } - - /// - /// 异步处理笔画平滑 - /// - private async Task ProcessStrokeAsync(Stroke originalStroke) - { - try - { - await _inkSmoothingManager.SmoothStrokeAsync(originalStroke, (original, smoothed) => - { - // 在UI线程上执行笔画替换 - if (inkCanvas.Strokes.Contains(original) && smoothed != original) - { - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - inkCanvas.Strokes.Remove(original); - inkCanvas.Strokes.Add(smoothed); - _currentCommitType = CommitReason.UserInput; - } - }); - } - catch (Exception ex) - { - Debug.WriteLine($"异步墨迹平滑失败: {ex.Message}"); - } - } - - // New method: Checks if a stroke is potentially a straight line - private bool IsPotentialStraightLine(Stroke stroke) { - // 确保有足够的点来进行线条分析 - if (stroke.StylusPoints.Count < 5) - return false; - - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - double lineLength = GetDistance(start, end); - // 分辨率自适应阈值 - double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale(); - // 线条必须足够长才考虑拉直,使用自适应阈值 - if (lineLength < adaptiveThreshold) - return false; - - // 新增:检查墨迹复杂度,避免将复杂图形拉直 - if (IsComplexShape(stroke)) - return false; - - // 新增:检查是否为明显的曲线 - if (IsObviousCurve(stroke)) - return false; - - // 获取用户设置的灵敏度值,确保使用正确的设置 - double sensitivity = Settings.InkToShape.LineStraightenSensitivity; - - // 输出当前灵敏度值(调试用) - Debug.WriteLine($"IsPotentialStraightLine - sensitivity: {sensitivity}, length: {lineLength}"); - - // 根据灵敏度调整快速检查阈值 - double quickThreshold; - - // 如果灵敏度超过1.0,使用更宽松的快速检查标准 - if (sensitivity > 1.0) { - // 高灵敏度模式 - 使用更宽松的阈值 - quickThreshold = Math.Min(0.2 + (sensitivity - 1.0) * 0.3, 0.5); // 映射到0.2-0.5范围 - } else { - // 常规灵敏度模式 - quickThreshold = Math.Min(sensitivity * 1.5, 0.20); - } - - Debug.WriteLine($"使用快速检查阈值: {quickThreshold}"); - - // 快速检查:计算几个关键点与直线的距离 - 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; - - 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}"); - - // 如果灵敏度超过1.5,则即使有一个点满足条件也认为可能是直线 - if (sensitivity > 1.5) { - // 超高灵敏度模式:只要有一个关键点偏差小,就认为可能是直线 - if (quarterDeviation <= quickRelativeThreshold || - midDeviation <= quickRelativeThreshold || - threeQuarterDeviation <= quickRelativeThreshold) { - return true; - } - } else { - // 常规判断:如果任一点偏离太大,直接排除 - if (quarterDeviation > quickRelativeThreshold || - midDeviation > quickRelativeThreshold || - threeQuarterDeviation > quickRelativeThreshold) { - return false; - } - } - } - - return true; - } - - /// - /// 检查墨迹是否为复杂形状(如一团墨迹、涂鸦等) - /// - private bool IsComplexShape(Stroke stroke) - { - if (stroke.StylusPoints.Count < 10) return false; - - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - double lineLength = GetDistance(start, end); - - // 计算墨迹的实际路径长度 - double actualLength = 0; - for (int i = 1; i < stroke.StylusPoints.Count; i++) - { - Point p1 = stroke.StylusPoints[i - 1].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - actualLength += GetDistance(p1, p2); - } - - // 如果实际路径长度远大于直线距离,说明是复杂形状 - double complexityRatio = actualLength / Math.Max(lineLength, 1); - if (complexityRatio > 2.5) // 实际路径是直线距离的2.5倍以上 - { - Debug.WriteLine($"检测到复杂形状:复杂度比率 = {complexityRatio:F2}"); - return true; - } - - // 检查方向变化次数 - int directionChanges = CountDirectionChanges(stroke); - int maxAllowedChanges = Math.Max(3, stroke.StylusPoints.Count / 20); // 动态阈值 - if (directionChanges > maxAllowedChanges) - { - Debug.WriteLine($"检测到复杂形状:方向变化次数 = {directionChanges},阈值 = {maxAllowedChanges}"); - return true; - } - - // 检查是否有明显的回环或重叠 - if (HasSignificantLoops(stroke)) - { - Debug.WriteLine("检测到复杂形状:存在明显回环"); - return true; - } - - return false; - } - - /// - /// 检查是否为明显的曲线(如圆弧、抛物线等) - /// - private bool IsObviousCurve(Stroke stroke) - { - if (stroke.StylusPoints.Count < 10) return false; - - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - double lineLength = GetDistance(start, end); - - // 检查曲率一致性 - if (HasConsistentCurvature(stroke)) - { - Debug.WriteLine("检测到明显曲线:曲率一致"); - return true; - } - - // 检查中点偏移(对圆弧特别有效) - int midIndex = stroke.StylusPoints.Count / 2; - Point midPoint = stroke.StylusPoints[midIndex].ToPoint(); - double midDeviation = DistanceFromLineToPoint(start, end, midPoint); - - // 如果中点偏移超过线长的15%,且偏移方向一致,可能是圆弧 - if (midDeviation > lineLength * 0.15) - { - // 检查偏移方向的一致性 - if (IsConsistentArcDirection(stroke)) - { - Debug.WriteLine($"检测到明显曲线:中点偏移 = {midDeviation:F2},线长 = {lineLength:F2}"); - return true; - } - } - - return false; - } - - /// - /// 计算方向变化次数 - /// - private int CountDirectionChanges(Stroke stroke) - { - if (stroke.StylusPoints.Count < 3) return 0; - - int changes = 0; - double lastAngle = 0; - bool hasLastAngle = false; - - for (int i = 1; i < stroke.StylusPoints.Count - 1; i++) - { - Point p1 = stroke.StylusPoints[i - 1].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - Point p3 = stroke.StylusPoints[i + 1].ToPoint(); - - // 计算角度变化 - double angle1 = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); - double angle2 = Math.Atan2(p3.Y - p2.Y, p3.X - p2.X); - double angleDiff = Math.Abs(angle2 - angle1); - - // 处理角度跨越问题 - if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; - - // 如果角度变化超过30度,认为是方向变化 - if (angleDiff > Math.PI / 6) // 30度 - { - if (hasLastAngle && Math.Abs(angleDiff - lastAngle) > Math.PI / 12) // 15度 - { - changes++; - } - lastAngle = angleDiff; - hasLastAngle = true; - } - } - - return changes; - } - - /// - /// 检查是否有明显的回环 - /// - 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; - } - - /// - /// 检查曲率是否一致(用于识别圆弧等规则曲线) - /// - private bool HasConsistentCurvature(Stroke stroke) - { - if (stroke.StylusPoints.Count < 15) return false; - - List curvatures = new List(); - - // 计算每个点的曲率 - for (int i = 2; i < stroke.StylusPoints.Count - 2; i++) - { - Point p1 = stroke.StylusPoints[i - 2].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - Point p3 = stroke.StylusPoints[i + 2].ToPoint(); - - double curvature = CalculateCurvature(p1, p2, p3); - if (!double.IsNaN(curvature) && !double.IsInfinity(curvature)) - { - curvatures.Add(Math.Abs(curvature)); - } - } - - if (curvatures.Count < 5) return false; - - // 计算曲率的标准差 - double avgCurvature = curvatures.Average(); - double variance = curvatures.Select(c => Math.Pow(c - avgCurvature, 2)).Average(); - double stdDev = Math.Sqrt(variance); - - // 如果曲率变化很小且平均曲率不为零,可能是规则曲线 - return avgCurvature > 0.001 && stdDev / avgCurvature < 0.5; - } - - /// - /// 检查圆弧方向是否一致 - /// - private bool IsConsistentArcDirection(Stroke stroke) - { - if (stroke.StylusPoints.Count < 10) return false; - - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - - int positiveDeviations = 0; - int negativeDeviations = 0; - - // 检查多个点相对于直线的偏移方向 - for (int i = 1; i < stroke.StylusPoints.Count - 1; i += Math.Max(1, stroke.StylusPoints.Count / 10)) - { - Point p = stroke.StylusPoints[i].ToPoint(); - double signedDistance = SignedDistanceFromLineToPoint(start, end, p); - - if (Math.Abs(signedDistance) > 5) // 忽略很小的偏移 - { - if (signedDistance > 0) positiveDeviations++; - else negativeDeviations++; - } - } - - // 如果大部分点都在直线的同一侧,说明是一致的弧形 - int totalSignificantDeviations = positiveDeviations + negativeDeviations; - if (totalSignificantDeviations < 3) return false; - - double consistency = Math.Max(positiveDeviations, negativeDeviations) / (double)totalSignificantDeviations; - return consistency > 0.8; // 80%的点在同一侧 - } - - /// - /// 计算三点的曲率 - /// - private double CalculateCurvature(Point p1, Point p2, Point p3) - { - // 使用三点计算曲率的公式 - double a = GetDistance(p1, p2); - double b = GetDistance(p2, p3); - double c = GetDistance(p1, p3); - - if (a == 0 || b == 0 || c == 0) return 0; - - // 使用海伦公式计算面积 - double s = (a + b + c) / 2; - double area = Math.Sqrt(s * (s - a) * (s - b) * (s - c)); - - // 曲率 = 4 * 面积 / (a * b * c) - return 4 * area / (a * b * c); - } - - /// - /// 计算点到直线的有符号距离 - /// - private double SignedDistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point) - { - // 使用叉积计算有符号距离 - double dx = lineEnd.X - lineStart.X; - double dy = lineEnd.Y - lineStart.Y; - double lineLength = Math.Sqrt(dx * dx + dy * dy); - - if (lineLength == 0) return 0; - - return ((lineEnd.Y - lineStart.Y) * point.X - (lineEnd.X - lineStart.X) * point.Y + - lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength; - } - - // New method: Determines if a stroke should be straightened into a line - private bool ShouldStraightenLine(Stroke stroke) { - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - double maxDeviation = 0; - double lineLength = GetDistance(start, end); - // 分辨率自适应阈值 - double adaptiveThreshold = Settings.Canvas.AutoStraightenLineThreshold * GetResolutionScale(); - // 如果线条太短,不进行拉直处理,使用自适应阈值 - if (lineLength < adaptiveThreshold) { - Debug.WriteLine($"线条太短: {lineLength} < {adaptiveThreshold}"); - return false; - } - - // 新增:再次检查复杂度(双重保险) - if (IsComplexShape(stroke)) - { - Debug.WriteLine("拒绝拉直:检测到复杂形状"); - return false; - } - - // 新增:检查线条的直线度评分 - double straightnessScore = CalculateStraightnessScore(stroke); - double minStraightnessThreshold = 0.7; // 最低直线度要求 - - if (straightnessScore < minStraightnessThreshold) - { - Debug.WriteLine($"拒绝拉直:直线度评分过低 {straightnessScore:F3} < {minStraightnessThreshold}"); - return false; - } - - // 获取用户设置的灵敏度值,确保使用正确的值进行后续判断 - double sensitivity = Settings.InkToShape.LineStraightenSensitivity; - - // 输出详细的调试信息 - Debug.WriteLine($"ShouldStraightenLine - sensitivity: {sensitivity}, length: {lineLength}"); - - // 临时:显示调试消息框 - // MessageBox.Show($"灵敏度值: {sensitivity}", "调试信息"); - - // 计算点与直线的偏差 - double totalDeviation = 0; - int pointCount = 0; - - // 检查是否启用了高精度直线拉直 - bool useHighPrecision = Settings.Canvas.HighPrecisionLineStraighten; - - if (useHighPrecision) { - Debug.WriteLine("使用高精度直线拉直模式"); - - // 高精度模式:每隔10像素取一个计数点 - double strokeLength = 0; - double sampleInterval = 10.0; // 10像素间隔 - - // 计算笔画的总长度,用于后续采样 - for (int i = 1; i < stroke.StylusPoints.Count; i++) { - Point p1 = stroke.StylusPoints[i-1].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - strokeLength += GetDistance(p1, p2); - } - - // 如果笔画太短,直接使用所有点 - if (strokeLength < sampleInterval * 5) { - foreach (StylusPoint sp in stroke.StylusPoints) { - Point p = sp.ToPoint(); - double deviation = DistanceFromLineToPoint(start, end, p); - maxDeviation = Math.Max(maxDeviation, deviation); - totalDeviation += deviation; - pointCount++; - } - } else { - // 使用等距采样点 - double currentLength = 0; - double nextSampleAt = 0; - - // 总是包含起点 - Point lastPoint = start; - double deviation = DistanceFromLineToPoint(start, end, lastPoint); - maxDeviation = Math.Max(maxDeviation, deviation); - totalDeviation += deviation; - pointCount++; - - // 采样中间点 - for (int i = 1; i < stroke.StylusPoints.Count; i++) { - Point currentPoint = stroke.StylusPoints[i].ToPoint(); - double segmentLength = GetDistance(lastPoint, currentPoint); - - // 如果这段线段跨越了下一个采样点 - while (currentLength + segmentLength >= nextSampleAt) { - // 计算采样点在线段上的位置 - double t = (nextSampleAt - currentLength) / segmentLength; - Point samplePoint = new Point( - lastPoint.X + t * (currentPoint.X - lastPoint.X), - lastPoint.Y + t * (currentPoint.Y - lastPoint.Y) - ); - - // 计算采样点的偏差 - deviation = DistanceFromLineToPoint(start, end, samplePoint); - maxDeviation = Math.Max(maxDeviation, deviation); - totalDeviation += deviation; - pointCount++; - - // 设置下一个采样点位置 - nextSampleAt += sampleInterval; - - // 防止无限循环 - if (nextSampleAt > strokeLength) break; - } - - currentLength += segmentLength; - lastPoint = currentPoint; - } - - // 总是包含终点 - deviation = DistanceFromLineToPoint(start, end, end); - maxDeviation = Math.Max(maxDeviation, deviation); - totalDeviation += deviation; - pointCount++; - } - } else { - // 原始模式:使用所有点 - foreach (StylusPoint sp in stroke.StylusPoints) { - Point p = sp.ToPoint(); - double deviation = DistanceFromLineToPoint(start, end, p); - maxDeviation = Math.Max(maxDeviation, deviation); - totalDeviation += deviation; - pointCount++; - } - } - - // 计算平均偏差 - double avgDeviation = totalDeviation / pointCount; - - // 更详细的调试信息 - Debug.WriteLine($"Max deviation: {maxDeviation}, Avg: {avgDeviation}, Threshold: {sensitivity * lineLength}, Points: {pointCount}"); - - // 支持更广泛的灵敏度范围 (0.05-2.0) - - // 如果灵敏度高于1.0,使用更宽松的判断标准 - if (sensitivity > 1.0) { - // 高灵敏度模式 - 允许更大的偏差 - double adjustedSensitivity = 0.5 + (sensitivity - 1.0) * 1.5; // 映射到0.5-2.0范围 - - // 只判断平均偏差和相对偏差 - if (maxDeviation / lineLength < adjustedSensitivity && avgDeviation < lineLength * 0.1 * adjustedSensitivity) { - Debug.WriteLine("接受拉直 (高灵敏度模式)"); - return true; - } - - Debug.WriteLine("拒绝拉直 (高灵敏度模式)"); - return false; - } - // 否则使用常规判断标准 - - // 检查点分布的一致性 - 如果有些点偏离很大而其他点很接近直线,表明线条有明显弯曲 - double deviationVariance = 0; - - // 使用相同的高精度/原始模式来计算方差 - if (useHighPrecision) { - // 高精度模式:重新采样计算方差 - double strokeLength = 0; - double sampleInterval = 10.0; // 10像素间隔 - - // 计算笔画的总长度,用于后续采样 - for (int i = 1; i < stroke.StylusPoints.Count; i++) { - Point p1 = stroke.StylusPoints[i-1].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - strokeLength += GetDistance(p1, p2); - } - - // 如果笔画太短,直接使用所有点 - if (strokeLength < sampleInterval * 5) { - foreach (StylusPoint sp in stroke.StylusPoints) { - Point p = sp.ToPoint(); - double deviation = DistanceFromLineToPoint(start, end, p); - deviationVariance += Math.Pow(deviation - avgDeviation, 2); - } - } else { - // 使用等距采样点 - double currentLength = 0; - double nextSampleAt = 0; - Point lastPoint = start; - - // 起点方差 - double deviation = DistanceFromLineToPoint(start, end, lastPoint); - deviationVariance += Math.Pow(deviation - avgDeviation, 2); - - // 采样中间点 - for (int i = 1; i < stroke.StylusPoints.Count; i++) { - Point currentPoint = stroke.StylusPoints[i].ToPoint(); - double segmentLength = GetDistance(lastPoint, currentPoint); - - // 如果这段线段跨越了下一个采样点 - while (currentLength + segmentLength >= nextSampleAt) { - // 计算采样点在线段上的位置 - double t = (nextSampleAt - currentLength) / segmentLength; - Point samplePoint = new Point( - lastPoint.X + t * (currentPoint.X - lastPoint.X), - lastPoint.Y + t * (currentPoint.Y - lastPoint.Y) - ); - - // 计算采样点的方差 - deviation = DistanceFromLineToPoint(start, end, samplePoint); - deviationVariance += Math.Pow(deviation - avgDeviation, 2); - - // 设置下一个采样点位置 - nextSampleAt += sampleInterval; - - // 防止无限循环 - if (nextSampleAt > strokeLength) break; - } - - currentLength += segmentLength; - lastPoint = currentPoint; - } - - // 终点方差 - deviation = DistanceFromLineToPoint(start, end, end); - deviationVariance += Math.Pow(deviation - avgDeviation, 2); - } - } else { - // 原始模式:使用所有点计算方差 - foreach (StylusPoint sp in stroke.StylusPoints) { - Point p = sp.ToPoint(); - double deviation = DistanceFromLineToPoint(start, end, p); - deviationVariance += Math.Pow(deviation - avgDeviation, 2); - } - } - - deviationVariance /= pointCount; - - // 输出更多调试信息 - Debug.WriteLine($"Deviation variance: {deviationVariance}, Threshold: {sensitivity * lineLength * 0.05}"); - - // 如果最大偏差超过线长的阈值比例,或者偏差方差较大(表示不均匀弯曲),则不拉直 - // 灵敏度越大,容许的偏差越大,更容易将线条识别为直线 - if ((maxDeviation / lineLength) > sensitivity) { - Debug.WriteLine("拒绝拉直:最大偏差过大"); - return false; - } - - // 如果偏差方差大,说明线条弯曲不均匀 - // 灵敏度越大,容许的偏差方差越大 - if (deviationVariance > (sensitivity * lineLength * 0.05)) { - Debug.WriteLine("拒绝拉直:偏差方差过大"); - return false; - } - - // 检查中点偏离情况 - 针对弧形线条特别有效 - if (stroke.StylusPoints.Count > 10) { - int midIndex = stroke.StylusPoints.Count / 2; - Point midPoint = stroke.StylusPoints[midIndex].ToPoint(); - double midDeviation = DistanceFromLineToPoint(start, end, midPoint); - - // 输出中点偏差信息 - Debug.WriteLine($"Mid deviation: {midDeviation}, Threshold: {lineLength * sensitivity * 0.8}"); - - // 如果中点偏离过大,不拉直 - // 使用灵敏度作为判断基准,灵敏度越大,容许的中点偏离越大 - if (midDeviation > (lineLength * sensitivity * 0.8)) { - Debug.WriteLine("拒绝拉直:中点偏差过大"); - return false; - } - } - - Debug.WriteLine($"接受拉直:直线度评分 = {straightnessScore:F3}"); - return true; - } - - /// - /// 计算墨迹的直线度评分(0-1,1表示完美直线) - /// - private double CalculateStraightnessScore(Stroke stroke) - { - if (stroke.StylusPoints.Count < 3) return 0; - - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - double lineLength = GetDistance(start, end); - - if (lineLength == 0) return 0; - - // 1. 计算偏差评分(基于点到直线的距离) - double totalDeviation = 0; - double maxDeviation = 0; - int pointCount = 0; - - foreach (StylusPoint sp in stroke.StylusPoints) - { - Point p = sp.ToPoint(); - double deviation = DistanceFromLineToPoint(start, end, p); - totalDeviation += deviation; - maxDeviation = Math.Max(maxDeviation, deviation); - pointCount++; - } - - double avgDeviation = totalDeviation / pointCount; - - // 偏差评分:基于平均偏差和最大偏差 - double deviationScore = Math.Max(0, 1 - (avgDeviation / (lineLength * 0.05)) - (maxDeviation / (lineLength * 0.1))); - - // 2. 计算方向一致性评分 - double directionScore = CalculateDirectionConsistency(stroke); - - // 3. 计算路径效率评分(实际路径长度 vs 直线距离) - double actualLength = 0; - for (int i = 1; i < stroke.StylusPoints.Count; i++) - { - Point p1 = stroke.StylusPoints[i - 1].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - actualLength += GetDistance(p1, p2); - } - double efficiencyScore = Math.Max(0, Math.Min(1, lineLength / actualLength)); - - // 4. 计算端点连接度评分(起点到终点的直接性) - double endpointScore = 1.0; // 默认满分,因为我们已经有了起点和终点 - - // 综合评分(加权平均) - double finalScore = (deviationScore * 0.4 + directionScore * 0.3 + efficiencyScore * 0.2 + endpointScore * 0.1); - - Debug.WriteLine($"直线度评分详情: 偏差={deviationScore:F3}, 方向={directionScore:F3}, 效率={efficiencyScore:F3}, 综合={finalScore:F3}"); - - return Math.Max(0, Math.Min(1, finalScore)); - } - - /// - /// 计算方向一致性评分 - /// - private double CalculateDirectionConsistency(Stroke stroke) - { - if (stroke.StylusPoints.Count < 5) return 1.0; - - Point start = stroke.StylusPoints.First().ToPoint(); - Point end = stroke.StylusPoints.Last().ToPoint(); - - // 目标方向 - double targetAngle = Math.Atan2(end.Y - start.Y, end.X - start.X); - - double totalAngleDifference = 0; - int segmentCount = 0; - - // 计算每个线段与目标方向的角度差 - for (int i = 1; i < stroke.StylusPoints.Count; i++) - { - Point p1 = stroke.StylusPoints[i - 1].ToPoint(); - Point p2 = stroke.StylusPoints[i].ToPoint(); - - double segmentLength = GetDistance(p1, p2); - if (segmentLength < 2) continue; // 忽略太短的线段 - - double segmentAngle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X); - double angleDiff = Math.Abs(segmentAngle - targetAngle); - - // 处理角度跨越问题 - if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; - - totalAngleDifference += angleDiff; - segmentCount++; - } - - if (segmentCount == 0) return 1.0; - - double avgAngleDifference = totalAngleDifference / segmentCount; - - // 将角度差转换为评分(0-1) - // 0度差 = 1分,90度差 = 0分 - double directionScore = Math.Max(0, 1 - (avgAngleDifference / (Math.PI / 2))); - - return directionScore; - } - - // New method: Creates a straight line stroke between two points - private StylusPointCollection CreateStraightLine(Point start, Point end) { - StylusPointCollection points = new StylusPointCollection(); - - // 根据是否启用压感触屏模式决定如何设置压感 - // 如果未启用压感触屏模式,则使用均匀粗细 - 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) { - // 对于较长的线条,添加几个中间点 - 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(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)); - } - - return points; - } - - // New method: Gets distance from point to a line defined by two points - private double DistanceFromLineToPoint(Point lineStart, Point lineEnd, Point point) { - // Calculate distance from point to line defined by lineStart and lineEnd - double lineLength = GetDistance(lineStart, lineEnd); - if (lineLength == 0) return GetDistance(point, lineStart); - - // Calculate the cross product to get the perpendicular distance - double distance = Math.Abs((lineEnd.Y - lineStart.Y) * point.X - - (lineEnd.X - lineStart.X) * point.Y + - lineEnd.X * lineStart.Y - lineEnd.Y * lineStart.X) / lineLength; - return distance; - } - - // 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; - - bool startSnapped = false; - bool endSnapped = false; - Point snappedStart = start; - Point snappedEnd = end; - - // 使用设置中的吸附距离阈值 - double snapThreshold = Settings.Canvas.LineEndpointSnappingThreshold; - - // Check all strokes in canvas for potential snap points - foreach (Stroke stroke in inkCanvas.Strokes) { - if (stroke.StylusPoints.Count == 0) continue; - - // Get stroke endpoints - Point strokeStart = stroke.StylusPoints.First().ToPoint(); - Point strokeEnd = stroke.StylusPoints.Last().ToPoint(); - - // Check if start point should snap to an endpoint - if (!startSnapped) { - if (GetDistance(start, strokeStart) < snapThreshold) { - snappedStart = strokeStart; - startSnapped = true; - } else if (GetDistance(start, strokeEnd) < snapThreshold) { - snappedStart = strokeEnd; - startSnapped = true; - } - } - - // Check if end point should snap to an endpoint - if (!endSnapped) { - if (GetDistance(end, strokeStart) < snapThreshold) { - snappedEnd = strokeStart; - endSnapped = true; - } else if (GetDistance(end, strokeEnd) < snapThreshold) { - snappedEnd = strokeEnd; - endSnapped = true; - } - } - - // If both endpoints are snapped, we're done - if (startSnapped && endSnapped) break; - } - - // Return snapped points if any snapping occurred - if (startSnapped || endSnapped) { - return new[] { snappedStart, snappedEnd }; - } - - return null; - } - - private void SetNewBackupOfStroke() { - lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); - var whiteboardIndex = CurrentWhiteboardIndex; - if (currentMode == 0) whiteboardIndex = 0; - - strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection; - } - - public double GetDistance(Point point1, Point point2) { - return Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) + - (point1.Y - point2.Y) * (point1.Y - point2.Y)); - } - - public double GetPointSpeed(Point point1, Point point2, Point point3) { - return (Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) + - (point1.Y - point2.Y) * (point1.Y - point2.Y)) - + Math.Sqrt((point3.X - point2.X) * (point3.X - point2.X) + - (point3.Y - point2.Y) * (point3.Y - point2.Y))) - / 20; - } - - public Point[] FixPointsDirection(Point p1, Point p2) { - if (Math.Abs(p1.X - p2.X) / Math.Abs(p1.Y - p2.Y) > 8) { - //水平 - var x = Math.Abs(p1.Y - p2.Y) / 2; - if (p1.Y > p2.Y) { - p1.Y -= x; - p2.Y += x; - } - else { - p1.Y += x; - p2.Y -= x; - } - } - else if (Math.Abs(p1.Y - p2.Y) / Math.Abs(p1.X - p2.X) > 8) { - //垂直 - var x = Math.Abs(p1.X - p2.X) / 2; - if (p1.X > p2.X) { - p1.X -= x; - p2.X += x; - } - else { - p1.X += x; - p2.X -= x; - } - } - - return new Point[2] { p1, p2 }; - } - - public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points) { - 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)); - 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)); - return newPoint; - } - } - - public StylusPointCollection GenerateFakePressureRectangle(StylusPointCollection points) - { - if (Settings.InkToShape.IsInkToShapeNoFakePressureRectangle || penType == 1) { - 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; - } - - public Point GetCenterPoint(Point point1, Point point2) { - return new Point((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2); - } - - public StylusPoint GetCenterPoint(StylusPoint point1, StylusPoint point2) { - return new StylusPoint((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2); - } - - // 分辨率自适应:以1080P为基准,返回当前分辨率下的阈值倍数 - private double GetResolutionScale() - { - // 以1920x1080为基准 - double baseWidth = 1920.0; - double baseHeight = 1080.0; - double screenWidth = SystemParameters.PrimaryScreenWidth; - double screenHeight = SystemParameters.PrimaryScreenHeight; - // 取宽高平均缩放,防止极端比例 - double scaleW = screenWidth / baseWidth; - double scaleH = screenHeight / baseHeight; - return (scaleW + scaleH) / 2.0; - } - - #region 矩形参考线系统 - - /// - /// 处理矩形参考线系统 - /// - private void ProcessRectangleGuideLines(Stroke newStroke) - { - // 只有启用矩形识别时才处理 - if (!Settings.InkToShape.IsInkToShapeRectangle) return; - - // 检查新笔画是否为直线 - if (!IsPotentialStraightLine(newStroke)) return; - - Point startPoint = newStroke.StylusPoints[0].ToPoint(); - Point endPoint = newStroke.StylusPoints[newStroke.StylusPoints.Count - 1].ToPoint(); - - // 创建新的参考线 - var newGuideLine = new RectangleGuideLine(newStroke, startPoint, endPoint); - - // 清理过期的参考线(超过30秒的) - CleanupExpiredGuideLines(); - - // 添加新参考线 - rectangleGuideLines.Add(newGuideLine); - - // 检查是否可以构成矩形 - CheckForRectangleFormation(); - } - - /// - /// 清理过期的参考线 - /// - private void CleanupExpiredGuideLines() - { - var expireTime = DateTime.Now.AddSeconds(-30); // 30秒过期 - for (int i = rectangleGuideLines.Count - 1; i >= 0; i--) - { - var guideLine = rectangleGuideLines[i]; - if (guideLine.CreatedTime < expireTime || !inkCanvas.Strokes.Contains(guideLine.OriginalStroke)) - { - rectangleGuideLines.RemoveAt(i); - } - } - } - - /// - /// 检查是否可以构成矩形 - /// - private void CheckForRectangleFormation() - { - if (rectangleGuideLines.Count < 4) return; - - // 尝试找到四条能构成矩形的直线 - var rectangleLines = FindRectangleLines(); - if (rectangleLines != null && rectangleLines.Count == 4) - { - // 创建矩形并替换原有直线 - CreateRectangleFromLines(rectangleLines); - } - } - - /// - /// 寻找能构成矩形的四条直线 - /// - private List FindRectangleLines() - { - // 按时间排序,优先考虑最近绘制的直线 - var sortedLines = rectangleGuideLines.OrderByDescending(l => l.CreatedTime).ToList(); - - // 尝试不同的四条直线组合 - for (int i = 0; i < sortedLines.Count - 3; i++) - { - for (int j = i + 1; j < sortedLines.Count - 2; j++) - { - for (int k = j + 1; k < sortedLines.Count - 1; k++) - { - for (int l = k + 1; l < sortedLines.Count; l++) - { - var lines = new List { sortedLines[i], sortedLines[j], sortedLines[k], sortedLines[l] }; - if (CanFormRectangle(lines)) - { - return lines; - } - } - } - } - } - - return null; - } - - /// - /// 判断四条直线是否能构成矩形 - /// - private bool CanFormRectangle(List lines) - { - if (lines.Count != 4) return false; - - // 分类水平线和垂直线 - var horizontalLines = lines.Where(l => l.IsHorizontal).ToList(); - var verticalLines = lines.Where(l => l.IsVertical).ToList(); - - // 必须有2条水平线和2条垂直线 - if (horizontalLines.Count != 2 || verticalLines.Count != 2) return false; - - // 检查端点相交关系 - return CheckEndpointConnections(horizontalLines, verticalLines); - } - - /// - /// 检查端点相交关系 - /// - private bool CheckEndpointConnections(List horizontalLines, List verticalLines) - { - // 收集所有端点 - var allEndpoints = new List(); - foreach (var line in horizontalLines.Concat(verticalLines)) - { - allEndpoints.Add(line.StartPoint); - allEndpoints.Add(line.EndPoint); - } - - // 检查是否有4个相交点(允许一定误差) - var intersectionPoints = new List(); - - foreach (var hLine in horizontalLines) - { - foreach (var vLine in verticalLines) - { - var intersection = GetLineIntersection(hLine, vLine); - if (intersection.HasValue) - { - // 检查交点是否在两条线段的端点附近 - if (IsPointNearLineEndpoints(intersection.Value, hLine) && - IsPointNearLineEndpoints(intersection.Value, vLine)) - { - intersectionPoints.Add(intersection.Value); - } - } - } - } - - // 需要有4个交点才能构成矩形 - return intersectionPoints.Count >= 4; - } - - /// - /// 计算两条直线的交点 - /// - private Point? GetLineIntersection(RectangleGuideLine line1, RectangleGuideLine line2) - { - double x1 = line1.StartPoint.X, y1 = line1.StartPoint.Y; - double x2 = line1.EndPoint.X, y2 = line1.EndPoint.Y; - double x3 = line2.StartPoint.X, y3 = line2.StartPoint.Y; - double x4 = line2.EndPoint.X, y4 = line2.EndPoint.Y; - - double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - if (Math.Abs(denom) < 1e-10) return null; // 平行线 - - double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom; - double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom; - - double intersectionX = x1 + t * (x2 - x1); - double intersectionY = y1 + t * (y2 - y1); - - return new Point(intersectionX, intersectionY); - } - - /// - /// 检查点是否在直线端点附近 - /// - private bool IsPointNearLineEndpoints(Point point, RectangleGuideLine line) - { - double distToStart = GetDistance(point, line.StartPoint); - double distToEnd = GetDistance(point, line.EndPoint); - - return distToStart <= RECTANGLE_ENDPOINT_THRESHOLD || distToEnd <= RECTANGLE_ENDPOINT_THRESHOLD; - } - - /// - /// 从四条直线创建矩形 - /// - private void CreateRectangleFromLines(List lines) - { - try - { - // 计算矩形的四个角点 - var corners = CalculateRectangleCorners(lines); - if (corners == null || corners.Count != 4) return; - - // 创建矩形笔画 - var pointList = new List(corners) { corners[0] }; // 闭合矩形 - var point = new StylusPointCollection(pointList); - var rectangleStroke = new Stroke(GenerateFakePressureRectangle(point)) - { - DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone() - }; - - // 移除原有的四条直线 - SetNewBackupOfStroke(); - _currentCommitType = CommitReason.ShapeRecognition; - - foreach (var line in lines) - { - if (inkCanvas.Strokes.Contains(line.OriginalStroke)) - { - inkCanvas.Strokes.Remove(line.OriginalStroke); - } - } - - // 添加新的矩形 - inkCanvas.Strokes.Add(rectangleStroke); - _currentCommitType = CommitReason.UserInput; - - // 清理参考线 - foreach (var line in lines) - { - rectangleGuideLines.Remove(line); - } - - // 清空新笔画集合,避免重复处理 - newStrokes = new StrokeCollection(); - - Debug.WriteLine("成功创建矩形参考线矩形"); - } - catch (Exception ex) - { - Debug.WriteLine($"创建矩形时出错: {ex.Message}"); - } - } - - /// - /// 计算矩形的四个角点 - /// - private List CalculateRectangleCorners(List lines) - { - var horizontalLines = lines.Where(l => l.IsHorizontal).ToList(); - var verticalLines = lines.Where(l => l.IsVertical).ToList(); - - if (horizontalLines.Count != 2 || verticalLines.Count != 2) return null; - - var corners = new List(); - - // 计算四个交点 - foreach (var hLine in horizontalLines) - { - foreach (var vLine in verticalLines) - { - var intersection = GetLineIntersection(hLine, vLine); - if (intersection.HasValue) - { - corners.Add(intersection.Value); - } - } - } - - if (corners.Count != 4) return null; - - // 按顺序排列角点(顺时针或逆时针) - return SortRectangleCorners(corners); - } - - /// - /// 按顺序排列矩形角点 - /// - private List SortRectangleCorners(List corners) - { - if (corners.Count != 4) return corners; - - // 计算中心点 - double centerX = corners.Average(p => p.X); - double centerY = corners.Average(p => p.Y); - var center = new Point(centerX, centerY); - - // 按角度排序 - return corners.OrderBy(p => Math.Atan2(p.Y - center.Y, p.X - center.X)).ToList(); - } - - #endregion - } -} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs deleted file mode 100644 index aa2b5074..00000000 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ /dev/null @@ -1,557 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Ink; -using System.Windows.Input; -using System.Windows.Media; -using Ink_Canvas.Helpers; -using Point = System.Windows.Point; - -namespace Ink_Canvas { - public partial class MainWindow : Window { - #region Multi-Touch - - private bool isInMultiTouchMode; - private List dec = new List(); - private bool isSingleFingerDragMode; - private Point centerPoint = new Point(0, 0); - private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink; - - /// - /// 保存画布上的非笔画元素(如图片、媒体元素等) - /// - private List PreserveNonStrokeElements() - { - var preservedElements = new List(); - - // 遍历inkCanvas的所有子元素 - for (int i = inkCanvas.Children.Count - 1; i >= 0; i--) - { - var child = inkCanvas.Children[i]; - - // 保存图片、媒体元素等非笔画相关的UI元素 - if (child is Image || child is MediaElement || - (child is Border border && border.Name != "AdvancedEraserOverlay")) - { - preservedElements.Add(child); - } - } - - return preservedElements; - } - - /// - /// 恢复之前保存的非笔画元素到画布 - /// - private void RestoreNonStrokeElements(List preservedElements) - { - if (preservedElements == null) return; - - foreach (var element in preservedElements) - { - // 确保元素没有父容器再添加到inkCanvas - if (element is FrameworkElement fe && fe.Parent == null) - { - inkCanvas.Children.Add(element); - } - } - } - - private void BorderMultiTouchMode_MouseUp(object sender, MouseButtonEventArgs e) { - 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.Ink; - } - // 保存非笔画元素(如图片) - var preservedElements = PreserveNonStrokeElements(); - inkCanvas.Children.Clear(); - // 恢复非笔画元素 - RestoreNonStrokeElements(preservedElements); - isInMultiTouchMode = false; - - } - else { - - 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.None; - } - // 保存非笔画元素(如图片) - var preservedElements = PreserveNonStrokeElements(); - inkCanvas.Children.Clear(); - // 恢复非笔画元素 - RestoreNonStrokeElements(preservedElements); - isInMultiTouchMode = true; - } - } - - private void MainWindow_TouchDown(object sender, TouchEventArgs e) { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint - || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke - || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; - - if (!isHidingSubPanelsWhenInking) { - isHidingSubPanelsWhenInking = true; - HideSubPanels(); // 书写时自动隐藏二级菜单 - } - - // 只保留普通橡皮逻辑 - TouchDownPointsList[e.TouchDevice.Id] = InkCanvasEditingMode.None; - inkCanvas.EraserShape = new EllipseStylusShape(50, 50); - if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) { - inkCanvas.EditingMode = InkCanvasEditingMode.None; - } - } - - private void MainWindow_StylusDown(object sender, StylusDownEventArgs e) { - SetCursorBasedOnEditingMode(inkCanvas); - - inkCanvas.CaptureStylus(); - ViewboxFloatingBar.IsHitTestVisible = false; - BlackboardUIGridForInkReplay.IsHitTestVisible = false; - - // 确保手写笔模式下显示光标 - if (Settings.Canvas.IsShowCursor) { - inkCanvas.ForceCursor = true; - inkCanvas.UseCustomCursor = true; - - // 根据当前编辑模式设置不同的光标 - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - inkCanvas.Cursor = Cursors.Cross; - } else if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink) { - var sri = Application.GetResourceStream(new Uri("Resources/Cursors/Pen.cur", UriKind.Relative)); - if (sri != null) - inkCanvas.Cursor = new Cursor(sri.Stream); - } - - // 强制显示光标 - System.Windows.Forms.Cursor.Show(); - } - - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint - || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke - || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; - - TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None; - } - - private async void MainWindow_StylusUp(object sender, StylusEventArgs e) { - try { - inkCanvas.Strokes.Add(GetStrokeVisual(e.StylusDevice.Id).Stroke); - await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁 - inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id)); - - inkCanvas_StrokeCollected(inkCanvas, - new InkCanvasStrokeCollectedEventArgs(GetStrokeVisual(e.StylusDevice.Id).Stroke)); - } - catch (Exception ex) { - Label.Content = ex.ToString(); - } - - try { - StrokeVisualList.Remove(e.StylusDevice.Id); - VisualCanvasList.Remove(e.StylusDevice.Id); - TouchDownPointsList.Remove(e.StylusDevice.Id); - if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0) { - // 只清除手写笔预览相关的Canvas,不清除所有子元素 - foreach (var canvas in VisualCanvasList.Values.ToList()) { - if (inkCanvas.Children.Contains(canvas)) { - inkCanvas.Children.Remove(canvas); - } - } - StrokeVisualList.Clear(); - VisualCanvasList.Clear(); - TouchDownPointsList.Clear(); - } - } - catch { } - - inkCanvas.ReleaseStylusCapture(); - ViewboxFloatingBar.IsHitTestVisible = true; - BlackboardUIGridForInkReplay.IsHitTestVisible = true; - } - - private void MainWindow_StylusMove(object sender, StylusEventArgs e) { - try { - if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) return; - try { - if (e.StylusDevice.StylusButtons[1].StylusButtonState == StylusButtonState.Down) return; - } - catch { } - - // 确保手写笔移动时光标保持可见 - if (Settings.Canvas.IsShowCursor) { - inkCanvas.ForceCursor = true; - inkCanvas.UseCustomCursor = true; - System.Windows.Forms.Cursor.Show(); - } - - var strokeVisual = GetStrokeVisual(e.StylusDevice.Id); - var stylusPointCollection = e.GetStylusPoints(this); - foreach (var stylusPoint in stylusPointCollection) - strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor)); - strokeVisual.Redraw(); - } - catch { } - } - - private StrokeVisual GetStrokeVisual(int id) { - if (StrokeVisualList.TryGetValue(id, out var visual)) return visual; - - var strokeVisual = new StrokeVisual(inkCanvas.DefaultDrawingAttributes.Clone()); - StrokeVisualList[id] = strokeVisual; - StrokeVisualList[id] = strokeVisual; - var visualCanvas = new VisualCanvas(strokeVisual); - VisualCanvasList[id] = visualCanvas; - inkCanvas.Children.Add(visualCanvas); - - return strokeVisual; - } - - private VisualCanvas GetVisualCanvas(int id) { - return VisualCanvasList.TryGetValue(id, out var visualCanvas) ? visualCanvas : null; - } - - private InkCanvasEditingMode GetTouchDownPointsList(int id) { - return TouchDownPointsList.TryGetValue(id, out var inkCanvasEditingMode) ? inkCanvasEditingMode : inkCanvas.EditingMode; - } - - private Dictionary TouchDownPointsList { get; } = - new Dictionary(); - - private Dictionary StrokeVisualList { get; } = new Dictionary(); - private Dictionary VisualCanvasList { get; } = new Dictionary(); - - #endregion - - - private int lastTouchDownTime = 0, lastTouchUpTime = 0; - - private Point iniP = new Point(0, 0); - - private void Main_Grid_TouchDown(object sender, TouchEventArgs e) { - SetCursorBasedOnEditingMode(inkCanvas); - inkCanvas.CaptureTouch(e.TouchDevice); - - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - // 橡皮状态下只return,保证橡皮状态可保持 - return; - } - if (inkCanvas.EditingMode == InkCanvasEditingMode.Select) { - // 套索选状态下只return,保证套索选可用 - return; - } - if (drawingShapeMode == 9) { - if (isFirstTouchCuboid) { - CuboidFrontRectIniP = e.GetTouchPoint(inkCanvas).Position; - } - // 允许MouseTouchMove在TouchMove时处理 - return; - } - if (drawingShapeMode != 0) { - return; - } - if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink) { - return; - } - if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) { - inkCanvas.EditingMode = InkCanvasEditingMode.Ink; - } - } - - // 手掌擦相关变量 - private bool isPalmEraserActive; - private InkCanvasEditingMode palmEraserLastEditingMode = InkCanvasEditingMode.Ink; - private bool palmEraserLastIsHighlighter; - private bool palmEraserWasEnabledBeforeMultiTouch; - - private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e) { - // 橡皮状态下不做任何切换,直接return,保证橡皮可持续 - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - return; - } - SetCursorBasedOnEditingMode(inkCanvas); - - inkCanvas.CaptureTouch(e.TouchDevice); - ViewboxFloatingBar.IsHitTestVisible = false; - BlackboardUIGridForInkReplay.IsHitTestVisible = false; - - dec.Add(e.TouchDevice.Id); - // Palm Eraser 逻辑 - if (Settings.Canvas.EnablePalmEraser && dec.Count >= 2 && !isPalmEraserActive) { - var bounds = e.GetTouchPoint(inkCanvas).Bounds; - double palmThreshold = 40; // 触摸面积阈值,可根据实际调整 - if (bounds.Width >= palmThreshold || bounds.Height >= palmThreshold) { - // 记录当前编辑模式和高光状态 - palmEraserLastEditingMode = inkCanvas.EditingMode; - palmEraserLastIsHighlighter = drawingAttributes.IsHighlighter; - // 切换为橡皮擦 - EraserIcon_Click(null, null); - isPalmEraserActive = true; - } - } - //设备1个的时候,记录中心点 - if (dec.Count == 1) { - var touchPoint = e.GetTouchPoint(inkCanvas); - centerPoint = touchPoint.Position; - - // 新增:几何绘制模式下,记录初始点 - if (drawingShapeMode != 0) { - iniP = touchPoint.Position; - } - - //记录第一根手指点击时的 StrokeCollection - lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); - } - //设备两个及两个以上,将画笔功能关闭 - if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture) { - if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; - if (inkCanvas.EditingMode == InkCanvasEditingMode.None || - inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; - lastInkCanvasEditingMode = inkCanvas.EditingMode; - if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) { - inkCanvas.EditingMode = InkCanvasEditingMode.None; - } - } - } - - 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 逻辑:所有点抬起后恢复原编辑模式 - dec.Remove(e.TouchDevice.Id); - if (isPalmEraserActive && dec.Count == 0) { - // 恢复高光状态 - drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter; - // 恢复编辑模式 - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) { - if (palmEraserLastEditingMode == InkCanvasEditingMode.Ink) { - PenIcon_Click(null, null); - } else if (palmEraserLastEditingMode == InkCanvasEditingMode.Select) { - SymbolIconSelect_MouseUp(null, null); - } else { - inkCanvas.EditingMode = palmEraserLastEditingMode; - } - } - isPalmEraserActive = false; - } - // 新增:几何绘制模式下,触摸抬手时自动落笔 - if (drawingShapeMode != 0) { - var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) - { - RoutedEvent = MouseLeftButtonUpEvent, - Source = inkCanvas - }; - inkCanvas_MouseUp(inkCanvas, mouseArgs); - } - - //手势完成后切回之前的状态 - // 修复:改进多指手势恢复逻辑,确保从橡皮擦切换到笔时多指手势能正确恢复 - 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; - } - } - inkCanvas.Opacity = 1; - - if (dec.Count == 0) - if (lastTouchDownStrokeCollection.Count() != inkCanvas.Strokes.Count() && - !(drawingShapeMode == 9 && !isFirstTouchCuboid)) { - var whiteboardIndex = CurrentWhiteboardIndex; - if (currentMode == 0) whiteboardIndex = 0; - strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection; - } - } - - private void inkCanvas_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { - e.Mode = ManipulationModes.All; - } - - 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.Ink; - // 修复:确保多指手势完成后正确更新lastInkCanvasEditingMode - 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 ((dec.Count >= 2 && (Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode || - StackPanelPPTControls.Visibility != Visibility.Visible || - StackPanelPPTButtons.Visibility == Visibility.Collapsed)) || - isSingleFingerDragMode) { - var md = e.DeltaManipulation; - var trans = md.Translation; // 获得位移矢量 - - var m = new Matrix(); - - if (Settings.Gesture.IsEnableTwoFingerTranslate) - m.Translate(trans.X, trans.Y); // 移动 - - 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); // 缩放 - } - - var strokes = inkCanvas.GetSelectedStrokes(); - if (strokes.Count != 0) { - foreach (var stroke in strokes) { - stroke.Transform(m, false); - - foreach (var circle in circles) - if (stroke == circle.Stroke) { - circle.R = GetDistance(circle.Stroke.StylusPoints[0].ToPoint(), - circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].ToPoint()) / 2; - circle.Centroid = new Point( - (circle.Stroke.StylusPoints[0].X + - circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].X) / 2, - (circle.Stroke.StylusPoints[0].Y + - circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].Y) / 2); - break; - } - - if (!Settings.Gesture.IsEnableTwoFingerZoom) continue; - try { - stroke.DrawingAttributes.Width *= md.Scale.X; - stroke.DrawingAttributes.Height *= md.Scale.Y; - } - catch { } - } - } - else { - if (Settings.Gesture.IsEnableTwoFingerZoom) { - foreach (var stroke in inkCanvas.Strokes) { - stroke.Transform(m, false); - try { - stroke.DrawingAttributes.Width *= md.Scale.X; - stroke.DrawingAttributes.Height *= md.Scale.Y; - } - catch { } - } - - ; - } - else { - foreach (var stroke in inkCanvas.Strokes) stroke.Transform(m, false); - ; - } - - foreach (var circle in circles) { - circle.R = GetDistance(circle.Stroke.StylusPoints[0].ToPoint(), - circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].ToPoint()) / 2; - circle.Centroid = new Point( - (circle.Stroke.StylusPoints[0].X + - circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].X) / 2, - (circle.Stroke.StylusPoints[0].Y + - circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].Y) / 2 - ); - } - } - } - } - - // 退出多指书写模式,恢复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.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.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; - } - } - } -} diff --git a/Ink Canvas/Resources/Cursors/Cursor.cur b/Ink Canvas/Resources/Cursors/Cursor.cur deleted file mode 100644 index 95b01752..00000000 Binary files a/Ink Canvas/Resources/Cursors/Cursor.cur and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/Alan-CRL.png b/Ink Canvas/Resources/DeveloperAvatars/Alan-CRL.png deleted file mode 100644 index 4aff7a43..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/Alan-CRL.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/CN-Ironegg.jpg b/Ink Canvas/Resources/DeveloperAvatars/CN-Ironegg.jpg deleted file mode 100644 index aeb7da94..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/CN-Ironegg.jpg and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/NetheriteBowl.png b/Ink Canvas/Resources/DeveloperAvatars/NetheriteBowl.png deleted file mode 100644 index 39f224e4..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/NetheriteBowl.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/NotYoojun.png b/Ink Canvas/Resources/DeveloperAvatars/NotYoojun.png deleted file mode 100644 index b328c1de..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/NotYoojun.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/RaspberryKan.jpg b/Ink Canvas/Resources/DeveloperAvatars/RaspberryKan.jpg deleted file mode 100644 index d26b4db0..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/RaspberryKan.jpg and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/STBBRD.png b/Ink Canvas/Resources/DeveloperAvatars/STBBRD.png deleted file mode 100644 index 098d12e7..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/STBBRD.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/aaaaaaccd.jpg b/Ink Canvas/Resources/DeveloperAvatars/aaaaaaccd.jpg deleted file mode 100644 index cca5f67a..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/aaaaaaccd.jpg and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/clover-yan.png b/Ink Canvas/Resources/DeveloperAvatars/clover-yan.png deleted file mode 100644 index 162440fd..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/clover-yan.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/jiajiaxd.jpg b/Ink Canvas/Resources/DeveloperAvatars/jiajiaxd.jpg deleted file mode 100644 index e845b2cd..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/jiajiaxd.jpg and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/kengwang.png b/Ink Canvas/Resources/DeveloperAvatars/kengwang.png deleted file mode 100644 index 1170d789..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/kengwang.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/wwei.png b/Ink Canvas/Resources/DeveloperAvatars/wwei.png deleted file mode 100644 index 379b90f8..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/wwei.png and /dev/null differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/yuwenhui2020.png b/Ink Canvas/Resources/DeveloperAvatars/yuwenhui2020.png deleted file mode 100644 index 61abd488..00000000 Binary files a/Ink Canvas/Resources/DeveloperAvatars/yuwenhui2020.png and /dev/null differ diff --git a/Ink Canvas/Resources/IACore/IACore.dll b/Ink Canvas/Resources/IACore/IACore.dll deleted file mode 100644 index fdef2ca7..00000000 Binary files a/Ink Canvas/Resources/IACore/IACore.dll and /dev/null differ diff --git a/Ink Canvas/Resources/IACore/IALoader.dll b/Ink Canvas/Resources/IACore/IALoader.dll deleted file mode 100644 index 3ebd354c..00000000 Binary files a/Ink Canvas/Resources/IACore/IALoader.dll and /dev/null differ diff --git a/Ink Canvas/Resources/IACore/IAWinFX.dll b/Ink Canvas/Resources/IACore/IAWinFX.dll deleted file mode 100644 index 1ce450a8..00000000 Binary files a/Ink Canvas/Resources/IACore/IAWinFX.dll and /dev/null differ diff --git a/Ink Canvas/Resources/Icons-png/icc-transparent-dark-small.png b/Ink Canvas/Resources/Icons-png/icc-transparent-dark-small.png deleted file mode 100644 index cfddd979..00000000 Binary files a/Ink Canvas/Resources/Icons-png/icc-transparent-dark-small.png and /dev/null differ diff --git a/Ink Canvas/Resources/Icons-png/icc-transparent-dark.png b/Ink Canvas/Resources/Icons-png/icc-transparent-dark.png deleted file mode 100644 index b2bbf8bf..00000000 Binary files a/Ink Canvas/Resources/Icons-png/icc-transparent-dark.png and /dev/null differ diff --git a/Ink Canvas/Resources/Icons-png/icc-transparent.png b/Ink Canvas/Resources/Icons-png/icc-transparent.png deleted file mode 100644 index b2bbf8bf..00000000 Binary files a/Ink Canvas/Resources/Icons-png/icc-transparent.png and /dev/null differ diff --git a/Ink Canvas/Resources/Icons-png/icc.png b/Ink Canvas/Resources/Icons-png/icc.png deleted file mode 100644 index cf1979ad..00000000 Binary files a/Ink Canvas/Resources/Icons-png/icc.png and /dev/null differ diff --git a/Ink Canvas/Resources/icc.ico b/Ink Canvas/Resources/icc.ico deleted file mode 100644 index 90853402..00000000 Binary files a/Ink Canvas/Resources/icc.ico and /dev/null differ diff --git a/Ink Canvas/Resources/new-icons/gesture-enabled.png b/Ink Canvas/Resources/new-icons/gesture-enabled.png deleted file mode 100644 index fe0b9b37..00000000 Binary files a/Ink Canvas/Resources/new-icons/gesture-enabled.png and /dev/null differ diff --git a/Ink Canvas/Resources/new-icons/gesture.png b/Ink Canvas/Resources/new-icons/gesture.png deleted file mode 100644 index 8e7c6401..00000000 Binary files a/Ink Canvas/Resources/new-icons/gesture.png and /dev/null differ diff --git a/Ink Canvas/Windows/AddCustomIconWindow.xaml b/Ink Canvas/Windows/AddCustomIconWindow.xaml deleted file mode 100644 index d20264e2..00000000 --- a/Ink Canvas/Windows/AddCustomIconWindow.xaml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/InkCanvasForClass/MainWindow_cs/MW_ShapeDrawingLayer.xaml.cs b/InkCanvasForClass/MainWindow_cs/MW_ShapeDrawingLayer.xaml.cs new file mode 100644 index 00000000..a5504668 --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_ShapeDrawingLayer.xaml.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Ink_Canvas { + public partial class ShapeDrawingLayer : UserControl { + + public ShapeDrawingLayer() { + InitializeComponent(); + + // ToolbarMoveHandle + ToolbarMoveHandle.MouseDown += ToolbarMoveHandle_MouseDown; + ToolbarMoveHandle.MouseUp += ToolbarMoveHandle_MouseUp; + ToolbarMoveHandle.MouseMove += ToolbarMoveHandle_MouseMove; + UpdateToolbarPosition(ToolbarNowPosition); + + // Update ToolBtns + ToolButtons = new Border[] { + CursorButton, + UndoButton, + RedoButton, + ClearButton, + GridLineButton, + SnapButton, + MultiPointButton, + InfoButton, + MoreButton, + }; + foreach (var tb in ToolButtons) { + tb.MouseDown += ToolButton_MouseDown; + tb.MouseUp += ToolButton_MouseUp; + tb.MouseLeave += ToolButton_MouseLeave; + } + + Toolbar.Visibility = Visibility.Collapsed; + AngleTooltip.Visibility = Visibility.Collapsed; + LengthTooltip.Visibility = Visibility.Collapsed; + + FullscreenGrid.MouseDown += FullscreenGrid_MouseDown; + FullscreenGrid.MouseUp += FullscreenGrid_MouseUp; + FullscreenGrid.MouseMove += FullscreenGrid_MouseMove; + } + + private Point CaculateCenteredToolbarPosition() { + var aw = Toolbar.Width; + var ah = Toolbar.Height; + var left = (MainWindow.ActualWidth - aw) / 2; + var top = MainWindow.ActualHeight - ah - 128; + return new Point(left, top); + } + + public MainWindow MainWindow { get; set; } + + public Border[] ToolButtons = new Border[] { }; + public Point ToolbarNowPosition = new Point(0, 0); + public bool IsToolbarMoveHandleDown = false; + public Point MouseDownPointInHandle; + public Border ToolButtonMouseDownBorder = null; + + public void UpdateToolbarPosition(Point? position) { + if (position == null) { + Toolbar.RenderTransform = null; + return; + } + Toolbar.RenderTransform = new TranslateTransform(((Point)position).X, ((Point)position).Y); + } + + private void ToolbarMoveHandle_MouseDown(object sender, MouseButtonEventArgs e) { + if (IsToolbarMoveHandleDown) return; + MouseDownPointInHandle = FullscreenGrid.TranslatePoint(e.GetPosition(null),ToolbarMoveHandle); + IsToolbarMoveHandleDown = true; + ToolbarMoveHandle.CaptureMouse(); + } + + private void ToolbarMoveHandle_MouseUp(object sender, MouseButtonEventArgs e) { + if (!IsToolbarMoveHandleDown) return; + IsToolbarMoveHandleDown = false; + ToolbarMoveHandle.ReleaseMouseCapture(); + } + + private void ToolbarMoveHandle_MouseMove(object sender, MouseEventArgs e) { + if (!IsToolbarMoveHandleDown) return; + var ptInScreen = e.GetPosition(null); + ToolbarNowPosition = new Point(ptInScreen.X - MouseDownPointInHandle.X, ptInScreen.Y - MouseDownPointInHandle.Y); + UpdateToolbarPosition(ToolbarNowPosition); + } + + private MainWindow.ShapeDrawingType? _shapeType; + + public bool IsInShapeDrawingMode { + get => _shapeType != null; + } + + public void StartShapeDrawing(MainWindow.ShapeDrawingType type, string name) { + _shapeType = type; + FullscreenGrid.Background = new SolidColorBrush(Color.FromArgb(1,0,0,0)); + FullscreenGrid.Visibility = Visibility.Visible; + Toolbar.Visibility = Visibility.Visible; + var pt = CaculateCenteredToolbarPosition(); + ToolbarNowPosition = pt; + UpdateToolbarPosition(pt); + ShapeDrawingTypeText.Text = name ?? "未知形状"; + PenSizeText.Text = $"{(MainWindow.inkCanvas.DefaultDrawingAttributes.Width + MainWindow.inkCanvas.DefaultDrawingAttributes.Height) / 2} 像素"; + } + + private void DoneButtonClicked(object sender, RoutedEventArgs e) { + EndShapeDrawing(); + } + + public void EndShapeDrawing() { + _shapeType = null; + FullscreenGrid.Background = null; + FullscreenGrid.Visibility = Visibility.Collapsed; + Toolbar.Visibility = Visibility.Collapsed; + } + + private void ToolButton_MouseDown(object sender, MouseButtonEventArgs e) { + if (ToolButtonMouseDownBorder != null) return; + ToolButtonMouseDownBorder = (Border)sender; + ToolButtonMouseDownBorder.Background = new SolidColorBrush(Color.FromRgb(228, 228, 231)); + } + + private void ToolButton_MouseUp(object sender, MouseButtonEventArgs e) { + if (ToolButtonMouseDownBorder == null || ToolButtonMouseDownBorder != sender) return; + + ToolButton_MouseLeave(sender, null); + } + + private void ToolButton_MouseLeave(object sender, MouseEventArgs e) { + if (ToolButtonMouseDownBorder == null || ToolButtonMouseDownBorder != sender) return; + ToolButtonMouseDownBorder.Background = new SolidColorBrush(Colors.Transparent); + ToolButtonMouseDownBorder = null; + } + + private bool isFullscreenGridDown = false; + public PointCollection points = new PointCollection(); + + private void FullscreenGrid_MouseDown(object sender, MouseButtonEventArgs e) { + if (isFullscreenGridDown) return; + points.Clear(); + points.Add(e.GetPosition(null)); + isFullscreenGridDown = true; + FullscreenGrid.CaptureMouse(); + } + + private void FullscreenGrid_MouseUp(object sender, MouseButtonEventArgs e) { + if (!isFullscreenGridDown) return; + FullscreenGrid.ReleaseMouseCapture(); + isFullscreenGridDown = false; + if (_shapeType == null) return; + using (DrawingContext dc = DrawingVisualCanvas.DrawingVisual.RenderOpen()) {} + + if (points.Count >= 2) + MainWindow.inkCanvas.Strokes.Add(MainWindow.DrawShapeCore(points, (MainWindow.ShapeDrawingType)_shapeType,false,false)); + points.Clear(); + AngleTooltip.Visibility = Visibility.Collapsed; + LengthTooltip.Visibility = Visibility.Collapsed; + } + + private void FullscreenGrid_MouseMove(object sender, MouseEventArgs e) { + if (!isFullscreenGridDown) return; + if (_shapeType == null) return; + if (points.Count >= 2) points[1] = e.GetPosition(null); + else points.Add(e.GetPosition(null)); + + using (DrawingContext dc = DrawingVisualCanvas.DrawingVisual.RenderOpen()) { + if ((_shapeType == MainWindow.ShapeDrawingType.Line || + _shapeType == MainWindow.ShapeDrawingType.DashedLine || + _shapeType == MainWindow.ShapeDrawingType.DottedLine || + _shapeType == MainWindow.ShapeDrawingType.ArrowOneSide || + _shapeType == MainWindow.ShapeDrawingType.ArrowTwoSide) && points.Count >= 2) { + MainWindow.DrawShapeCore(points, (MainWindow.ShapeDrawingType)_shapeType,true,true).Draw(dc); + var angle = MainWindow.ShapeDrawingHelper.CaculateRotateAngleByGivenTwoPoints(points[0], points[1]); + if (AngleTooltip.Visibility == Visibility.Collapsed) AngleTooltip.Visibility = Visibility.Visible; + AngleText.Text = $"{angle}°"; + if (LengthTooltip.Visibility == Visibility.Collapsed) LengthTooltip.Visibility = Visibility.Visible; + LengthText.Text = $"{Math.Round(Math.Sqrt(Math.Pow((points[1].Y - points[0].Y), 2) + Math.Pow((points[1].X - points[0].X), 2)),2)} 像素"; + } + } + } + } +} diff --git a/InkCanvasForClass/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/InkCanvasForClass/MainWindow_cs/MW_SimulatePressure&InkToShape.cs new file mode 100644 index 00000000..d52511b3 --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_SimulatePressure&InkToShape.cs @@ -0,0 +1,579 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows.Threading; +using Point = System.Windows.Point; + +namespace Ink_Canvas { + public partial class MainWindow : PerformanceTransparentWin { + private StrokeCollection newStrokes = new StrokeCollection(); + private List circles = new List(); + + private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e) { + if (Settings.Canvas.FitToCurve == true) drawingAttributes.FitToCurve = false; + + try { + inkCanvas.Opacity = 1; + if (Settings.InkToShape.IsInkToShapeEnabled && !Environment.Is64BitProcess) { + void InkToShapeProcess() { + try { + newStrokes.Add(e.Stroke); + if (newStrokes.Count > 4) newStrokes.RemoveAt(0); + Dispatcher.InvokeAsync(() => { + for (var i = 0; i < newStrokes.Count; i++) + if (!inkCanvas.Strokes.Contains(newStrokes[i])) + newStrokes.RemoveAt(i--); + + for (var i = 0; i < circles.Count; i++) + if (!inkCanvas.Strokes.Contains(circles[i].Stroke)) + circles.RemoveAt(i); + }); + var strokeReco = new StrokeCollection(); + var result = InkRecognizeHelper.RecognizeShape(newStrokes); + for (var i = newStrokes.Count - 1; i >= 0; i--) { + strokeReco.Add(newStrokes[i]); + var newResult = InkRecognizeHelper.RecognizeShape(strokeReco); + if (newResult.InkDrawingNode.GetShapeName() == "Circle" || + newResult.InkDrawingNode.GetShapeName() == "Ellipse") { + result = newResult; + break; + } + } + + if (result.InkDrawingNode.GetShapeName() == "Circle" && + Settings.InkToShape.IsInkToShapeRounded == true) { + var shapeWidth = Dispatcher.Invoke(()=>result.InkDrawingNode.GetShape().Width); + var shapeHeight = Dispatcher.Invoke(()=>result.InkDrawingNode.GetShape().Height); + if (shapeWidth > 75) { + foreach (var circle in circles) + //判断是否画同心圆 + if (Math.Abs(result.Centroid.X - circle.Centroid.X) / shapeWidth < 0.12 && + Math.Abs(result.Centroid.Y - circle.Centroid.Y) / shapeWidth < 0.12) { + result.Centroid = circle.Centroid; + break; + } + else { + var d = (result.Centroid.X - circle.Centroid.X) * + (result.Centroid.X - circle.Centroid.X) + + (result.Centroid.Y - circle.Centroid.Y) * + (result.Centroid.Y - circle.Centroid.Y); + d = Math.Sqrt(d); + //判断是否画外切圆 + var x = shapeWidth / 2.0 + circle.R - d; + if (Math.Abs(x) / shapeWidth < 0.1) { + var sinTheta = (result.Centroid.Y - circle.Centroid.Y) / d; + var cosTheta = (result.Centroid.X - circle.Centroid.X) / d; + var newX = result.Centroid.X + x * cosTheta; + var newY = result.Centroid.Y + x * sinTheta; + result.Centroid = new Point(newX, newY); + } + + //判断是否画外切圆 + x = Math.Abs(circle.R - shapeWidth / 2.0) - d; + if (Math.Abs(x) / shapeWidth < 0.1) { + var sinTheta = (result.Centroid.Y - circle.Centroid.Y) / d; + var cosTheta = (result.Centroid.X - circle.Centroid.X) / d; + var newX = result.Centroid.X + x * cosTheta; + var newY = result.Centroid.Y + x * sinTheta; + result.Centroid = new Point(newX, newY); + } + } + + var iniP = new Point(result.Centroid.X - shapeWidth / 2, + result.Centroid.Y - shapeHeight / 2); + var endP = new Point(result.Centroid.X + shapeWidth / 2, + result.Centroid.Y + shapeHeight / 2); + var pointList = GenerateEllipseGeometry(iniP, endP); + var point = new StylusPointCollection(pointList); + var stroke = new Stroke(point) { + DrawingAttributes = Dispatcher.Invoke(()=>inkCanvas.DefaultDrawingAttributes.Clone()) + }; + circles.Add(new Circle(result.Centroid, shapeWidth / 2.0, stroke)); + Dispatcher.InvokeAsync(() => { + SetNewBackupOfStroke(); + _currentCommitType = CommitReason.ShapeRecognition; + inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); + inkCanvas.Strokes.Add(stroke); + }); + _currentCommitType = CommitReason.UserInput; + newStrokes = new StrokeCollection(); + } + } + else if (result.InkDrawingNode.GetShapeName().Contains("Ellipse") && + Settings.InkToShape.IsInkToShapeRounded == true) { + var shapeWidth = Dispatcher.Invoke(()=>result.InkDrawingNode.GetShape().Width); + var shapeHeight = Dispatcher.Invoke(()=>result.InkDrawingNode.GetShape().Height); + PointCollection p = new PointCollection(); + Point[] _p = new Point[]{}; + Dispatcher.Invoke(() => { + var arr = result.InkDrawingNode.HotPoints.ToArray(); + _p = arr; + }); + p = new PointCollection(_p); + var a = GetDistance(p[0], p[2]) / 2; //长半轴 + var b = GetDistance(p[1], p[3]) / 2; //短半轴 + if (a < b) { + var t = a; + a = b; + b = t; + } + + result.Centroid = new Point((p[0].X + p[2].X) / 2, (p[0].Y + p[2].Y) / 2); + var needRotation = true; + + if (shapeWidth > 75 || (shapeHeight > 75 && p.Count == 4)) { + var iniP = new Point(result.Centroid.X - shapeWidth / 2, + result.Centroid.Y - shapeHeight / 2); + var endP = new Point(result.Centroid.X + shapeWidth / 2, + result.Centroid.Y + shapeHeight / 2); + + foreach (var circle in circles) + //判断是否画同心椭圆 + if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 && + Math.Abs(result.Centroid.Y - circle.Centroid.Y) / a < 0.2) { + result.Centroid = circle.Centroid; + iniP = new Point(result.Centroid.X - shapeWidth / 2, + result.Centroid.Y - shapeHeight / 2); + endP = new Point(result.Centroid.X + shapeWidth / 2, + result.Centroid.Y + shapeHeight / 2); + + //再判断是否与圆相切 + if (Math.Abs(a - circle.R) / a < 0.2) { + if (shapeWidth >= shapeHeight) { + iniP.X = result.Centroid.X - circle.R; + endP.X = result.Centroid.X + circle.R; + iniP.Y = result.Centroid.Y - b; + endP.Y = result.Centroid.Y + b; + } + else { + iniP.Y = result.Centroid.Y - circle.R; + endP.Y = result.Centroid.Y + circle.R; + iniP.X = result.Centroid.X - a; + endP.X = result.Centroid.X + a; + } + } + + break; + } + else if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2) { + var sinTheta = Math.Abs(circle.Centroid.Y - result.Centroid.Y) / + circle.R; + var cosTheta = Math.Sqrt(1 - sinTheta * sinTheta); + var newA = circle.R * cosTheta; + if (circle.R * sinTheta / circle.R < 0.9 && a / b > 2 && + Math.Abs(newA - a) / newA < 0.3) { + iniP.X = circle.Centroid.X - newA; + endP.X = circle.Centroid.X + newA; + iniP.Y = result.Centroid.Y - newA / 5; + endP.Y = result.Centroid.Y + newA / 5; + + var topB = endP.Y - iniP.Y; + + SetNewBackupOfStroke(); + _currentCommitType = CommitReason.ShapeRecognition; + Dispatcher.InvokeAsync(() => { + inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); + }); + newStrokes = new StrokeCollection(); + + var _pointList = GenerateEllipseGeometry(iniP, endP, false, true); + var _point = new StylusPointCollection(_pointList); + var _stroke = new Stroke(_point) { + DrawingAttributes = Dispatcher.Invoke(()=>inkCanvas.DefaultDrawingAttributes.Clone()) + }; + Dispatcher.InvokeAsync(() => { + var _dashedLineStroke = + GenerateDashedLineEllipseStrokeCollection(iniP, endP, true, + false); + var strokes = new StrokeCollection() { + _stroke, + _dashedLineStroke + }; + inkCanvas.Strokes.Add(strokes); + _currentCommitType = CommitReason.UserInput; + }); + return; + } + } + else if (Math.Abs(result.Centroid.Y - circle.Centroid.Y) / a < 0.2) { + var cosTheta = Math.Abs(circle.Centroid.X - result.Centroid.X) / + circle.R; + var sinTheta = Math.Sqrt(1 - cosTheta * cosTheta); + var newA = circle.R * sinTheta; + if (circle.R * sinTheta / circle.R < 0.9 && a / b > 2 && + Math.Abs(newA - a) / newA < 0.3) { + iniP.X = result.Centroid.X - newA / 5; + endP.X = result.Centroid.X + newA / 5; + iniP.Y = circle.Centroid.Y - newA; + endP.Y = circle.Centroid.Y + newA; + needRotation = false; + } + } + + //纠正垂直与水平关系 + var newPoints = FixPointsDirection(p[0], p[2]); + p[0] = newPoints[0]; + p[2] = newPoints[1]; + newPoints = FixPointsDirection(p[1], p[3]); + p[1] = newPoints[0]; + p[3] = newPoints[1]; + + var pointList = GenerateEllipseGeometry(iniP, endP); + var point = new StylusPointCollection(pointList); + var stroke = new Stroke(point) { + DrawingAttributes = Dispatcher.Invoke(()=>inkCanvas.DefaultDrawingAttributes.Clone()) + }; + + if (needRotation) { + var m = new Matrix(); + var fe = e.Source as FrameworkElement; + var tanTheta = (p[2].Y - p[0].Y) / (p[2].X - p[0].X); + var theta = Math.Atan(tanTheta); + m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y); + stroke.Transform(m, false); + } + + Dispatcher.InvokeAsync(() => { + SetNewBackupOfStroke(); + _currentCommitType = CommitReason.ShapeRecognition; + inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); + inkCanvas.Strokes.Add(stroke); + _currentCommitType = CommitReason.UserInput; + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + newStrokes = new StrokeCollection(); + }); + } + } + else if (result.InkDrawingNode.GetShapeName().Contains("Triangle") && + Settings.InkToShape.IsInkToShapeTriangle == true) { + PointCollection p = new PointCollection(); + Point[] _p = new Point[]{}; + Dispatcher.Invoke(() => { + var arr = result.InkDrawingNode.HotPoints.ToArray(); + _p = arr; + }); + p = new PointCollection(_p); + if ((Math.Max(Math.Max(p[0].X, p[1].X), p[2].X) - + Math.Min(Math.Min(p[0].X, p[1].X), p[2].X) >= 100 || + Math.Max(Math.Max(p[0].Y, p[1].Y), p[2].Y) - + Math.Min(Math.Min(p[0].Y, p[1].Y), p[2].Y) >= 100) && + result.InkDrawingNode.HotPoints.Count == 3) { + //纠正垂直与水平关系 + var newPoints = FixPointsDirection(p[0], p[1]); + p[0] = newPoints[0]; + p[1] = newPoints[1]; + newPoints = FixPointsDirection(p[0], p[2]); + p[0] = newPoints[0]; + p[2] = newPoints[1]; + newPoints = FixPointsDirection(p[1], p[2]); + p[1] = newPoints[0]; + p[2] = newPoints[1]; + + var pointList = p.ToList(); + //pointList.Add(p[0]); + var point = new StylusPointCollection(pointList); + var stroke = new Stroke(GenerateFakePressureTriangle(point)) { + DrawingAttributes = Dispatcher.Invoke(()=>inkCanvas.DefaultDrawingAttributes.Clone()) + }; + + Dispatcher.InvokeAsync(() => { + SetNewBackupOfStroke(); + _currentCommitType = CommitReason.ShapeRecognition; + inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); + inkCanvas.Strokes.Add(stroke); + _currentCommitType = CommitReason.UserInput; + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + newStrokes = new StrokeCollection(); + }); + } + } + else if ((result.InkDrawingNode.GetShapeName().Contains("Rectangle") || + result.InkDrawingNode.GetShapeName().Contains("Diamond") || + result.InkDrawingNode.GetShapeName().Contains("Parallelogram") || + result.InkDrawingNode.GetShapeName().Contains("Square") || + result.InkDrawingNode.GetShapeName().Contains("Trapezoid")) && + Settings.InkToShape.IsInkToShapeRectangle == true) { + PointCollection p = new PointCollection(); + Point[] _p = new Point[]{}; + Dispatcher.Invoke(() => { + var arr = result.InkDrawingNode.HotPoints.ToArray(); + _p = arr; + }); + p = new PointCollection(_p); + if ((Math.Max(Math.Max(Math.Max(p[0].X, p[1].X), p[2].X), p[3].X) - + Math.Min(Math.Min(Math.Min(p[0].X, p[1].X), p[2].X), p[3].X) >= 100 || + Math.Max(Math.Max(Math.Max(p[0].Y, p[1].Y), p[2].Y), p[3].Y) - + Math.Min(Math.Min(Math.Min(p[0].Y, p[1].Y), p[2].Y), p[3].Y) >= 100) && + result.InkDrawingNode.HotPoints.Count == 4) { + //纠正垂直与水平关系 + var newPoints = FixPointsDirection(p[0], p[1]); + p[0] = newPoints[0]; + p[1] = newPoints[1]; + newPoints = FixPointsDirection(p[1], p[2]); + p[1] = newPoints[0]; + p[2] = newPoints[1]; + newPoints = FixPointsDirection(p[2], p[3]); + p[2] = newPoints[0]; + p[3] = newPoints[1]; + newPoints = FixPointsDirection(p[3], p[0]); + p[3] = newPoints[0]; + p[0] = newPoints[1]; + + var pointList = p.ToList(); + pointList.Add(p[0]); + var point = new StylusPointCollection(pointList); + var stroke = new Stroke(GenerateFakePressureRectangle(point)) { + DrawingAttributes = Dispatcher.Invoke(()=>inkCanvas.DefaultDrawingAttributes.Clone()) + }; + + Dispatcher.InvokeAsync(() => { + SetNewBackupOfStroke(); + _currentCommitType = CommitReason.ShapeRecognition; + inkCanvas.Strokes.Remove(result.InkDrawingNode.Strokes); + inkCanvas.Strokes.Add(stroke); + _currentCommitType = CommitReason.UserInput; + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + newStrokes = new StrokeCollection(); + }); + + } + } + } + catch { } + } + + var _t = new Thread(InkToShapeProcess); + _t.Start(); + } + + foreach (var stylusPoint in e.Stroke.StylusPoints) + //LogHelper.WriteLogToFile(stylusPoint.PressureFactor.ToString(), LogHelper.LogType.Info); + // 检查是否是压感笔书写 + //if (stylusPoint.PressureFactor != 0.5 && stylusPoint.PressureFactor != 0) + if ((stylusPoint.PressureFactor > 0.501 || stylusPoint.PressureFactor < 0.5) && + stylusPoint.PressureFactor != 0) + return; + + try { + if (e.Stroke.StylusPoints.Count > 3) { + var random = new Random(); + var _speed = GetPointSpeed( + e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint(), + e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint(), + e.Stroke.StylusPoints[random.Next(0, e.Stroke.StylusPoints.Count - 1)].ToPoint()); + + RandWindow.randSeed = (int)(_speed * 100000 * 1000); + } + } + catch { } + + switch (Settings.Canvas.InkStyle) { + case 1: + if (penType == 0) + try { + var stylusPoints = new StylusPointCollection(); + var n = e.Stroke.StylusPoints.Count - 1; + var s = ""; + + for (var i = 0; i <= n; i++) { + var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(), + e.Stroke.StylusPoints[i].ToPoint(), + e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint()); + s += speed.ToString() + "\t"; + var point = new StylusPoint(); + if (speed >= 0.25) + point.PressureFactor = (float)(0.5 - 0.3 * (Math.Min(speed, 1.5) - 0.3) / 1.2); + else if (speed >= 0.05) + point.PressureFactor = (float)0.5; + else + point.PressureFactor = (float)(0.5 + 0.4 * (0.05 - speed) / 0.05); + + point.X = e.Stroke.StylusPoints[i].X; + point.Y = e.Stroke.StylusPoints[i].Y; + stylusPoints.Add(point); + } + + e.Stroke.StylusPoints = stylusPoints; + } + catch { } + + break; + case 0: + if (penType == 0) + try { + var stylusPoints = new StylusPointCollection(); + var n = e.Stroke.StylusPoints.Count - 1; + var pressure = 0.1; + var x = 10; + if (n == 1) return; + if (n >= x) { + for (var i = 0; i < n - x; i++) { + var point = new StylusPoint(); + + point.PressureFactor = (float)0.5; + point.X = e.Stroke.StylusPoints[i].X; + point.Y = e.Stroke.StylusPoints[i].Y; + stylusPoints.Add(point); + } + + for (var i = n - x; i <= n; i++) { + var point = new StylusPoint(); + + point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure); + point.X = e.Stroke.StylusPoints[i].X; + point.Y = e.Stroke.StylusPoints[i].Y; + stylusPoints.Add(point); + } + } + else { + for (var i = 0; i <= n; i++) { + var point = new StylusPoint(); + + point.PressureFactor = (float)(0.4 * (n - i) / n + pressure); + point.X = e.Stroke.StylusPoints[i].X; + point.Y = e.Stroke.StylusPoints[i].Y; + stylusPoints.Add(point); + } + } + + e.Stroke.StylusPoints = stylusPoints; + } + catch { } + + break; + } + } + catch { } + + if (Settings.Canvas.FitToCurve == true) drawingAttributes.FitToCurve = true; + } + + private void SetNewBackupOfStroke() { + lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); + var whiteboardIndex = CurrentWhiteboardIndex; + if (currentMode == 0) whiteboardIndex = 0; + + strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection; + } + + public double GetDistance(Point point1, Point point2) { + return Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) + + (point1.Y - point2.Y) * (point1.Y - point2.Y)); + } + + public double GetPointSpeed(Point point1, Point point2, Point point3) { + return (Math.Sqrt((point1.X - point2.X) * (point1.X - point2.X) + + (point1.Y - point2.Y) * (point1.Y - point2.Y)) + + Math.Sqrt((point3.X - point2.X) * (point3.X - point2.X) + + (point3.Y - point2.Y) * (point3.Y - point2.Y))) + / 20; + } + + public Point[] FixPointsDirection(Point p1, Point p2) { + if (Math.Abs(p1.X - p2.X) / Math.Abs(p1.Y - p2.Y) > 8) { + //水平 + var x = Math.Abs(p1.Y - p2.Y) / 2; + if (p1.Y > p2.Y) { + p1.Y -= x; + p2.Y += x; + } + else { + p1.Y += x; + p2.Y -= x; + } + } + else if (Math.Abs(p1.Y - p2.Y) / Math.Abs(p1.X - p2.X) > 8) { + //垂直 + var x = Math.Abs(p1.X - p2.X) / 2; + if (p1.X > p2.X) { + p1.X -= x; + p2.X += x; + } + else { + p1.X += x; + p2.X -= x; + } + } + + return new Point[2] { p1, p2 }; + } + + public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points) { + if (Settings.InkToShape.IsInkToShapeNoFakePressureTriangle == true || 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)); + 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)); + return newPoint; + } + } + + public StylusPointCollection GenerateFakePressureRectangle(StylusPointCollection points) { + if (Settings.InkToShape.IsInkToShapeNoFakePressureRectangle == true || penType == 1) { + return points; + } + 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[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; + } + } + + public Point GetCenterPoint(Point point1, Point point2) { + return new Point((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2); + } + + public StylusPoint GetCenterPoint(StylusPoint point1, StylusPoint point2) { + return new StylusPoint((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2); + } + } +} \ No newline at end of file diff --git a/InkCanvasForClass/MainWindow_cs/MW_Storage.cs b/InkCanvasForClass/MainWindow_cs/MW_Storage.cs new file mode 100644 index 00000000..671c895e --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_Storage.cs @@ -0,0 +1,522 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Ink_Canvas.Helpers; +using JetBrains.Annotations; + +namespace Ink_Canvas { + public partial class MainWindow : PerformanceTransparentWin { + + public class StorageLocationItem { + public string Path { get; set; } + public ImageSource Icon { get; set; } + public string Title { get; set; } + public bool IsDrive { get; set; } + public DriveType DriveType { get; set; } + public string SelectItem { get; set; } + } + + public static long GetDirectorySize(System.IO.DirectoryInfo directoryInfo, bool recursive = true) + { + var startDirectorySize = default(long); + if (directoryInfo == null || !directoryInfo.Exists) + return startDirectorySize; //Return 0 while Directory does not exist. + + //Add size of files in the Current Directory to main size. + foreach (var fileInfo in directoryInfo.GetFiles()) + System.Threading.Interlocked.Add(ref startDirectorySize, fileInfo.Length); + + if (recursive) //Loop on Sub Direcotries in the Current Directory and Calculate it's files size. + System.Threading.Tasks.Parallel.ForEach(directoryInfo.GetDirectories(), (subDirectory) => + System.Threading.Interlocked.Add(ref startDirectorySize, GetDirectorySize(subDirectory, recursive))); + + return startDirectorySize; + } + + public async Task GetDirectorySizeAsync(System.IO.DirectoryInfo directoryInfo, bool recursive = true) { + var size = await Task.Run(()=> GetDirectorySize(directoryInfo, recursive)); + return size; + } + + private static string FormatBytes(long bytes) { + string[] Suffix = { "B", "KB", "MB", "GB", "TB" }; + int i; double dblSByte = bytes; + for (i = 0; i < Suffix.Length && bytes >= 1024; i++, bytes /= 1024) { + dblSByte = bytes / 1024.0; + } + return String.Format("{0:0.##} {1}", dblSByte, Suffix[i]); + } + + private ObservableCollection storageLocationItems = + new ObservableCollection() { }; + + private void UpdateStorageLocations() { + DriveInfo[] allDrives = DriveInfo.GetDrives(); + var fixedDrives = new List() { }; + var integratedFolders = new List() { }; + storageLocationItems.Clear(); + foreach (var driveInfo in allDrives) { + if (driveInfo.DriveType == DriveType.Fixed) { + if (driveInfo.Name.Contains("C") || !driveInfo.IsReady) continue; + fixedDrives.Add(new StorageLocationItem() { + Path = driveInfo.Name + "InkCanvasForClass", + Title = driveInfo.Name.Substring(0,1) + "盘 ", + Icon = new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/disk-drive.png")), + DriveType = driveInfo.DriveType, + IsDrive = true, + SelectItem = "d"+driveInfo.Name.Substring(0,1).ToLower() + }); + } + } + var docfolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + integratedFolders.Add(new StorageLocationItem() { + Path = (docfolder.EndsWith("\\") ? docfolder.Substring(0, docfolder.Length - 1) : docfolder) + "\\InkCanvasForClass", + Title = "“文档”文件夹 ", + IsDrive = false, + Icon = new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/documents-folder.png")), + SelectItem = "fw" + }); + var runfolder = AppDomain.CurrentDomain.BaseDirectory; + integratedFolders.Add(new StorageLocationItem() { + Path = (runfolder.EndsWith("\\") ? runfolder.Substring(0, runfolder.Length - 1) : runfolder) + "\\InkCanvasForClass", + Title = "icc安装目录 ", + IsDrive = false, + Icon = new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/folder.png")), + SelectItem = "fr" + }); + var usrfolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + integratedFolders.Add(new StorageLocationItem() { + Path = (usrfolder.EndsWith("\\") ? usrfolder.Substring(0, usrfolder.Length - 1) : usrfolder) + "\\InkCanvasForClass", + Title = "当前用户目录 ", + IsDrive = false, + Icon = new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/user-folder.png")), + SelectItem = "fu" + }); + var dskfolder = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + integratedFolders.Add(new StorageLocationItem() { + Path = (dskfolder.EndsWith("\\") ? dskfolder.Substring(0, dskfolder.Length - 1) : dskfolder) + "\\InkCanvasForClass", + Title = "“桌面”文件夹 ", + IsDrive = false, + Icon = new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/desktop-folder.png")), + SelectItem = "fd" + }); + + foreach (var i in fixedDrives) storageLocationItems.Add(i); + foreach (var i in integratedFolders) storageLocationItems.Add(i); + storageLocationItems.Add(new StorageLocationItem() { + Path = "", + Title = "自定义... ", + IsDrive = false, + Icon = new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/folder.png")), + SelectItem = "c-" + }); + } + + private bool isChangingUserStorageSelectionProgramically = false; + + private void UpdateUserStorageSelection() { + // 先获取磁盘信息 + DriveInfo[] allDrives = DriveInfo.GetDrives(); + var fixedDrives = new List() { }; + foreach (var driveInfo in allDrives) { + if (driveInfo.Name.Contains("C") || !driveInfo.IsReady) continue; + fixedDrives.Add("d"+driveInfo.Name.Substring(0,1).ToLower()); + } + + var integratedFolders = new List() { + "fw", "fr", "fu", "fd" // fw - folder wendang ; fd - folder desktop ; fu - folder user ; fr - folder running + }; + if (Settings.Storage.StorageLocation.Substring(0, 1) == "d") { + if (fixedDrives.Count == 0) { + Settings.Storage.StorageLocation = "fw"; + SaveSettingsToFile(); + ComboBoxStoragePath.SelectedIndex = 0; + } else if (fixedDrives.Contains(Settings.Storage.StorageLocation)) { + ComboBoxStoragePath.SelectedIndex = fixedDrives.IndexOf(Settings.Storage.StorageLocation); + } else { + ComboBoxStoragePath.SelectedIndex = 0; + Settings.Storage.StorageLocation = fixedDrives[0]; + SaveSettingsToFile(); + } + } else if (Settings.Storage.StorageLocation.Substring(0, 1) == "f") { + if (integratedFolders.Contains(Settings.Storage.StorageLocation)) { + ComboBoxStoragePath.SelectedIndex = fixedDrives.Count + integratedFolders.IndexOf(Settings.Storage.StorageLocation); + } else { + ComboBoxStoragePath.SelectedIndex = fixedDrives.Count; + Settings.Storage.StorageLocation = "fw"; + SaveSettingsToFile(); + } + } else if (Settings.Storage.StorageLocation.Substring(0, 1) == "c") { + ComboBoxStoragePath.SelectedIndex = storageLocationItems.Count - 1; + } + + if (isLoaded) CustomStorageLocationGroup.Visibility = Settings.Storage.StorageLocation == "c-" ? Visibility.Visible : Visibility.Collapsed; + if (isLoaded) CustomStorageLocation.Text = Settings.Storage.UserStorageLocation; + } + + private void ComboBoxStoragePath_DropDownOpened(object sender, EventArgs e) { + isChangingUserStorageSelectionProgramically = true; + UpdateStorageLocations(); + UpdateUserStorageSelection(); + isChangingUserStorageSelectionProgramically = false; + } + + private async Task GetDirectoryFilesCount(string path) { + var count = await Task.Run(() => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Length); + return count; + } + + private void InitStorageFoldersStructure(string dirPath) { + string path; + if (storageLocationItems[ComboBoxStoragePath.SelectedIndex].SelectItem == "c-") { + if (Settings.Storage.StorageLocation != "c-") { + var si = Settings.Storage.StorageLocation; + var item = storageLocationItems.Where(i => i.SelectItem == si).ToArray()[0]; + path = item.Path; + } else { + if (Settings.Storage.UserStorageLocation != "") { + path = new DirectoryInfo(Settings.Storage.UserStorageLocation).FullName; + } else { + var item = storageLocationItems.Where(i => i.SelectItem == "fr").ToArray()[0]; + path = item.Path; + Settings.Storage.StorageLocation = "fr"; + SaveSettingsToFile(); + UpdateStorageLocations(); + UpdateUserStorageSelection(); + } + } + } else { + if (dirPath == null) { + var si = Settings.Storage.StorageLocation; + var item = storageLocationItems.Where(i => i.SelectItem == si).ToArray()[0]; + path = item.Path; + } else path = dirPath; + } + var basePath = new DirectoryInfo(path); + var autoSavedInkPath = new DirectoryInfo(path+"\\AutoSavedInk"); + var autoSavedSnapshotPath = new DirectoryInfo(path+"\\AutoSavedSnapshot"); + var exportedInkPath = new DirectoryInfo(path+"\\ExportedInk"); + var quotedPhotosPath = new DirectoryInfo(path+"\\QuotedPhotos"); + var cachesPath = new DirectoryInfo(path+"\\caches"); + var paths = new DirectoryInfo[] { + basePath, autoSavedInkPath, autoSavedSnapshotPath, exportedInkPath, quotedPhotosPath, cachesPath + }; + foreach (var di in paths) { + if (!di.Exists) di.Create(); + } + } + + private bool isAnalyzingStorageInfo = false; + + private async void StartAnalyzeStorage(bool forceAnalyze = false) { + if (isAnalyzingStorageInfo && !forceAnalyze) return; + isAnalyzingStorageInfo = true; + var item = storageLocationItems[ComboBoxStoragePath.SelectedIndex]; + StorageAnalazeWaitingGroup.Visibility = Visibility.Visible; + StorageAnalazeGroup.Visibility = Visibility.Collapsed; + + string path; + var runfolder = AppDomain.CurrentDomain.BaseDirectory; + + if (item.SelectItem != "c-") { + path = item.Path; + } else if (Settings.Storage.UserStorageLocation != "") { + path = Settings.Storage.UserStorageLocation; + } else { + path = (runfolder.EndsWith("\\") ? runfolder.Substring(0, runfolder.Length - 1) : runfolder) + + "\\InkCanvasForClass"; + } + + StorageNowLocationTextBlock.Text = $"当前位置:{path}"; + DriveInfo[] allDrives = DriveInfo.GetDrives(); + var driveArr = allDrives.Where((info, i) => info.Name.Substring(0,1)==path.Substring(0,1)).ToArray(); + if (driveArr.Length > 0) { + StorageDiskUsageTextBlock.Visibility = Visibility.Visible; + var freeSpace = driveArr[0].TotalFreeSpace; + var usedSpace = driveArr[0].TotalSize - driveArr[0].TotalFreeSpace; + StorageDiskUsageTextBlock.Text = $"磁盘使用情况:已用 {FormatBytes(usedSpace)}、剩余 {FormatBytes(freeSpace)}"; + var dirsize = await GetDirectorySizeAsync(new DirectoryInfo(path)); + var formatedDirSize = FormatBytes(dirsize); + var dirFilecount = await GetDirectoryFilesCount(path); + StorageDirectoryUsageTextBlock.Text = $"目录占用情况:已用 {formatedDirSize},共 {dirFilecount} 个文件"; + var usedBorderWidth = Math.Round(388 * ((double)usedSpace / (double)(driveArr[0].TotalSize)), 1); + var ICCUsedBorderWidth = Math.Round(usedBorderWidth * ((double)dirsize / (double)usedSpace), 1); + usedBorderWidth = usedBorderWidth - ICCUsedBorderWidth; + ICCDirectoryStorageAnalyzeGroup.Visibility = dirsize == 0 ? Visibility.Collapsed : Visibility.Visible; + DiskUsageUsedSpaceBorder.Width = usedBorderWidth; + DiskUsageICCSpaceBorder.Width = ICCUsedBorderWidth; + var asiSize = await GetDirectorySizeAsync(new DirectoryInfo(path + "\\AutoSavedInk")); + var assSize = await GetDirectorySizeAsync(new DirectoryInfo(path + "\\AutoSavedSnapshot")); + var eiSize = await GetDirectorySizeAsync(new DirectoryInfo(path + "\\ExportedInk")); + var qpSize = await GetDirectorySizeAsync(new DirectoryInfo(path + "\\QuotedPhotos")); + var cachesSize = await GetDirectorySizeAsync(new DirectoryInfo(path + "\\caches")); + ClearCacheFilesButton.IsEnabled = cachesSize != 0; + ClearAutoSavedSnapshotButton.IsEnabled = assSize != 0; + StorageDirectoryAutoSavedInkUsageBorder.Width = + Math.Round(388 * ((double)asiSize / (double)dirsize), 1); + StorageDirectoryAutoSavedSnapshotUsageBorder.Width = + Math.Round(388 * ((double)assSize / (double)dirsize), 1); + StorageDirectoryExportedInkUsageBorder.Width = Math.Round(388 * ((double)eiSize / (double)dirsize), 1); + StorageDirectoryQuotedPhotoUsageBorder.Width = Math.Round(388 * ((double)qpSize / (double)dirsize), 1); + StorageDirectoryCachesUsageBorder.Width = Math.Round(388 * ((double)cachesSize / (double)dirsize), 1); + StorageAutoSavedInkDescription.Text = + $"{Math.Round(100 * ((double)asiSize / (double)dirsize), 1)}% 、{FormatBytes(asiSize)}"; + StorageAutoSavedSnapshotDescription.Text = + $"{Math.Round(100 * ((double)assSize / (double)dirsize), 1)}% 、{FormatBytes(assSize)}"; + StorageExportedInkDescription.Text = + $"{Math.Round(100 * ((double)eiSize / (double)dirsize), 1)}% 、{FormatBytes(eiSize)}"; + StorageQuotedPhotosDescription.Text = + $"{Math.Round(100 * ((double)qpSize / (double)dirsize), 1)}% 、{FormatBytes(qpSize)}"; + StorageCachesDescription.Text = + $"{Math.Round(100 * ((double)cachesSize / (double)dirsize), 1)}% 、{FormatBytes(cachesSize)}"; + StorageAnalazeWaitingGroup.Visibility = Visibility.Collapsed; + StorageAnalazeGroup.Visibility = Visibility.Visible; + isAnalyzingStorageInfo = false; + } else isAnalyzingStorageInfo = false; + } + + private void ClearCacheFilesButton_Clicked(object sender, RoutedEventArgs e) { + var di = new DirectoryInfo(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path+"\\caches"); + try { + Directory.Delete(di.FullName, true); + ShowNewToast("清理缓存成功!", MW_Toast.ToastType.Success, 2000); + } + catch (Exception ex) { + ShowNewToast($"清理缓存失败!{ex.Message}", MW_Toast.ToastType.Error, 2000); + } + InitStorageFoldersStructure(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path); + StartAnalyzeStorage(); + } + + private void ClearAutoSavedSnapshotButton_Clicked(object sender, RoutedEventArgs e) { + var di = new DirectoryInfo(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path+"\\AutoSavedSnapshot"); + try { + Directory.Delete(di.FullName, true); + ShowNewToast("清理自动保存的截图成功!", MW_Toast.ToastType.Success, 2000); + } + catch (Exception ex) { + ShowNewToast($"清理自动保存的截图失败!{ex.Message}", MW_Toast.ToastType.Error, 2000); + } + InitStorageFoldersStructure(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path); + StartAnalyzeStorage(); + } + + + private DirectoryInfo GetDirectory(string type) { + var path = ""; + if (Settings.Storage.StorageLocation == "c-" && Settings.Storage.UserStorageLocation != "") + path = Settings.Storage.UserStorageLocation; + else path = storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path; + var autoSavedInkPath = new DirectoryInfo(path+"\\AutoSavedInk"); + var autoSavedSnapshotPath = new DirectoryInfo(path+"\\AutoSavedSnapshot"); + var exportedInkPath = new DirectoryInfo(path +"\\ExportedInk"); + var quotedPhotosPath = new DirectoryInfo(path +"\\QuotedPhotos"); + var cachesPath = new DirectoryInfo(path +"\\caches"); + if (type == "autosaveink") return autoSavedInkPath; + if (type == "autosavesnapshot") return autoSavedSnapshotPath; + if (type == "exportedink") return exportedInkPath; + if (type == "quotedphotos") return quotedPhotosPath; + if (type == "caches") return cachesPath; + return new DirectoryInfo(""); + } + + private DirectoryInfo GetDirectoryInfoByIndex(int index) { + var path = ""; + if (Settings.Storage.StorageLocation == "c-" && Settings.Storage.UserStorageLocation != "") + path = Settings.Storage.UserStorageLocation; + else path = storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path; + var autoSavedInkPath = new DirectoryInfo(path +"\\AutoSavedInk"); + var autoSavedSnapshotPath = new DirectoryInfo(path +"\\AutoSavedSnapshot"); + var exportedInkPath = new DirectoryInfo(path +"\\ExportedInk"); + var quotedPhotosPath = new DirectoryInfo(path +"\\QuotedPhotos"); + var cachesPath = new DirectoryInfo(path +"\\caches"); + return (new DirectoryInfo[] + { autoSavedInkPath, quotedPhotosPath, exportedInkPath, cachesPath, autoSavedSnapshotPath })[index]; + } + + private Border lastStorageJumpToFolderBorderDown = null; + + private void StorageJumpToFolderBtn_MouseDown(object sender, MouseButtonEventArgs e) { + if (lastStorageJumpToFolderBorderDown != null) return; + lastStorageJumpToFolderBorderDown = (Border)sender; + lastStorageJumpToFolderBorderDown.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); + } + + private void StorageJumpToFolderBtn_MouseUp(object sender, MouseButtonEventArgs e) { + if (lastStorageJumpToFolderBorderDown == null || lastStorageJumpToFolderBorderDown != (Border)sender) return; + var bd = (Border)sender; + StorageJumpToFolderBtn_MouseLeave(sender,null); + var di = GetDirectoryInfoByIndex(Int32.Parse(bd.Name[bd.Name.Length - 1].ToString())); + Process.Start("explorer.exe", di.FullName); + } + + private void StorageJumpToFolderBtn_MouseLeave(object sender, MouseEventArgs e) { + if (lastStorageJumpToFolderBorderDown == null || lastStorageJumpToFolderBorderDown != (Border)sender) return; + lastStorageJumpToFolderBorderDown.Background = new SolidColorBrush(Colors.Transparent); + lastStorageJumpToFolderBorderDown = null; + } + + private void InitStorageManagementModule() { + ComboBoxStoragePath.ItemsSource = storageLocationItems; + ComboBoxStoragePath.DropDownOpened += ComboBoxStoragePath_DropDownOpened; + ComboBoxStoragePath.SelectionChanged += ComboBoxStoragePath_OnSelectionChanged; + isChangingUserStorageSelectionProgramically = true; + UpdateStorageLocations(); + UpdateUserStorageSelection(); + isChangingUserStorageSelectionProgramically = false; + HandleUserCustomStorageLocation(); + InitStorageFoldersStructure(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path); + StartAnalyzeStorage(); + var sb = new Border[] { + StorageJumpToFolderBtn1, StorageJumpToFolderBtn2, StorageJumpToFolderBtn3, StorageJumpToFolderBtn4, + StorageJumpToFolderBtn5, + }; + foreach (var btn in sb) { + btn.MouseUp += StorageJumpToFolderBtn_MouseUp; + btn.MouseDown += StorageJumpToFolderBtn_MouseDown; + btn.MouseLeave += StorageJumpToFolderBtn_MouseLeave; + } + CustomStorageLocationGroup.Visibility = ((StorageLocationItem)ComboBoxStoragePath.SelectedItem).SelectItem == "c-" ? Visibility.Visible : Visibility.Collapsed; + CustomStorageLocationCheckPanel.Visibility = ((StorageLocationItem)ComboBoxStoragePath.SelectedItem).SelectItem == "c-" ? Visibility.Visible : Visibility.Collapsed; + CustomStorageLocation.Text = Settings.Storage.UserStorageLocation; + } + + private async void HandleUserCustomStorageLocation([CanBeNull] string dirPath = null) { + var path = dirPath ?? (Settings.Storage.UserStorageLocation != "" + ? Settings.Storage.UserStorageLocation + : null); + if (path != null) { + CustomStorageLocationGroup.Visibility = Visibility.Visible; + CustomStorageLocationCheckPanel.Visibility = Visibility.Visible; + CustomStorageNonRemovableDriveTip.Visibility = Visibility.Visible; + CustomStorageWritableTip.Visibility = Visibility.Collapsed; + CustomStorageLocation.Text = ""; + + CustomStorageLocation.Text = new DirectoryInfo(path).FullName; + + // 加载动画 + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).Source = + CustomStorageLocationCheckPanel.FindResource("LoadingIcon") as DrawingImage; + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).RenderTransformOrigin = new Point(0.5, 0.5); + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).RenderTransform = new RotateTransform(0, 8, 8); + var tg = new EventTrigger(LoadedEvent); + var sb = new Storyboard(); + var da = new DoubleAnimation { + To = -360, + Duration = new Duration(new TimeSpan(0, 0, 1)), + RepeatBehavior = RepeatBehavior.Forever, + }; + sb.Children.Add(da); + Storyboard.SetTargetProperty(da, new PropertyPath("(Image.RenderTransform).(RotateTransform.Angle)")); + tg.Actions.Add(new BeginStoryboard() { + Storyboard = sb + }); + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).Triggers.Add(tg); + + // 检测是否在非可移动存储上 + var drive = new DirectoryInfo(path).FullName.Substring(0, 1); + var allDrives = DriveInfo.GetDrives(); + var ds = allDrives.Where(info => info.Name.StartsWith(drive) && info.DriveType == DriveType.Fixed); + if (ds.Any()) { + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).Source = + CustomStorageLocationCheckPanel.FindResource("SuccessIcon") as DrawingImage; + ((TextBlock)CustomStorageNonRemovableDriveTip.Children[1]).Text = "非可移动存储介质"; + } else { + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).Source = + CustomStorageLocationCheckPanel.FindResource("FailedIcon") as DrawingImage; + ((TextBlock)CustomStorageNonRemovableDriveTip.Children[1]).Text = "路径在可移动存储介质上"; + } + sb.Stop(); + ((Image)CustomStorageNonRemovableDriveTip.Children[0]).Triggers.Remove(tg); + + // 加载动画2 + CustomStorageWritableTip.Visibility = Visibility.Visible; + ((Image)CustomStorageWritableTip.Children[0]).Source = + CustomStorageLocationCheckPanel.FindResource("LoadingIcon") as DrawingImage; + ((Image)CustomStorageWritableTip.Children[0]).RenderTransformOrigin = new Point(0.5, 0.5); + ((Image)CustomStorageWritableTip.Children[0]).RenderTransform = new RotateTransform(0, 8, 8); + var tg2 = new EventTrigger(LoadedEvent); + var sb2 = new Storyboard(); + var da2 = new DoubleAnimation { + To = -360, + Duration = new Duration(new TimeSpan(0, 0, 1)), + RepeatBehavior = RepeatBehavior.Forever, + }; + sb.Children.Add(da2); + Storyboard.SetTargetProperty(da2, new PropertyPath("(Image.RenderTransform).(RotateTransform.Angle)")); + tg.Actions.Add(new BeginStoryboard() { + Storyboard = sb2 + }); + ((Image)CustomStorageWritableTip.Children[0]).Triggers.Add(tg2); + + // 检查是否可写 + var result = await DirectoryUtils.IsWritableAsync(path); + if (result) { + ((Image)CustomStorageWritableTip.Children[0]).Source = + CustomStorageLocationCheckPanel.FindResource("SuccessIcon") as DrawingImage; + ((TextBlock)CustomStorageWritableTip.Children[1]).Text = "目录可写"; + } else { + ((Image)CustomStorageWritableTip.Children[0]).Source = + CustomStorageLocationCheckPanel.FindResource("FailedIcon") as DrawingImage; + ((TextBlock)CustomStorageWritableTip.Children[1]).Text = "目录权限错误"; + } + sb2.Stop(); + ((Image)CustomStorageWritableTip.Children[0]).Triggers.Remove(tg2); + + if (ds.Any() && result) { + Settings.Storage.StorageLocation = storageLocationItems[ComboBoxStoragePath.SelectedIndex].SelectItem; + Settings.Storage.UserStorageLocation = new DirectoryInfo(path).FullName; + SaveSettingsToFile(); + InitStorageFoldersStructure(null); + StartAnalyzeStorage(); + } + } + } + + private void ComboBoxStoragePath_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { + if (isChangingUserStorageSelectionProgramically) return; + if (!isLoaded) return; + if (storageLocationItems[ComboBoxStoragePath.SelectedIndex].SelectItem == "c-") { + if (Settings.Storage.UserStorageLocation == "") { + var folderBrowser = new System.Windows.Forms.FolderBrowserDialog(); + folderBrowser.Description = "请选择ICC的自定义存储目录。不支持存储到固定硬盘除外的设备和网络地址上!"; + folderBrowser.ShowDialog(); + if (folderBrowser.SelectedPath.Length > 0) + HandleUserCustomStorageLocation(folderBrowser.SelectedPath); + else { + var si = Settings.Storage.StorageLocation; + var item = storageLocationItems.Where(i => i.SelectItem == si).ToArray()[0]; + var index = storageLocationItems.IndexOf(item); + ComboBoxStoragePath.SelectedIndex = index; + } + } else HandleUserCustomStorageLocation(); + } else { + if (isLoaded) CustomStorageLocationGroup.Visibility = ((StorageLocationItem)ComboBoxStoragePath.SelectedItem).SelectItem == "c-" ? Visibility.Visible : Visibility.Collapsed; + if (isLoaded) CustomStorageLocation.Text = Settings.Storage.UserStorageLocation; + Settings.Storage.StorageLocation = storageLocationItems[ComboBoxStoragePath.SelectedIndex].SelectItem; + SaveSettingsToFile(); + InitStorageFoldersStructure(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path); + StartAnalyzeStorage(); + } + } + + private void CustomStorageLocationButton_Click(object sender, RoutedEventArgs e) { + var folderBrowser = new System.Windows.Forms.FolderBrowserDialog(); + folderBrowser.Description = "请选择ICC的自定义存储目录。不支持存储到固定硬盘除外的设备和网络地址上!"; + folderBrowser.ShowDialog(); + if (folderBrowser.SelectedPath.Length > 0) HandleUserCustomStorageLocation(folderBrowser.SelectedPath); + } + } +} diff --git a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs b/InkCanvasForClass/MainWindow_cs/MW_TimeMachine.cs similarity index 76% rename from Ink Canvas/MainWindow_cs/MW_TimeMachine.cs rename to InkCanvasForClass/MainWindow_cs/MW_TimeMachine.cs index 6642350e..2cbca5f0 100644 --- a/Ink Canvas/MainWindow_cs/MW_TimeMachine.cs +++ b/InkCanvasForClass/MainWindow_cs/MW_TimeMachine.cs @@ -1,15 +1,16 @@ -using System; +using Ink_Canvas.Helpers; using System.Collections.Generic; -using System.Diagnostics; +using System; using System.Windows; using System.Windows.Controls; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; -using Ink_Canvas.Helpers; +using System.Diagnostics; +using System.Windows.Media.Imaging; namespace Ink_Canvas { - public partial class MainWindow : Window { + public partial class MainWindow : PerformanceTransparentWin { private enum CommitReason { UserInput, CodeInput, @@ -20,7 +21,7 @@ namespace Ink_Canvas { } private CommitReason _currentCommitType = CommitReason.UserInput; - private bool IsEraseByPoint => inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint; + private bool IsEraseByPoint => SelectedMode == ICCToolsEnum.EraseByGeometryMode; private StrokeCollection ReplacedStroke; private StrokeCollection AddedStroke; private StrokeCollection CuboidStrokeCollection; @@ -32,7 +33,7 @@ namespace Ink_Canvas { private Dictionary> DrawingAttributesHistory = new Dictionary>(); - private Dictionary> DrawingAttributesHistoryFlag = new Dictionary> { + private Dictionary> DrawingAttributesHistoryFlag = new Dictionary>() { { DrawingAttributeIds.Color, new List() }, { DrawingAttributeIds.DrawingFlags, new List() }, { DrawingAttributeIds.IsHighlighter, new List() }, @@ -44,10 +45,10 @@ namespace Ink_Canvas { private TimeMachine timeMachine = new TimeMachine(); - private void ApplyHistoryToCanvas(TimeMachineHistory item, InkCanvas applyCanvas = null) { + private void ApplyHistoryToCanvas(TimeMachineHistory item, IccInkCanvas applyCanvas = null) { _currentCommitType = CommitReason.CodeInput; var canvas = inkCanvas; - if (applyCanvas != null && applyCanvas is InkCanvas) { + if (applyCanvas != null && applyCanvas is IccInkCanvas) { canvas = applyCanvas; } @@ -129,46 +130,13 @@ namespace Ink_Canvas { if (canvas.Strokes.Contains(currentStroke)) canvas.Strokes.Remove(currentStroke); } - } else if (item.CommitType == TimeMachineHistoryType.ElementInsert) { - // 使用传入的canvas参数,而不是总是使用inkCanvas - var targetCanvas = canvas ?? inkCanvas; - - if (item.StrokeHasBeenCleared) { - // Undo: 移除元素 - if (item.InsertedElement != null && targetCanvas.Children.Contains(item.InsertedElement)) - targetCanvas.Children.Remove(item.InsertedElement); - } else { - // Redo: 添加元素 - if (item.InsertedElement != null && !targetCanvas.Children.Contains(item.InsertedElement)) { - targetCanvas.Children.Add(item.InsertedElement); - - // 重新绑定事件处理器(仅对主画布) - if (targetCanvas == inkCanvas) { - if (item.InsertedElement is Image img) { - img.MouseDown -= UIElement_MouseDown; - img.MouseDown += UIElement_MouseDown; - img.IsManipulationEnabled = true; - - // 重新应用CenterAndScaleElement变换 - CenterAndScaleElement(img); - } else if (item.InsertedElement is MediaElement media) { - media.MouseDown -= UIElement_MouseDown; - media.MouseDown += UIElement_MouseDown; - media.IsManipulationEnabled = true; - - // 重新应用CenterAndScaleElement变换 - CenterAndScaleElement(media); - } - } - } - } } _currentCommitType = CommitReason.UserInput; } private StrokeCollection ApplyHistoriesToNewStrokeCollection(TimeMachineHistory[] items) { - InkCanvas fakeInkCanv = new InkCanvas { + IccInkCanvas fakeInkCanv = new IccInkCanvas() { Width = inkCanvas.ActualWidth, Height = inkCanvas.ActualHeight, EditingMode = InkCanvasEditingMode.None, @@ -176,52 +144,30 @@ namespace Ink_Canvas { if (items != null && items.Length > 0) { foreach (var timeMachineHistory in items) { - // 只处理笔画历史,不处理图片元素历史 - // 因为页面预览只需要显示笔画,图片元素会影响主画布 - if (timeMachineHistory.CommitType != TimeMachineHistoryType.ElementInsert) { - ApplyHistoryToCanvas(timeMachineHistory, fakeInkCanv); - } + ApplyHistoryToCanvas(timeMachineHistory, fakeInkCanv); } } return fakeInkCanv.Strokes; } - // 新增:获取页面的所有图片元素 - private List GetPageImageElements(TimeMachineHistory[] items) { - var imageElements = new List(); - - if (items != null && items.Length > 0) { - foreach (var timeMachineHistory in items) { - if (timeMachineHistory.CommitType == TimeMachineHistoryType.ElementInsert && - timeMachineHistory.InsertedElement != null && - !timeMachineHistory.StrokeHasBeenCleared) { - imageElements.Add(timeMachineHistory.InsertedElement); - } - } - } - - return imageElements; - } - private void TimeMachine_OnUndoStateChanged(bool status) { - var result = status ? Visibility.Visible : Visibility.Collapsed; - BtnUndo.Visibility = result; - BtnUndo.IsEnabled = status; + SymbolIconUndo.IsEnabled = status; } private void TimeMachine_OnRedoStateChanged(bool status) { - var result = status ? Visibility.Visible : Visibility.Collapsed; - BtnRedo.Visibility = result; - BtnRedo.IsEnabled = status; + SymbolIconRedo.IsEnabled = status; } + private bool _mouseGesturingPrevious = false; + private void StrokesOnStrokesChanged(object sender, StrokeCollectionChangedEventArgs e) { if (!isHidingSubPanelsWhenInking) { isHidingSubPanelsWhenInking = true; HideSubPanels(); // 书写时自动隐藏二级菜单 } + foreach (var stroke in e?.Removed) { stroke.StylusPointsChanged -= Stroke_StylusPointsChanged; stroke.StylusPointsReplaced -= Stroke_StylusPointsReplaced; @@ -246,23 +192,24 @@ namespace Ink_Canvas { return; } - if (e.Added.Count != 0) - { + if (e.Added.Count != 0) { if (_currentCommitType == CommitReason.ShapeRecognition) { timeMachine.CommitStrokeShapeHistory(ReplacedStroke, e.Added); ReplacedStroke = null; return; + } else { + timeMachine.CommitStrokeUserInputHistory(e.Added); + return; } - - timeMachine.CommitStrokeUserInputHistory(e.Added); - return; } if (e.Removed.Count != 0) { if (_currentCommitType == CommitReason.ShapeRecognition) { ReplacedStroke = e.Removed; + return; } else if (!IsEraseByPoint || _currentCommitType == CommitReason.ClearingCanvas) { timeMachine.CommitStrokeEraseHistory(e.Removed); + return; } } } @@ -311,10 +258,12 @@ namespace Ink_Canvas { } private void Stroke_StylusPointsReplaced(object sender, StylusPointsReplacedEventArgs e) { + if (isMouseGesturing) return; StrokeInitialHistory[sender as Stroke] = e.NewStylusPoints.Clone(); } private void Stroke_StylusPointsChanged(object sender, EventArgs e) { + if (isMouseGesturing) return; var selectedStrokes = inkCanvas.GetSelectedStrokes(); var count = selectedStrokes.Count; if (count == 0) count = inkCanvas.Strokes.Count; diff --git a/Ink Canvas/MainWindow_cs/MW_Timer.cs b/InkCanvasForClass/MainWindow_cs/MW_Timer.cs similarity index 56% rename from Ink Canvas/MainWindow_cs/MW_Timer.cs rename to InkCanvasForClass/MainWindow_cs/MW_Timer.cs index c620b848..1245115e 100644 --- a/Ink Canvas/MainWindow_cs/MW_Timer.cs +++ b/InkCanvasForClass/MainWindow_cs/MW_Timer.cs @@ -1,17 +1,11 @@ -using System; +using Ink_Canvas.Helpers; +using System; using System.ComponentModel; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using System.Timers; using System.Windows; -using System.Windows.Controls; -using Ink_Canvas.Helpers; +using MessageBox = System.Windows.MessageBox; namespace Ink_Canvas { public class TimeViewModel : INotifyPropertyChanged { @@ -45,53 +39,22 @@ namespace Ink_Canvas { } } - public partial class MainWindow : Window { + public partial class MainWindow : PerformanceTransparentWin { private Timer timerCheckPPT = new Timer(); private Timer timerKillProcess = new Timer(); private Timer timerCheckAutoFold = new Timer(); - private string AvailableLatestVersion; + private string AvailableLatestVersion = null; private Timer timerCheckAutoUpdateWithSilence = new Timer(); - private bool isHidingSubPanelsWhenInking; // 避免书写时触发二次关闭二级菜单导致动画不连续 + private bool isHidingSubPanelsWhenInking = false; // 避免书写时触发二次关闭二级菜单导致动画不连续 private Timer timerDisplayTime = new Timer(); private Timer timerDisplayDate = new Timer(); private TimeViewModel nowTimeVM = new TimeViewModel(); - private async Task GetNetworkTimeAsync() - { - try - { - const string ntpServer = "ntp.ntsc.ac.cn"; - var ntpData = new byte[48]; - ntpData[0] = 0x1B; - var addresses = await Dns.GetHostAddressesAsync(ntpServer); - var ipEndPoint = new IPEndPoint(addresses[0], 123); - using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) - { - socket.ReceiveTimeout = 2000; - socket.Connect(ipEndPoint); - await Task.Factory.FromAsync(socket.BeginSend(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndSend); - await Task.Factory.FromAsync(socket.BeginReceive(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndReceive); - } - const byte serverReplyTime = 40; - ulong intPart = BitConverter.ToUInt32(ntpData.Skip(serverReplyTime).Take(4).Reverse().ToArray(), 0); - ulong fractPart = BitConverter.ToUInt32(ntpData.Skip(serverReplyTime + 4).Take(4).Reverse().ToArray(), 0); - var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L); - var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds); - return networkDateTime.ToLocalTime(); - } - catch - { - return DateTime.Now; - } - } - - // 修改InitTimers方法中的初始时间和日期格式 private void InitTimers() { - // PPT检查现在由PPTManager处理,不再需要定时器 - // timerCheckPPT.Elapsed += TimerCheckPPT_Elapsed; - // timerCheckPPT.Interval = 500; + timerCheckPPT.Elapsed += TimerCheckPPT_Elapsed; + timerCheckPPT.Interval = 500; timerKillProcess.Elapsed += TimerKillProcess_Elapsed; timerKillProcess.Interval = 2000; timerCheckAutoFold.Elapsed += timerCheckAutoFold_Elapsed; @@ -100,31 +63,23 @@ namespace Ink_Canvas { timerCheckAutoUpdateWithSilence.Interval = 1000 * 60 * 10; WaterMarkTime.DataContext = nowTimeVM; WaterMarkDate.DataContext = nowTimeVM; - timerDisplayTime.Elapsed += async (s, e) => await TimerDisplayTime_ElapsedAsync(); + timerDisplayTime.Elapsed += TimerDisplayTime_Elapsed; timerDisplayTime.Interval = 1000; timerDisplayTime.Start(); timerDisplayDate.Elapsed += TimerDisplayDate_Elapsed; timerDisplayDate.Interval = 1000 * 60 * 60 * 1; timerDisplayDate.Start(); timerKillProcess.Start(); - nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd"); - nowTimeVM.nowTime = DateTime.Now.ToString("tt hh'时'mm'分'ss'秒'"); + nowTimeVM.nowDate = DateTime.Now.ToShortDateString().ToString(); + nowTimeVM.nowTime = DateTime.Now.ToShortTimeString().ToString(); } - // 修改TimerDisplayTime_ElapsedAsync方法中的时间格式 - private async Task TimerDisplayTime_ElapsedAsync() - { - DateTime now = await GetNetworkTimeAsync(); - // 只更新时间,日期由原有逻辑定时更新即可 - Dispatcher.Invoke(() => - { - nowTimeVM.nowTime = now.ToString("tt hh'时'mm'分'ss'秒'"); - }); + private void TimerDisplayTime_Elapsed(object sender, ElapsedEventArgs e) { + nowTimeVM.nowTime = DateTime.Now.ToShortTimeString().ToString(); } - // 修改TimerDisplayDate_Elapsed方法中的日期格式 private void TimerDisplayDate_Elapsed(object sender, ElapsedEventArgs e) { - nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd"); + nowTimeVM.nowDate = DateTime.Now.ToShortDateString().ToString(); } private void TimerKillProcess_Elapsed(object sender, ElapsedEventArgs e) { @@ -166,16 +121,17 @@ namespace Ink_Canvas { if (processes.Length > 0) arg += " /IM \"Ink Canvas.exe\""; } - if (Settings.Automation.IsAutoKillIDT) { - var processes = Process.GetProcessesByName("Inkeys"); - if (processes.Length > 0) arg += " /IM \"Inkeys.exe\""; - } + // 移除了IDT的查殺 + //if (Settings.Automation.IsAutoKillIDT) { + // var processes = Process.GetProcessesByName("智绘教"); + // if (processes.Length > 0) arg += " /IM \"智绘教.exe\""; + //} - if (Settings.Automation.IsAutoKillSeewoLauncher2DesktopAnnotation) { + //if (Settings.Automation.IsAutoKillSeewoLauncher2DesktopAnnotation) { //由于希沃桌面2.0提供的桌面批注是64位应用程序,32位程序无法访问,目前暂不做精准匹配,只匹配进程名称,后面会考虑封装一套基于P/Invoke和WMI的综合进程识别方案。 - var processes = Process.GetProcessesByName("DesktopAnnotation"); - if (processes.Length > 0) arg += " /IM DesktopAnnotation.exe"; - } + // var processes = Process.GetProcessesByName("DesktopAnnotation"); + // if (processes.Length > 0) arg += " /IM DesktopAnnotation.exe"; + //} if (arg != "/F") { var p = new Process(); @@ -185,65 +141,63 @@ namespace Ink_Canvas { if (arg.Contains("EasiNote")) { Dispatcher.Invoke(() => { - ShowNotification("“希沃白板 5”已自动关闭"); + ShowNewToast("“希沃白板 5”已自动关闭", MW_Toast.ToastType.Warning, 3000); }); } if (arg.Contains("HiteAnnotation")) { Dispatcher.Invoke(() => { - ShowNotification("“鸿合屏幕书写”已自动关闭"); - if (Settings.Automation.IsAutoKillHiteAnnotation && Settings.Automation.IsAutoEnterAnnotationAfterKillHite) { - // 自动进入批注状态 - PenIcon_Click(null, null); - } + ShowNewToast("“鸿合屏幕书写”已自动关闭", MW_Toast.ToastType.Warning, 3000); }); } if (arg.Contains("Ink Canvas Annotation") || arg.Contains("Ink Canvas Artistry")) { Dispatcher.Invoke(() => { - ShowNewMessage("“ICA”已自动关闭"); + ShowNewToast("“ICA”已自动关闭", MW_Toast.ToastType.Warning, 3000); }); } if (arg.Contains("\"Ink Canvas.exe\"")) { Dispatcher.Invoke(() => { - ShowNotification("“Ink Canvas”已自动关闭"); + ShowNewToast("“Ink Canvas”已自动关闭", MW_Toast.ToastType.Warning, 3000); }); } - if (arg.Contains("Inkeys")) { - Dispatcher.Invoke(() => { - ShowNotification("“智绘教Inkeys”已自动关闭"); - }); - } + //if (arg.Contains("智绘教")) { + // Dispatcher.Invoke(() => { + // ShowNotification("“智绘教”已自动关闭"); + // }); + //} if (arg.Contains("VcomTeach")) { Dispatcher.Invoke(() => { - ShowNotification("“优教授课端”已自动关闭"); + ShowNewToast("“优教授课端”已自动关闭", MW_Toast.ToastType.Warning, 3000); }); } - if (arg.Contains("DesktopAnnotation")) - { - Dispatcher.Invoke(() => { - ShowNotification("“希沃桌面2.0 桌面批注”已自动关闭"); - }); - } + //if (arg.Contains("DesktopAnnotation")) + //{ + // Dispatcher.Invoke(() => { + // ShowNotification("“DesktopAnnotation”已自动关闭"); + // }); + //} } } catch {} } - private bool foldFloatingBarByUser, // 保持收纳操作不受自动收纳的控制 - unfoldFloatingBarByUser; // 允许用户在希沃软件内进行展开操作 + private bool foldFloatingBarByUser = false, // 保持收纳操作不受自动收纳的控制 + unfoldFloatingBarByUser = false; // 允许用户在希沃软件内进行展开操作 private void timerCheckAutoFold_Elapsed(object sender, ElapsedEventArgs e) { if (isFloatingBarChangingHideMode) return; try { var windowProcessName = ForegroundWindowInfo.ProcessName(); + Trace.WriteLine(windowProcessName); var windowTitle = ForegroundWindowInfo.WindowTitle(); + Trace.WriteLine(windowTitle); //LogHelper.WriteLogToFile("windowTitle | " + windowTitle + " | windowProcessName | " + windowProcessName); if (windowProcessName == "EasiNote") { @@ -255,8 +209,7 @@ namespace Ink_Canvas { Trace.WriteLine(ForegroundWindowInfo.ProcessPath()); Trace.WriteLine(version); Trace.WriteLine(prodName); - if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote && (!(windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500) || - !Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno)) { // EasiNote5 + if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote ) { // EasiNote5 if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); } else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3) { // EasiNote3 if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null); @@ -351,131 +304,24 @@ namespace Ink_Canvas { } private void timerCheckAutoUpdateWithSilence_Elapsed(object sender, ElapsedEventArgs e) { - // 停止计时器,避免重复触发 - timerCheckAutoUpdateWithSilence.Stop(); - + Dispatcher.Invoke(() => { + try { + if (!Topmost || inkCanvas.Strokes.Count > 0) return; + } + catch (Exception ex) { + LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + } + }); try { - // 检查是否有可用的更新 - if (string.IsNullOrEmpty(AvailableLatestVersion)) { - LogHelper.WriteLogToFile("AutoUpdate | No available update version found"); - return; - } - - // 检查是否启用了静默更新 - if (!Settings.Startup.IsAutoUpdateWithSilence) { - LogHelper.WriteLogToFile("AutoUpdate | Silent update is disabled"); - return; - } - - // 检查更新文件是否已下载 - string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate"); - string statusFilePath = Path.Combine(updatesFolderPath, $"DownloadV{AvailableLatestVersion}Status.txt"); - - if (!File.Exists(statusFilePath) || File.ReadAllText(statusFilePath).Trim().ToLower() != "true") { - LogHelper.WriteLogToFile("AutoUpdate | Update file not downloaded yet"); - - // 尝试下载更新文件,使用多线路组下载功能 - Task.Run(async () => { - bool isDownloadSuccessful = false; - - try - { - // 如果主要线路组可用,直接使用 - if (AvailableLatestLineGroup != null) - { - LogHelper.WriteLogToFile($"AutoUpdate | 使用主要线路组下载: {AvailableLatestLineGroup.GroupName}"); - isDownloadSuccessful = await AutoUpdateHelper.DownloadSetupFile(AvailableLatestVersion, AvailableLatestLineGroup); - } - - // 如果主要线路组不可用或下载失败,获取所有可用线路组 - if (!isDownloadSuccessful) - { - LogHelper.WriteLogToFile("AutoUpdate | 主要线路组不可用或下载失败,获取所有可用线路组"); - var availableGroups = await AutoUpdateHelper.GetAvailableLineGroupsOrdered(Settings.Startup.UpdateChannel); - if (availableGroups.Count > 0) - { - LogHelper.WriteLogToFile($"AutoUpdate | 使用 {availableGroups.Count} 个可用线路组进行下载"); - isDownloadSuccessful = await AutoUpdateHelper.DownloadSetupFileWithFallback(AvailableLatestVersion, availableGroups); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error); - } - - if (isDownloadSuccessful) { - LogHelper.WriteLogToFile("AutoUpdate | Update downloaded successfully, will check again for installation"); - // 重新启动计时器,下次检查时安装 - timerCheckAutoUpdateWithSilence.Start(); - } else { - LogHelper.WriteLogToFile("AutoUpdate | Failed to download update", LogHelper.LogType.Error); - } - }); - - return; - } - - // 检查是否在静默更新时间段内 - bool isInSilencePeriod = AutoUpdateWithSilenceTimeComboBox.CheckIsInSilencePeriod( - Settings.Startup.AutoUpdateWithSilenceStartTime, - Settings.Startup.AutoUpdateWithSilenceEndTime); - - if (!isInSilencePeriod) { - LogHelper.WriteLogToFile("AutoUpdate | Not in silence update time period"); - // 重新启动计时器,稍后再检查 - timerCheckAutoUpdateWithSilence.Start(); - return; - } - - // 检查应用程序状态,确保可以安全更新 - // 空闲状态的判定为不处于批注模式和画板模式 - bool canSafelyUpdate = false; - - Dispatcher.Invoke(() => { - try { - // 判断是否处于批注模式(inkCanvas.EditingMode == InkCanvasEditingMode.Ink) - // 判断是否处于画板模式(!Topmost) - if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink && Topmost) { - // 检查是否有未保存的内容或正在进行的操作 - if (!isHidingSubPanelsWhenInking) { - canSafelyUpdate = true; - LogHelper.WriteLogToFile("AutoUpdate | Application is in a safe state for update - not in ink or board mode"); - } else { - LogHelper.WriteLogToFile("AutoUpdate | Application is currently performing operations"); - } - } else { - LogHelper.WriteLogToFile("AutoUpdate | Application is in ink or board mode, cannot update now"); - } - } - catch (Exception ex) { - LogHelper.WriteLogToFile($"AutoUpdate | Error checking application state: {ex.Message}", LogHelper.LogType.Error); - } - }); - - if (canSafelyUpdate) { - LogHelper.WriteLogToFile("AutoUpdate | Installing update now"); - - // 设置为用户主动退出,避免被看门狗判定为崩溃 - App.IsAppExitByUser = true; - - // 执行更新安装 + if (AutoUpdateWithSilenceTimeComboBox.CheckIsInSilencePeriod( + Settings.Startup.AutoUpdateWithSilenceStartTime, + Settings.Startup.AutoUpdateWithSilenceEndTime)) { AutoUpdateHelper.InstallNewVersionApp(AvailableLatestVersion, true); - - // 关闭应用程序 - Dispatcher.Invoke(() => { - Application.Current.Shutdown(); - }); - } else { - LogHelper.WriteLogToFile("AutoUpdate | Cannot safely update now, will try again later"); - // 重新启动计时器,稍后再检查 - timerCheckAutoUpdateWithSilence.Start(); + timerCheckAutoUpdateWithSilence.Stop(); } } catch (Exception ex) { - LogHelper.WriteLogToFile($"AutoUpdate | Error in silent update check: {ex.Message}", LogHelper.LogType.Error); - // 出错时重新启动计时器,稍后再检查 - timerCheckAutoUpdateWithSilence.Start(); + LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); } } } diff --git a/InkCanvasForClass/MainWindow_cs/MW_Toast.xaml b/InkCanvasForClass/MainWindow_cs/MW_Toast.xaml new file mode 100644 index 00000000..edcf6ad5 --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_Toast.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/MainWindow_cs/MW_Toast.xaml.cs b/InkCanvasForClass/MainWindow_cs/MW_Toast.xaml.cs new file mode 100644 index 00000000..5a39da5c --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_Toast.xaml.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Xml.Linq; + +namespace Ink_Canvas { + + public partial class MW_Toast : UserControl { + + private Tuple[] gradientTuples = new Tuple[] { + new Tuple(Color.FromRgb(246, 116, 62),Color.FromRgb(212, 37, 37)), + new Tuple(Color.FromRgb(248, 184, 6), Color.FromRgb(255, 140, 4)), + new Tuple(Color.FromRgb(45, 130, 178), Color.FromRgb(50, 154, 187)), + new Tuple(Color.FromRgb(50, 187, 113), Color.FromRgb(42, 157, 143)) + }; + + private Color[] borderColors = new Color[] { + Color.FromRgb(240, 134, 58), + Color.FromRgb(255, 223, 141), + Color.FromRgb(123, 207, 237), + Color.FromRgb(67, 213, 144), + }; + + public enum ToastType { + Error, + Warning, + Informative, + Success + } + + public DrawingImage[] tipIconDrawingImages; + + private void UpdateToastStyle(ToastType type) { + ToastBorder.BorderBrush = new SolidColorBrush(borderColors[(int)type]); + GradientStop1.Color = gradientTuples[(int)type].Item1; + GradientStop2.Color = gradientTuples[(int)type].Item2; + ToastIconImage.Source = tipIconDrawingImages[(int)type]; + } + + private string _toastText = "InkCanvasForClass"; + public string ToastText { + get => _toastText; + set { + _toastText = value; + ToastTextBlock.Text = _toastText; + } + } + + private ToastType _toastType = ToastType.Error; + + public ToastType Type { + get => _toastType; + set { + _toastType = value; + UpdateToastStyle(value); + } + } + + private void Animate(double opacityFrom, double opacityTo, int durationMs, Action complete, EasingFunctionBase easing = null) { + var sb = new Storyboard(); + + var fadeInAnimation = new DoubleAnimation { + From = opacityFrom, + To = opacityTo, + Duration = TimeSpan.FromMilliseconds(durationMs) + }; + Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty)); + if (easing != null) fadeInAnimation.EasingFunction = easing; + sb.Children.Add(fadeInAnimation); + sb.Completed += (sender, args) => { + if (complete != null) complete(); + }; + + ToastGrid.Opacity = opacityFrom; + sb.Begin(ToastGrid); + } + + public void ShowImmediately() { + ToastGrid.Opacity = 1; + } + + public void ShowAnimated() { + HideImmediately(); + Animate(0, 1, 200, null ,new CubicEase()); + } + + public void ShowAnimatedWithAutoDispose(int autoCloseMs=3000) { + var t = new Thread(() => { + _isDisplay = true; + Dispatcher.InvokeAsync(() => { ShowAnimated(); }); + Thread.Sleep(autoCloseMs); + Dispatcher.InvokeAsync(() => { + Animate(1, 0, 200, (() => { + ShouldDisposeAction(this); + }), new CubicEase()); + }); + _isDisplay = false; + }); + t.Start(); + } + + public void ShowImmediatelyWithAutoDispose(int autoCloseMs=3000) { + var t = new Thread(() => { + _isDisplay = true; + Dispatcher.InvokeAsync(() => { ToastGrid.Opacity = 1; }); + Thread.Sleep(autoCloseMs); + Dispatcher.InvokeAsync(() => { + Animate(1, 0, 200, (() => { + ShouldDisposeAction(this); + }), new CubicEase()); + }); + _isDisplay = false; + }); + t.Start(); + } + + public void HideImmediately() { + ToastGrid.Opacity = 0; + } + + public void HideAnimated() { + Animate(1, 0, 200, null, new CubicEase()); + } + + public Action ShouldDisposeAction; + + private bool _isDisplay = false; + public bool IsDisplay { + get => _isDisplay; + } + + public MW_Toast(ToastType type, string text, Action shouldDisposeAction) { + InitializeComponent(); + + tipIconDrawingImages = new DrawingImage[] { + this.FindResource("ErrorIcon") as DrawingImage, + this.FindResource("WarningIcon") as DrawingImage, + this.FindResource("InfoIcon") as DrawingImage, + this.FindResource("SuccessIcon") as DrawingImage, + }; + + Type = type; + ToastText = text; + + ToastGrid.Opacity = 0; + ShouldDisposeAction = shouldDisposeAction; + } + } +} diff --git a/InkCanvasForClass/MainWindow_cs/MW_TouchEvents.cs b/InkCanvasForClass/MainWindow_cs/MW_TouchEvents.cs new file mode 100644 index 00000000..ea4673c7 --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_TouchEvents.cs @@ -0,0 +1,447 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using Point = System.Windows.Point; + +namespace Ink_Canvas { + public partial class MainWindow : PerformanceTransparentWin { + #region Multi-Touch + + private bool isInMultiTouchMode = false; + + private void BorderMultiTouchMode_MouseUp(object sender, MouseButtonEventArgs e) { + if (isInMultiTouchMode) { + inkCanvas.StylusDown -= MainWindow_StylusDown; + inkCanvas.StylusMove -= MainWindow_StylusMove; + inkCanvas.StylusUp -= MainWindow_StylusUp; + inkCanvas.TouchDown -= MainWindow_TouchDown; + inkCanvas.TouchDown += Main_Grid_TouchDown; + inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + inkCanvas.Children.Clear(); + isInMultiTouchMode = false; + } else { + inkCanvas.StylusDown += MainWindow_StylusDown; + inkCanvas.StylusMove += MainWindow_StylusMove; + inkCanvas.StylusUp += MainWindow_StylusUp; + inkCanvas.TouchDown += MainWindow_TouchDown; + inkCanvas.TouchDown -= Main_Grid_TouchDown; + inkCanvas.EditingMode = InkCanvasEditingMode.None; + inkCanvas.Children.Clear(); + isInMultiTouchMode = true; + } + } + + private void MainWindow_TouchDown(object sender, TouchEventArgs e) { + if (!isCursorHidden && Settings.Gesture.HideCursorWhenUsingTouchDevice) { + System.Windows.Forms.Cursor.Hide(); + isCursorHidden = true; + } + + if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint + || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke + || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; + + if (!isHidingSubPanelsWhenInking) { + isHidingSubPanelsWhenInking = true; + HideSubPanels(); // 书写时自动隐藏二级菜单 + } + + // 不禁用手势橡皮 + if (!Settings.Gesture.DisableGestureEraser) { + double boundWidth = e.GetTouchPoint(null).Bounds.Width; + if ((Settings.Advanced.TouchMultiplier != 0 || + !Settings.Advanced.IsSpecialScreen) //启用特殊屏幕且触摸倍数为 0 时禁用橡皮 + && (boundWidth > BoundsWidth)) { + if (drawingShapeMode == 0 && forceEraser) return; + double EraserThresholdValue = Settings.Startup.IsEnableNibMode + ? Settings.Advanced.NibModeBoundsWidthThresholdValue + : Settings.Advanced.FingerModeBoundsWidthThresholdValue; + if (boundWidth > BoundsWidth * EraserThresholdValue) { + boundWidth *= (Settings.Startup.IsEnableNibMode + ? Settings.Advanced.NibModeBoundsWidthEraserSize + : Settings.Advanced.FingerModeBoundsWidthEraserSize); + if (Settings.Advanced.IsSpecialScreen) boundWidth *= Settings.Advanced.TouchMultiplier; + TouchDownPointsList[e.TouchDevice.Id] = InkCanvasEditingMode.EraseByPoint; + eraserWidth = boundWidth; + isEraserCircleShape = Settings.Canvas.EraserShapeType == 0; + isUsingStrokesEraser = false; + inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint; + } else { + isUsingStrokesEraser = true; + inkCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke; + } + } else { + inkCanvas.EraserShape = + forcePointEraser ? new EllipseStylusShape(50, 50) : new EllipseStylusShape(5, 5); + TouchDownPointsList[e.TouchDevice.Id] = InkCanvasEditingMode.None; + inkCanvas.EditingMode = InkCanvasEditingMode.None; + } + } + } + + private void MainWindow_StylusDown(object sender, StylusDownEventArgs e) { + if (e.StylusDevice.TabletDevice.Type == TabletDeviceType.Touch) { + if (!isCursorHidden && Settings.Gesture.HideCursorWhenUsingTouchDevice && + e.StylusDevice.TabletDevice.Type == TabletDeviceType.Touch) { + System.Windows.Forms.Cursor.Hide(); + isCursorHidden = true; + } + + ViewboxFloatingBar.IsHitTestVisible = false; + BlackboardUIGridForInkReplay.IsHitTestVisible = false; + + if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint + || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke + || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; + + TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None; + } + } + + private async void MainWindow_StylusUp(object sender, StylusEventArgs e) { + if (e.StylusDevice.TabletDevice.Type == TabletDeviceType.Touch) { + try { + inkCanvas.Strokes.Add(GetStrokeVisual(e.StylusDevice.Id).Stroke); + await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁 + inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id)); + inkCanvas_StrokeCollected(inkCanvas, + new InkCanvasStrokeCollectedEventArgs(GetStrokeVisual(e.StylusDevice.Id).Stroke)); + } + catch (Exception ex) { + Label.Content = ex.ToString(); + } + + try { + StrokeVisualList.Remove(e.StylusDevice.Id); + VisualCanvasList.Remove(e.StylusDevice.Id); + TouchDownPointsList.Remove(e.StylusDevice.Id); + if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0) { + inkCanvas.Children.Clear(); + StrokeVisualList.Clear(); + VisualCanvasList.Clear(); + TouchDownPointsList.Clear(); + } + } + catch { } + + ViewboxFloatingBar.IsHitTestVisible = true; + BlackboardUIGridForInkReplay.IsHitTestVisible = true; + } + } + + private void MainWindow_StylusMove(object sender, StylusEventArgs e) { + if (!isCursorHidden && Settings.Gesture.HideCursorWhenUsingTouchDevice && + e.StylusDevice.TabletDevice.Type == TabletDeviceType.Touch) { + System.Windows.Forms.Cursor.Hide(); + isCursorHidden = true; + } + + if (e.StylusDevice.TabletDevice.Type == TabletDeviceType.Touch) { + try { + if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) return; + try { + if (e.StylusDevice.StylusButtons[1].StylusButtonState == StylusButtonState.Down) return; + } + catch { } + + var strokeVisual = GetStrokeVisual(e.StylusDevice.Id); + var stylusPointCollection = e.GetStylusPoints(this); + foreach (var stylusPoint in stylusPointCollection) + strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor)); + strokeVisual.Redraw(); + } + catch { } + } + } + + private StrokeVisual GetStrokeVisual(int id) { + if (StrokeVisualList.TryGetValue(id, out var visual)) return visual; + + var strokeVisual = new StrokeVisual(inkCanvas.DefaultDrawingAttributes.Clone()); + StrokeVisualList[id] = strokeVisual; + StrokeVisualList[id] = strokeVisual; + var visualCanvas = new VisualCanvas(strokeVisual); + VisualCanvasList[id] = visualCanvas; + inkCanvas.Children.Add(visualCanvas); + + return strokeVisual; + } + + private VisualCanvas GetVisualCanvas(int id) { + return VisualCanvasList.TryGetValue(id, out var visualCanvas) ? visualCanvas : null; + } + + private InkCanvasEditingMode GetTouchDownPointsList(int id) { + return TouchDownPointsList.TryGetValue(id, out var inkCanvasEditingMode) + ? inkCanvasEditingMode + : inkCanvas.EditingMode; + } + + private Dictionary TouchDownPointsList { get; } = + new Dictionary(); + + private Dictionary StrokeVisualList { get; } = new Dictionary(); + private Dictionary VisualCanvasList { get; } = new Dictionary(); + + #endregion + + #region Touch Pointer Hide + + public bool isCursorHidden = false; + + private void MainWindow_OnMouseMove(object sender, MouseEventArgs e) { + if (e.StylusDevice == null) { + if (isCursorHidden) { + System.Windows.Forms.Cursor.Show(); + isCursorHidden = false; + } + } else if (e.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) { + if (isCursorHidden) { + System.Windows.Forms.Cursor.Show(); + isCursorHidden = false; + } + } + } + + #endregion + + private int lastTouchDownTime = 0, lastTouchUpTime = 0; + + private Point iniP = new Point(0, 0); + private bool isLastTouchEraser = false; + private bool forcePointEraser = true; + + private void Main_Grid_TouchDown(object sender, TouchEventArgs e) { + if (!isCursorHidden && Settings.Gesture.HideCursorWhenUsingTouchDevice) { + System.Windows.Forms.Cursor.Hide(); + isCursorHidden = true; + } + + inkCanvas.CaptureTouch(e.TouchDevice); + ViewboxFloatingBar.IsHitTestVisible = false; + BlackboardUIGridForInkReplay.IsHitTestVisible = false; + + if (!isHidingSubPanelsWhenInking) { + isHidingSubPanelsWhenInking = true; + HideSubPanels(); // 书写时自动隐藏二级菜单 + } + + if (NeedUpdateIniP()) iniP = e.GetTouchPoint(inkCanvas).Position; + if (drawingShapeMode == 9 && isFirstTouchCuboid == false) MouseTouchMove(iniP); + inkCanvas.Opacity = 1; + + if (!Settings.Gesture.DisableGestureEraser) { + double boundsWidth = GetTouchBoundWidth(e); + if ((Settings.Advanced.TouchMultiplier != 0 || + !Settings.Advanced.IsSpecialScreen) //启用特殊屏幕且触摸倍数为 0 时禁用橡皮 + && (boundsWidth > BoundsWidth)) { + isLastTouchEraser = true; + if (drawingShapeMode == 0 && forceEraser) return; + double EraserThresholdValue = Settings.Startup.IsEnableNibMode + ? Settings.Advanced.NibModeBoundsWidthThresholdValue + : Settings.Advanced.FingerModeBoundsWidthThresholdValue; + if (boundsWidth > BoundsWidth * EraserThresholdValue) { + boundsWidth *= (Settings.Startup.IsEnableNibMode + ? Settings.Advanced.NibModeBoundsWidthEraserSize + : Settings.Advanced.FingerModeBoundsWidthEraserSize); + if (Settings.Advanced.IsSpecialScreen) boundsWidth *= Settings.Advanced.TouchMultiplier; + eraserWidth = boundsWidth; + isEraserCircleShape = Settings.Canvas.EraserShapeType == 0; + isUsingStrokesEraser = false; + inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint; + } else { + isUsingStrokesEraser = true; + inkCanvas.EditingMode = InkCanvasEditingMode.EraseByStroke; + } + } else { + isLastTouchEraser = false; + inkCanvas.EraserShape = + forcePointEraser ? new EllipseStylusShape(50, 50) : new EllipseStylusShape(5, 5); + if (forceEraser) return; + inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + } + } + } + + 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); //四边红外 + } + + //记录触摸设备ID + private List dec = new List(); + + //中心点 + private Point centerPoint; + private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink; + private bool isSingleFingerDragMode = false; + + private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e) { + inkCanvas.CaptureTouch(e.TouchDevice); + ViewboxFloatingBar.IsHitTestVisible = false; + BlackboardUIGridForInkReplay.IsHitTestVisible = false; + + dec.Add(e.TouchDevice.Id); + //设备1个的时候,记录中心点 + if (dec.Count == 1) { + var touchPoint = e.GetTouchPoint(inkCanvas); + centerPoint = touchPoint.Position; + + //记录第一根手指点击时的 StrokeCollection + lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); + } + + //设备两个及两个以上,将画笔功能关闭 + if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture) { + if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; + if (inkCanvas.EditingMode == InkCanvasEditingMode.None || + inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; + lastInkCanvasEditingMode = inkCanvas.EditingMode; + inkCanvas.EditingMode = InkCanvasEditingMode.None; + } + } + + private void inkCanvas_PreviewTouchUp(object sender, TouchEventArgs e) { + inkCanvas.ReleaseAllTouchCaptures(); + ViewboxFloatingBar.IsHitTestVisible = true; + BlackboardUIGridForInkReplay.IsHitTestVisible = true; + + //手势完成后切回之前的状态 + if (dec.Count > 1) + if (inkCanvas.EditingMode == InkCanvasEditingMode.None) + inkCanvas.EditingMode = lastInkCanvasEditingMode; + dec.Remove(e.TouchDevice.Id); + inkCanvas.Opacity = 1; + if (dec.Count == 0) + if (lastTouchDownStrokeCollection.Count() != inkCanvas.Strokes.Count() && + !(drawingShapeMode == 9 && !isFirstTouchCuboid)) { + var whiteboardIndex = CurrentWhiteboardIndex; + if (currentMode == 0) whiteboardIndex = 0; + strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection; + } + } + + private void inkCanvas_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { + e.Mode = ManipulationModes.All; + } + + private void inkCanvas_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e) { } + + private void Main_Grid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { + if (e.Manipulators.Count() != 0) return; + if (forceEraser) return; + inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + } + + + //private void inkCanvas_ManipulationStarted(object sender, ManipulationStartedEventArgs e) + //{ + // if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture || inkCanvas.Strokes.Count == 0 || dec.Count() < 2) return; + // _currentCommitType = CommitReason.Manipulation; + // StrokeCollection strokes = inkCanvas.GetSelectedStrokes(); + // if (strokes.Count != 0) + // { + // inkCanvas.Strokes.Replace(strokes, strokes.Clone()); + // } + // else + // { + // var originalStrokes = inkCanvas.Strokes; + // var targetStrokes = originalStrokes.Clone(); + // originalStrokes.Replace(originalStrokes, targetStrokes); + // } + // _currentCommitType = CommitReason.UserInput; + //} + + private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { + if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; + if ((dec.Count >= 2 && (Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode || + BorderFloatingBarExitPPTBtn.Visibility != Visibility.Visible)) || + isSingleFingerDragMode) { + var md = e.DeltaManipulation; + var trans = md.Translation; // 获得位移矢量 + + var m = new Matrix(); + + if (Settings.Gesture.IsEnableTwoFingerTranslate) + m.Translate(trans.X, trans.Y); // 移动 + + 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) + m.ScaleAt(scale.X, scale.Y, center.X, center.Y); // 缩放 + } + + var strokes = inkCanvas.GetSelectedStrokes(); + if (strokes.Count != 0) { + foreach (var stroke in strokes) { + stroke.Transform(m, false); + + foreach (var circle in circles) + if (stroke == circle.Stroke) { + circle.R = GetDistance(circle.Stroke.StylusPoints[0].ToPoint(), + circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].ToPoint()) / 2; + circle.Centroid = new Point( + (circle.Stroke.StylusPoints[0].X + + circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].X) / 2, + (circle.Stroke.StylusPoints[0].Y + + circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].Y) / 2); + break; + } + + if (!Settings.Gesture.IsEnableTwoFingerZoom) continue; + try { + stroke.DrawingAttributes.Width *= md.Scale.X; + stroke.DrawingAttributes.Height *= md.Scale.Y; + } + catch { } + } + } else { + if (Settings.Gesture.IsEnableTwoFingerZoom) { + foreach (var stroke in inkCanvas.Strokes) { + stroke.Transform(m, false); + try { + stroke.DrawingAttributes.Width *= md.Scale.X; + stroke.DrawingAttributes.Height *= md.Scale.Y; + } + catch { } + } + + ; + } else { + foreach (var stroke in inkCanvas.Strokes) stroke.Transform(m, false); + ; + } + + foreach (var circle in circles) { + circle.R = GetDistance(circle.Stroke.StylusPoints[0].ToPoint(), + circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].ToPoint()) / 2; + circle.Centroid = new Point( + (circle.Stroke.StylusPoints[0].X + + circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].X) / 2, + (circle.Stroke.StylusPoints[0].Y + + circle.Stroke.StylusPoints[circle.Stroke.StylusPoints.Count / 2].Y) / 2 + ); + } + } + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs b/InkCanvasForClass/MainWindow_cs/MW_TrayIcon.cs similarity index 71% rename from Ink Canvas/MainWindow_cs/MW_TrayIcon.cs rename to InkCanvasForClass/MainWindow_cs/MW_TrayIcon.cs index d3c22217..ccb52f39 100644 --- a/Ink Canvas/MainWindow_cs/MW_TrayIcon.cs +++ b/InkCanvasForClass/MainWindow_cs/MW_TrayIcon.cs @@ -1,15 +1,18 @@ -using System; +using Ink_Canvas.Helpers; +using iNKORE.UI.WPF.Modern.Controls; +using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; using System.Windows.Interop; +using System.Windows.Media.Animation; using Hardcodet.Wpf.TaskbarNotification; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; -using Application = System.Windows.Application; -using ContextMenu = System.Windows.Controls.ContextMenu; -using MenuItem = System.Windows.Controls.MenuItem; namespace Ink_Canvas { @@ -25,7 +28,7 @@ namespace Ink_Canvas (TextBlock)((SimpleStackPanel)((MenuItem)s.Items[s.Items.Count - 5]).Header).Children[0]; var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4]; var HideICCMainWindowTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 9]; - var mainWin = (MainWindow)Current.MainWindow; + var mainWin = (MainWindow)Application.Current.MainWindow; if (mainWin.IsLoaded) { // 判斷是否在收納模式中 if (mainWin.isFloatingBarFolded) { @@ -50,49 +53,27 @@ namespace Ink_Canvas } private void CloseAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e) { - var mainWin = (MainWindow)Current.MainWindow; - if (mainWin.IsLoaded) { - IsAppExitByUser = true; - Current.Shutdown(); - // mainWin.BtnExit_Click(null,null); - } + var mainWin = (MainWindow)Application.Current.MainWindow; + if (mainWin.IsLoaded) mainWin.BtnExit_Click(null,null); } private void RestartAppTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e) { - var mainWin = (MainWindow)Current.MainWindow; - if (mainWin.IsLoaded) { - IsAppExitByUser = true; - - try { - // 启动新实例 - string exePath = Process.GetCurrentProcess().MainModule.FileName; - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.FileName = exePath; - startInfo.UseShellExecute = true; - - // 启动进程但不等待 - Process.Start(startInfo); - } catch (Exception ex) { - LogHelper.NewLog($"重启程序时出错: {ex.Message}"); - } - - // 退出当前实例 - Current.Shutdown(); - } + var mainWin = (MainWindow)Application.Current.MainWindow; + if (mainWin.IsLoaded) mainWin.BtnRestart_Click(null,null); } private void ForceFullScreenTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e) { - var mainWin = (MainWindow)Current.MainWindow; + var mainWin = (MainWindow)Application.Current.MainWindow; if (mainWin.IsLoaded) { Ink_Canvas.MainWindow.MoveWindow(new WindowInteropHelper(mainWin).Handle, 0, 0, - Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, true); - Ink_Canvas.MainWindow.ShowNewMessage($"已强制全屏化:{Screen.PrimaryScreen.Bounds.Width}x{Screen.PrimaryScreen.Bounds.Height}(缩放比例为{Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth}x{Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight})"); + System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true); + Ink_Canvas.MainWindow.ShowNewMessage($"已强制全屏化:{System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width}x{System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height}(缩放比例为{System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth}x{System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight})"); } } private void FoldFloatingBarTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e) { - var mainWin = (MainWindow)Current.MainWindow; + var mainWin = (MainWindow)Application.Current.MainWindow; if (mainWin.IsLoaded) if (mainWin.isFloatingBarFolded) mainWin.UnFoldFloatingBar_MouseUp(new object(),null); else mainWin.FoldFloatingBar_MouseUp(new object(),null); @@ -100,11 +81,11 @@ namespace Ink_Canvas private void ResetFloatingBarPositionTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e) { - var mainWin = (MainWindow)Current.MainWindow; + var mainWin = (MainWindow)Application.Current.MainWindow; if (mainWin.IsLoaded) { var isInPPTPresentationMode = false; Dispatcher.Invoke(() => { - isInPPTPresentationMode = mainWin.BtnPPTSlideShowEnd.Visibility == Visibility.Visible; + isInPPTPresentationMode = mainWin.BorderFloatingBarExitPPTBtn.Visibility == Visibility.Visible; }); if (!mainWin.isFloatingBarFolded) { if (!isInPPTPresentationMode) mainWin.PureViewboxFloatingBarMarginAnimationInDesktopMode(); @@ -115,10 +96,10 @@ namespace Ink_Canvas private void HideICCMainWindowTrayIconMenuItem_Checked(object sender, RoutedEventArgs e) { var mi = (MenuItem)sender; - var mainWin = (MainWindow)Current.MainWindow; + var mainWin = (MainWindow)Application.Current.MainWindow; if (mainWin.IsLoaded) { mainWin.Hide(); - var s = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu; + var s = ((TaskbarIcon)Application.Current.Resources["TaskbarTrayIcon"]).ContextMenu; var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4]; var FoldFloatingBarTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 5]; var ForceFullScreenTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 6]; @@ -136,10 +117,10 @@ namespace Ink_Canvas private void HideICCMainWindowTrayIconMenuItem_UnChecked(object sender, RoutedEventArgs e) { var mi = (MenuItem)sender; - var mainWin = (MainWindow)Current.MainWindow; + var mainWin = (MainWindow)Application.Current.MainWindow; if (mainWin.IsLoaded) { mainWin.Show(); - var s = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu; + var s = ((TaskbarIcon)Application.Current.Resources["TaskbarTrayIcon"]).ContextMenu; var ResetFloatingBarPositionTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 4]; var FoldFloatingBarTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 5]; var ForceFullScreenTrayIconMenuItem = (MenuItem)s.Items[s.Items.Count - 6]; diff --git a/InkCanvasForClass/MainWindow_cs/MW_V2_FloatingBar.cs b/InkCanvasForClass/MainWindow_cs/MW_V2_FloatingBar.cs new file mode 100644 index 00000000..691f9aea --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_V2_FloatingBar.cs @@ -0,0 +1,137 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Ink; +using System.Windows.Media; + +namespace Ink_Canvas { + public partial class MainWindow : PerformanceTransparentWin { + + public FloatingToolBarV2 FloatingToolBarV2; + + private void InitFloatingToolbarV2() { + FloatingToolBarV2 = new FloatingToolBarV2(); + FloatingToolBarV2.Topmost = false; + FloatingToolBarV2.Show(); + FloatingToolBarV2.Owner = this; + + FloatingToolBarV2.FloatingBarToolSelectionChanged += FloatingToolBarV2_ToolSelectionChanged; + FloatingToolBarV2.FloatingBarToolButtonClicked += FloatingToolBarV2_ToolButtonClicked; + } + + #region 工具切换 + + private void SwitchToCursorMode() { + // 结束未完成的形状绘制 + if (ShapeDrawingV2Layer.IsInShapeDrawingMode) ShapeDrawingV2Layer.EndShapeDrawing(); + + // 切换前自动截图保存墨迹 + if (inkCanvas.Strokes.Count > 0 && + inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) { + if (BorderFloatingBarExitPPTBtn.Visibility == Visibility.Visible) SavePPTScreenshot($"{pptName}/{previousSlideID}_{DateTime.Now:HH-mm-ss}"); + else SaveScreenshot(true); + } + + + inkCanvas.Visibility = Settings.Canvas.HideStrokeWhenSelecting ? Visibility.Collapsed : Visibility.Visible; + inkCanvas.IsHitTestVisible = false; + SetTransparentHitThrough(); + + GridBackgroundCoverHolder.Visibility = Visibility.Collapsed; + inkCanvas.Select(new StrokeCollection()); + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + + RectangleSelectionHitTestBorder.Visibility = Visibility.Collapsed; + + if (currentMode != 0) { + SaveStrokes(); + RestoreStrokes(true); + } + } + + private void SwitchToPenMode() { + // 结束未完成的形状绘制 + if (ShapeDrawingV2Layer.IsInShapeDrawingMode) ShapeDrawingV2Layer.EndShapeDrawing(); + + inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + + SetTransparentNotHitThrough(); + inkCanvas.IsHitTestVisible = true; + inkCanvas.Visibility = Visibility.Visible; + + GridBackgroundCoverHolder.Visibility = Visibility.Visible; + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + + ColorSwitchCheck(); + } + + private void ClearInkCanvasStrokes(bool isClearTimeMachineHistory, bool isErasedByCode) { + if (inkCanvas.GetSelectedStrokes().Count > 0) { + inkCanvas.Strokes.Remove(inkCanvas.GetSelectedStrokes()); + // cancel + GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed; + inkCanvas.Opacity = 1; + InkSelectionStrokesOverlay.Visibility = Visibility.Collapsed; + InkSelectionStrokesBackgroundInkCanvas.Visibility = Visibility.Collapsed; + InkSelectionStrokesOverlay.DrawStrokes(new StrokeCollection(), new Matrix()); + UpdateStrokeSelectionBorder(false, null); + RectangleSelectionHitTestBorder.Visibility = Visibility.Visible; + } else if (inkCanvas.Strokes.Count > 0) { + if (Settings.Automation.IsAutoSaveStrokesAtClear && + inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) { + if (BorderFloatingBarExitPPTBtn.Visibility == Visibility.Visible) + SavePPTScreenshot($"{pptName}/{previousSlideID}_{DateTime.Now:HH-mm-ss}"); + else + SaveScreenshot(true); + } + + forceEraser = false; + + if (currentMode == 0) { + // 先回到画笔再清屏,避免 TimeMachine 的相关 bug 影响 + if (Pen_Icon.Background == null && StackPanelCanvasControls.Visibility == Visibility.Visible) SwitchToPenMode(); + } else if (Pen_Icon.Background == null) SwitchToPenMode(); + + if (inkCanvas.Strokes.Count != 0) { + var whiteboardIndex = CurrentWhiteboardIndex; + if (currentMode == 0) whiteboardIndex = 0; + strokeCollections[whiteboardIndex] = inkCanvas.Strokes.Clone(); + } + + ClearStrokes(false); + inkCanvas.Children.Clear(); + + CancelSingleFingerDragMode(); + + if (isClearTimeMachineHistory) { + inkCanvas.Strokes.Clear(); + timeMachine.ClearStrokeHistory(); + } else { + _currentCommitType = CommitReason.ClearingCanvas; + if (isErasedByCode) _currentCommitType = CommitReason.CodeInput; + inkCanvas.Strokes.Clear(); + _currentCommitType = CommitReason.UserInput; + } + } + } + + #endregion + + private void FloatingToolBarV2_ToolSelectionChanged(object sender, EventArgs e) { + var item = (FloatingBarItem)sender; + if (item.ToolType == ICCToolsEnum.CursorMode) SwitchToCursorMode(); + if (item.ToolType == ICCToolsEnum.PenMode) SwitchToPenMode(); + } + + private void FloatingToolBarV2_ToolButtonClicked(object sender, EventArgs e) { + var item = (FloatingBarItem)sender; + if (item.Name == "Clear") ClearInkCanvasStrokes(Settings.Canvas.ClearCanvasAndClearTimeMachine,false); + } + } +} diff --git a/InkCanvasForClass/MainWindow_cs/MW_WindowsInk.cs b/InkCanvasForClass/MainWindow_cs/MW_WindowsInk.cs new file mode 100644 index 00000000..e36085de --- /dev/null +++ b/InkCanvasForClass/MainWindow_cs/MW_WindowsInk.cs @@ -0,0 +1,117 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Ink_Canvas +{ + public partial class MainWindow : PerformanceTransparentWin { + + private bool _stylusInverted = false; + private int _stylusInvertedInit = 0; + + private bool IsStylusInverted { + get => _stylusInverted; + set { + if (value && !_stylusInverted) { + StylusInverted?.Invoke(this,new RoutedEventArgs()); + } else if (!value && _stylusInverted) { + StylusUnInverted?.Invoke(this,new RoutedEventArgs()); + } + _stylusInverted = value; + } + } + + public event EventHandler StylusInverted; + public event EventHandler StylusUnInverted; + + public void UpdateStylusPenInvertedStatus(bool isInverted) { + if (_stylusInvertedInit == 0) { + _stylusInverted = isInverted; + _stylusInvertedInit = 1; + } else { + IsStylusInverted = isInverted; + } + } + + #region 托管StylusInAirMove和StylusMove事件 + + public void mainWin_StylusInAirMove(object sender, StylusEventArgs e) { + UpdateStylusPenInvertedStatus(e.Inverted); + } + + public void mainWin_StylusMove(object sender, StylusEventArgs e) { + UpdateStylusPenInvertedStatus(e.Inverted); + } + + #endregion + + #region Windows Ink 橡皮按钮自定义适配 + + public void StylusInvertedListenerInit() { + StylusInverted += StylusInvertedEvent; + StylusUnInverted += StylusUnInvertedEvent; + } + + private void StylusInvertedEvent(object sender, RoutedEventArgs e) { + if (Settings.Gesture.WindowsInkEraserButtonAction != 0) { + if (SelectedMode != ICCToolsEnum.EraseByGeometryMode && + SelectedMode != ICCToolsEnum.EraseByStrokeMode) { + GridEraserOverlay.Visibility = Visibility.Visible; + isUsingStrokesEraser = Settings.Gesture.WindowsInkEraserButtonAction == 1; + } else if (SelectedMode == (Settings.Gesture.WindowsInkEraserButtonAction == 2 + ? ICCToolsEnum.EraseByStrokeMode + : ICCToolsEnum.EraseByGeometryMode)) { + isUsingStrokesEraser = Settings.Gesture.WindowsInkEraserButtonAction == 1; + } + ForceUpdateToolSelection((Settings.Gesture.WindowsInkEraserButtonAction == 2 + ? ICCToolsEnum.EraseByGeometryMode + : ICCToolsEnum.EraseByStrokeMode)); + } + } + + private void StylusUnInvertedEvent(object sender, RoutedEventArgs e) { + if (Settings.Gesture.WindowsInkEraserButtonAction != 0) { + if (SelectedMode != ICCToolsEnum.EraseByGeometryMode && + SelectedMode != ICCToolsEnum.EraseByStrokeMode) { + GridEraserOverlay.Visibility = Visibility.Collapsed; + } else if (SelectedMode == (Settings.Gesture.WindowsInkEraserButtonAction == 2 + ? ICCToolsEnum.EraseByStrokeMode + : ICCToolsEnum.EraseByGeometryMode)) { + isUsingStrokesEraser = Settings.Gesture.WindowsInkEraserButtonAction == 2; + } + } + ForceUpdateToolSelection(null); + } + + #endregion + + #region Windows Ink 筒形按钮自定义适配 + + private void mainWin_StylusButtonUp(object sender, StylusButtonEventArgs e) { + if (e.StylusButton.Guid == StylusPointProperties.BarrelButton.Id) { + if (Settings.Gesture.WindowsInkBarrelButtonAction == 0) return; + if (Settings.Gesture.WindowsInkBarrelButtonAction == 1) SelectIcon_MouseUp(null,null); + else if (Settings.Gesture.WindowsInkBarrelButtonAction == 2) { + SymbolIconSelect_MouseUp(null,null); + inkCanvas.Select(inkCanvas.Strokes); + } + else if (Settings.Gesture.WindowsInkBarrelButtonAction == 3) SymbolIconUndo_MouseUp(null,null); + } + } + + private void mainWin_StylusButtonDown(object sender, StylusButtonEventArgs e) { + if (e.StylusButton.Guid == StylusPointProperties.BarrelButton.Id) { + + } + } + + #endregion + } +} diff --git a/InkCanvasForClass/Popups/ColorPalette.xaml b/InkCanvasForClass/Popups/ColorPalette.xaml new file mode 100644 index 00000000..8ad87dda --- /dev/null +++ b/InkCanvasForClass/Popups/ColorPalette.xaml @@ -0,0 +1,784 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 墨迹纠正 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 模拟笔锋 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 圆形笔尖 + + + + + + + + + + + + + + + + + + + + + + + + 手指模式 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InkCanvasForClass/Popups/ColorPalette.xaml.cs b/InkCanvasForClass/Popups/ColorPalette.xaml.cs new file mode 100644 index 00000000..07d00cf7 --- /dev/null +++ b/InkCanvasForClass/Popups/ColorPalette.xaml.cs @@ -0,0 +1,1101 @@ +using iNKORE.UI.WPF.Modern.Controls; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using ColorPicker; +using iNKORE.UI.WPF.Helpers; +using static Ink_Canvas.Popups.ColorPalette; +using System.Drawing; +using Ink_Canvas.Helpers; +using Color = System.Windows.Media.Color; +using Point = System.Windows.Point; +using Image = System.Windows.Controls.Image; +using System.Reflection; + +namespace Ink_Canvas.Popups { + public partial class ColorPalette : UserControl { + public enum ColorPaletteColor { + ColorBlack, + ColorWhite, + ColorRed, + ColorOrange, + ColorYellow, + ColorLime, + ColorGreen, + ColorTeal, + ColorCyan, + ColorBlue, + ColorIndigo, + ColorPurple, + ColorFuchsia, + ColorPink, + ColorRose, + ColorCustom + }; + + private Color[] _darkColors = new Color[] { + Color.FromRgb(9, 9, 11), + Color.FromRgb(250, 250, 250), + Color.FromRgb(220, 38, 38), + Color.FromRgb(234, 88, 12), + Color.FromRgb(250, 204, 21), + Color.FromRgb(101, 163, 13), + Color.FromRgb(22, 163, 74), + Color.FromRgb(13, 148, 136), + Color.FromRgb(8, 145, 178), + Color.FromRgb(37, 99, 235), + Color.FromRgb(79, 70, 229), + Color.FromRgb(124, 58, 237), + Color.FromRgb(192, 38, 211), + Color.FromRgb(219, 39, 119), + Color.FromRgb(225, 29, 72), + }; + + private Color[] _lightColors = new Color[] { + Color.FromRgb(9, 9, 11), + Color.FromRgb(250, 250, 250), + Color.FromRgb(239, 68, 68), + Color.FromRgb(249, 115, 22), + Color.FromRgb(253, 224, 71), + Color.FromRgb(163, 230, 53), + Color.FromRgb(74, 222, 128), + Color.FromRgb(94, 234, 212), + Color.FromRgb(34, 211, 238), + Color.FromRgb(59, 130, 246), + Color.FromRgb(129, 140, 248), + Color.FromRgb(168, 85, 247), + Color.FromRgb(217, 70, 239), + Color.FromRgb(236, 72, 153), + Color.FromRgb(244, 63, 94), + }; + + public string[] ColorPaletteColorStrings = new[] { + "black", "white", "red", "orange", "yellow", "lime", "green", "teal", "cyan", "blue", "indigo", "purple", + "fuchsia", "pink", "rose" + }; + + public Border[] ColorPaletteColorButtonBorders; + public Border[] PenModeTabButtonBorders; + public SimpleStackPanel[] PenModeTabButtonIndicators; + public GeometryDrawing[] PenModeTabButtonIcons; + public TextBlock[] PenModeTabButtonTexts; + + #region 暗色亮色成員 + + /// + /// 內部變量,代表是否使用暗色配色方案 + /// + private bool _usingDarkColors = true; + + /// + /// 公共成員,讀取或修改是否使用暗色配色方案 + /// + public bool UsingDarkColors { + get => _usingDarkColors; + set { + var pre = _usingDarkColors; + _usingDarkColors = value; + UpdateColorPaletteColorsAndColorModeChangeButton(); + ColorModeChanged?.Invoke(this, new ColorModeChangedEventArgs() + { + IsPreviousUsedDarkColor = pre, + IsNowUsingDarkColor = value, + TriggerMode = TriggerMode.TriggeredByCode, + }); + } + } + + public class ColorModeChangedEventArgs : EventArgs + { + public bool IsPreviousUsedDarkColor { get; set; } + public bool IsNowUsingDarkColor { get; set; } + public TriggerMode TriggerMode { get; set; } + } + + #endregion + + #region 選中顏色成員 + + /// + /// 內部變量,代表當前選中的顏色 + /// + private ColorPaletteColor _colorSelected = ColorPaletteColor.ColorRed; + + /// + /// 公共成員,讀取或修改當前選中的顏色,實時生效 + /// + public ColorPaletteColor SelectedColor { + get => _colorSelected; + set { + var pre = _colorSelected; + _colorSelected = value; + UpdateColorButtonsCheckedDisplayStatus(); + UpdateCustomColorButtonCheckedDisplayStatus(); + ColorSelectionChanged?.Invoke(this, new ColorSelectionChangedEventArgs { + PreviousColor = pre, + NowColor = value, + TriggerMode = TriggerMode.TriggeredByCode, + CustomColor = value == ColorPaletteColor.ColorCustom ? (Color)_customColor : new Color(), + }); + } + } + + public class ColorSelectionChangedEventArgs : EventArgs { + public ColorPaletteColor PreviousColor { get; set; } + public ColorPaletteColor NowColor { get; set; } + public TriggerMode TriggerMode { get; set; } + public Color CustomColor { get; set; } + } + + #endregion + + #region 自定義顏色成員 + + private Color? _customColor = null; + + public Color? CustomColor { + get => _customColor; + set { + var pre = _customColor; + _customColor = value; + CustomColorChanged?.Invoke(this, new CustomColorChangedEventArgs { + PreviousColor = pre, + NowColor = value, + TriggerMode = TriggerMode.TriggeredByCode, + }); + } + } + + public class CustomColorChangedEventArgs : EventArgs + { + public Color? PreviousColor { get; set; } + public Color? NowColor { get; set; } + public TriggerMode TriggerMode { get; set; } + } + + #endregion + + #region 墨跡顏色相關邏輯 + + /// + /// 按下的顏色按鈕,用於顏色按鈕鼠標事件 + /// + private Border lastColorBtnDown; + + #region 顏色按鈕和自定義顏色按鈕 選中狀態管理 + + /// + /// 顏色按鈕的選中狀態更新函數,根據選中的顏色判斷對勾的前景色 + /// + public void UpdateColorButtonsCheckedDisplayStatus() { + foreach (var bd in ColorPaletteColorButtonBorders) { + bd.Child = null; + } + + UpdateCustomColorButtonCheckedDisplayStatus(); + if (_colorSelected == ColorPaletteColor.ColorCustom) return; + var index = (int)_colorSelected; + var bdSel = ColorPaletteColorButtonBorders[index]; + Image checkedImage = new Image(); + checkedImage.Width = 24; + checkedImage.Height = 24; + var checkLight = this.FindResource("CheckedLightIcon"); + var checkDark = this.FindResource("CheckedDarkIcon"); + if (ColorUtilities.GetReverseForegroundColor(ColorUtilities.GetGrayLevel((_usingDarkColors?_darkColors:_lightColors)[(int)_colorSelected])) == Colors.Black) checkedImage.Source = checkDark as DrawingImage; + else checkedImage.Source = checkLight as DrawingImage; + bdSel.Child = checkedImage; + } + + /// + /// 更新自定義顏色按鈕的選中狀態 + /// + private void UpdateCustomColorButtonCheckedDisplayStatus() { + if (_customColor == null) { + CustomColorButtonColorBorder.Visibility = Visibility.Collapsed; + CustomColorButtonIcon.Visibility = Visibility.Visible; + } else { + CustomColorButtonColorBorder.Visibility = Visibility.Visible; + CustomColorButtonColorBorder.Background = new SolidColorBrush((Color)_customColor); + CustomColorButtonIcon.Visibility = Visibility.Collapsed; + if (_colorSelected == ColorPaletteColor.ColorCustom) + CustomColorButtonColorBorderCheckIcon.Visibility = Visibility.Visible; + else CustomColorButtonColorBorderCheckIcon.Visibility = Visibility.Collapsed; + CustomColorButtonColorBorderCheckIcon.Source = + this.FindResource(ColorUtilities.GetReverseForegroundColor(ColorUtilities.GetGrayLevel((Color)_customColor)) == Colors.White + ? "CheckedLightIcon" + : "CheckedDarkIcon") as DrawingImage; + } + } + + #endregion + + #region 顏色按鈕的鼠標事件 + + private void ColorButton_MouseDown(object sender, MouseButtonEventArgs e) { + lastColorBtnDown = sender as Border; + ColorBtnStoryboardScaleSmaller(sender); + } + + private void ColorButton_MouseUp(object sender, MouseButtonEventArgs e) { + if (lastColorBtnDown != sender) return; + lastColorBtnDown = null; + var border = (Border)sender; + var index = Array.IndexOf(ColorPaletteColorStrings, border.Name.ToLower()); + + var pre = _colorSelected; + _colorSelected = (ColorPaletteColor)index; + UpdateColorButtonsCheckedDisplayStatus(); + ColorBtnStoryboardScaleLarger(sender); + + ColorSelectionChanged?.Invoke(this, new ColorSelectionChangedEventArgs { + PreviousColor = pre, + NowColor = (ColorPaletteColor)index, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + private void ColorButton_MouseLeave(object sender, MouseEventArgs e) { + if (lastColorBtnDown != null) { + var la = lastColorBtnDown as Border; + var st = la.RenderTransform as ScaleTransform; + if (st.ScaleX != 1 && st.ScaleY != 1) ColorBtnStoryboardScaleLarger(lastColorBtnDown); + } + + lastColorBtnDown = null; + } + + #endregion + + #region 自定義顏色 Picker 相關邏輯 + + private void UpdateCustomColorPickerDisplayStatus() { + if (_customColor == null) { + CustomColorHexTextBox.Text = "请在上方选择一个颜色"; + CustomColorHexBorder.Background = new SolidColorBrush(Colors.Transparent); + } else { + CustomColorHexTextBox.Text = "#" + CustomColorPicker.SelectedColor.R.ToString("X2") + CustomColorPicker.SelectedColor.G.ToString("X2") + CustomColorPicker.SelectedColor.B.ToString("X2"); + CustomColorHexBorder.Background = new SolidColorBrush(CustomColorPicker.SelectedColor); + } + } + + private void CustomColorPicker_ColorChanged(object sender, RoutedEventArgs e) { + var cp = sender as SquarePicker; + var pre = _customColor; + _customColor = cp.SelectedColor; + Trace.WriteLine(_customColor); + if (_colorSelected != ColorPaletteColor.ColorCustom) { + var mode_pre = _colorSelected; + _colorSelected = ColorPaletteColor.ColorCustom; + ColorSelectionChanged?.Invoke(this, new ColorSelectionChangedEventArgs { + PreviousColor = mode_pre, + NowColor = _colorSelected, + TriggerMode = TriggerMode.TriggeredByUser, + CustomColor = cp.SelectedColor, + }); + } + CustomColorChanged?.Invoke(this, new CustomColorChangedEventArgs { + PreviousColor = pre, + NowColor = _customColor, + TriggerMode = TriggerMode.TriggeredByUser, + }); + UpdateCustomColorPickerDisplayStatus(); + UpdateCustomColorButtonCheckedDisplayStatus(); + UpdateColorButtonsCheckedDisplayStatus(); + } + + #endregion + + #region 顏色按鈕Storyboard動畫邏輯 + + private void ColorBtnStoryBoardScaleAnimation(object sender, double from, double to) { + var border = sender as Border; + + var sb = new Storyboard(); + var scaleAnimationX = new DoubleAnimation() { + From = from, + To = to, + Duration = new Duration(TimeSpan.FromMilliseconds(100)) + }; + var scaleAnimationY = new DoubleAnimation() { + From = from, + To = to, + Duration = new Duration(TimeSpan.FromMilliseconds(100)) + }; + scaleAnimationY.EasingFunction = new CubicEase(); + scaleAnimationX.EasingFunction = new CubicEase(); + Storyboard.SetTargetProperty(scaleAnimationX, + new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)")); + Storyboard.SetTargetProperty(scaleAnimationY, + new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)")); + sb.Children.Add(scaleAnimationX); + sb.Children.Add(scaleAnimationY); + + sb.Begin(border); + } + + private void ColorBtnStoryboardScaleSmaller(object sender) { + ColorBtnStoryBoardScaleAnimation(sender, 1D, 0.9); + } + + private void ColorBtnStoryboardScaleLarger(object sender) { + ColorBtnStoryBoardScaleAnimation(sender, 0.9, 1D); + } + + #endregion + + #region 顏色按鈕的亮色暗色和切換按鈕 相關邏輯 + + /// + /// 根據是否使用暗色配色方案來更新顏色按鈕的背景顏色,並更新切換按鈕的UI文字 + /// + private void UpdateColorPaletteColorsAndColorModeChangeButton() { + foreach (var bd in ColorPaletteColorButtonBorders) { + bd.Background = + new SolidColorBrush((_usingDarkColors ? _darkColors : _lightColors)[ + Array.IndexOf(ColorPaletteColorStrings, bd.Name.ToLower())]); + } + + ChangedColorButtonsTransparentVisibility(_penModeSelected == PenMode.HighlighterMode); + + var tb = ((SimpleStackPanel)ColorModeChangeButton.Content).Children.OfType().Single(); + tb.Text = _usingDarkColors ? "亮色" : "暗色"; + } + + private void ColorModeChangeButton_Clicked(object sender, RoutedEventArgs e) { + var pre = _usingDarkColors; + _usingDarkColors = !_usingDarkColors; + UpdateColorPaletteColorsAndColorModeChangeButton(); + UpdateColorButtonsCheckedDisplayStatus(); + ColorModeChanged?.Invoke(this, new ColorModeChangedEventArgs() + { + IsPreviousUsedDarkColor = pre, + IsNowUsingDarkColor = _usingDarkColors, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + #endregion + + #region GetRawColor + + /// + /// 根據傳入的 獲取對應的顏色 + /// + /// 傳入 ColorPaletteColor + /// 如果傳入true,則不會遵循調色板的亮色暗色配色方案, 則不能為null。 + /// 指定是否使用暗色配色方案,如果為 false,使用亮色,如果在 true 的情況下傳入 null 會報錯。 + /// 傳入的 ColorPaletteColor 對應的顏色 + public Color GetColor(ColorPaletteColor color, bool doNotFollowPaletteColor, bool? isDarkPalette) { + if (doNotFollowPaletteColor && isDarkPalette == null) throw new ArgumentNullException(nameof(isDarkPalette),"指定了自訂的配色方案卻沒有傳入正確的 isDarkPalette。"); + if (color == ColorPaletteColor.ColorCustom) return _customColor??new Color(); + return (doNotFollowPaletteColor + ? ((bool)isDarkPalette ? _darkColors : _lightColors) + : (_usingDarkColors ? _darkColors : _lightColors))[(int)color]; + } + + #endregion + + #region 隨機切換顏色按鈕 邏輯 + + public static int StrictNext(int maxValue = int.MaxValue) { + return new Random(BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0)).Next(maxValue); + } + + private void RandomColorButton_Clicked(object sender, RoutedEventArgs e) { + var pre = _colorSelected; + _colorSelected = (ColorPaletteColor)StrictNext(14); + UpdateColorButtonsCheckedDisplayStatus(); + UpdateCustomColorButtonCheckedDisplayStatus(); + ColorSelectionChanged?.Invoke(this, new ColorSelectionChangedEventArgs { + PreviousColor = pre, + NowColor = _colorSelected, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + #endregion + + #region 顏色按鈕透明度 邏輯 + + private void ChangedColorButtonsTransparentVisibility(bool isTransparent) { + foreach (var bd in ColorPaletteColorButtonBorders) { + var ori_color = ((SolidColorBrush)bd.Background).Color; + if (isTransparent) ori_color.A = (byte)Math.Round(byte.MaxValue * 0.6,0); + else ori_color.A = byte.MaxValue; + bd.Background = new SolidColorBrush(ori_color); + } + } + + #endregion + + #endregion + + #region 自定義顏色 相關邏輯 + + private void CustomColorButton_Clicked(object sender, RoutedEventArgs e) { + if (_customColor == null) { + CustomColorPanel.Visibility = Visibility.Visible; + } else { + if (_colorSelected == ColorPaletteColor.ColorCustom) CustomColorPanel.Visibility = Visibility.Visible; + else { + var pre = _colorSelected; + _colorSelected = ColorPaletteColor.ColorCustom; + ColorSelectionChanged?.Invoke(this, new ColorSelectionChangedEventArgs() { + PreviousColor = pre, + NowColor = _colorSelected, + TriggerMode = TriggerMode.TriggeredByUser, + CustomColor = (Color)_customColor, + }); + UpdateColorButtonsCheckedDisplayStatus(); + UpdateCustomColorButtonCheckedDisplayStatus(); + UpdateCustomColorPickerDisplayStatus(); + } + } + } + + #endregion + + #region 墨跡糾正成員 + + private bool _inkRecognition = true; + public bool InkRecognition { + get => _inkRecognition; + set { + var pre = _inkRecognition; + _inkRecognition = value; + InkRecognitionChanged?.Invoke(this, new InkRecognitionChangedEventArgs + { + PreviousStatus = pre, + NowStatus = value, + TriggerMode = TriggerMode.TriggeredByCode, + }); + } + } + + public class InkRecognitionChangedEventArgs : EventArgs { + public bool PreviousStatus { get; set; } + public bool NowStatus { get; set; } + public TriggerMode TriggerMode { get; set; } + public InkRecognitionOptions Options { get; set; } + } + + #endregion + + #region 墨跡糾正相關邏輯 + + private void InkRecognitionMoreButton_MouseDown(object sender, MouseButtonEventArgs e) { + var ircm = this.FindResource("InkRecognitionContextMenu") as ContextMenu; + var transform = InkRecognitionMoreButton.TransformToVisual(this); + var pt = transform.Transform(new Point(0, 0)); + ircm.VerticalOffset = pt.Y-4; + ircm.HorizontalOffset = pt.X-4; + ircm.IsOpen = true; + ircm.StaysOpen = true; + } + + #region 墨跡糾正更多菜單 相關邏輯 + + /// + /// 更新墨跡糾正菜單裡面菜單項的選中狀態,根據_inkRecognition來做更新 + /// + private void UpdateInkRecognitionContextMenuDisplayStatus() { + var ircm = (ContextMenu)this.FindResource("InkRecognitionContextMenu"); + var enableRecog = ircm.Items[0] as MenuItem; + enableRecog.IsChecked = _inkRecognition; + var recogTri = ircm.Items[2] as MenuItem; + var recogQua = ircm.Items[3] as MenuItem; + var recogEll = ircm.Items[4] as MenuItem; + var recogPlg = ircm.Items[5] as MenuItem; + var recogSub = new MenuItem[] { + recogTri, recogQua, recogEll, recogPlg, + }; + foreach (var mi in recogSub) mi.IsEnabled = _inkRecognition; + } + + private void InkRecognitionContextMenuItem_Checked(object sender, RoutedEventArgs e) { + var mi = (MenuItem)sender; + if (mi.Name == "EnableRecog") { + var pre = _inkRecognition; + Trace.WriteLine(mi.IsChecked); + _inkRecognition = mi.IsChecked; + + UpdateInkRecognitionContextMenuDisplayStatus(); + InkRecognitionToggleSwitchImage.Source = + this.FindResource(_inkRecognition ? "SwitchOnImage" : "SwitchOffImage") as DrawingImage; + + InkRecognitionChanged?.Invoke(this, new InkRecognitionChangedEventArgs + { + PreviousStatus = pre, + NowStatus = _inkRecognition, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } else { + UpdateInkRecognitionContextMenuDisplayStatus(); + InkRecognitionChanged?.Invoke(this, new InkRecognitionChangedEventArgs + { + PreviousStatus = _inkRecognition, + NowStatus = _inkRecognition, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + } + + private void InkRecognitionContextMenu_Closed(object sender, RoutedEventArgs e) { + InkRecognitionMoreButton.Background = new SolidColorBrush(Colors.Transparent); + UpdateInkRecognitionContextMenuDisplayStatus(); + } + + private void InkRecognitionContextMenu_Opened(object sender, RoutedEventArgs e) { + InkRecognitionMoreButton.Background = new SolidColorBrush(Color.FromArgb(34,39, 39, 42)); + UpdateInkRecognitionContextMenuDisplayStatus(); + } + + #endregion + + #region 墨跡糾正開關 相關邏輯 + + private void InkRecognitionToggleSwitchButton_Clicked(object sender, RoutedEventArgs e) { + var pre = _inkRecognition; + _inkRecognition = !_inkRecognition; + InkRecognitionToggleSwitchImage.Source = + this.FindResource(_inkRecognition ? "SwitchOnImage" : "SwitchOffImage") as DrawingImage; + UpdateInkRecognitionContextMenuDisplayStatus(); + InkRecognitionChanged?.Invoke(this, new InkRecognitionChangedEventArgs + { + PreviousStatus = pre, + NowStatus = _inkRecognition, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + #endregion + + #endregion + + #region 關閉彈窗 邏輯 + + private bool isCloseButtonDown = false; + + private void CloseButtonBorder_MouseDown(object sender, MouseButtonEventArgs e) { + isCloseButtonDown = true; + CloseButtonBorder.Background = new SolidColorBrush(Color.FromArgb(34, 220, 38, 38)); + } + + private void CloseButtonBorder_MouseLeave(object sender, MouseEventArgs e) { + isCloseButtonDown = false; + CloseButtonBorder.Background = new SolidColorBrush(Colors.Transparent); + } + + private void CloseButtonBorder_MouseUp(object sender, MouseButtonEventArgs e) { + if (!isCloseButtonDown) return; + + CloseButtonBorder_MouseLeave(null, null); + PaletteShouldCloseEvent?.Invoke(this,new RoutedEventArgs()); + } + + #endregion + + #region 筆觸模式 成員 + + public enum PenMode { + PenMode, + HighlighterMode, + LaserPenMode + } + + private PenMode _penModeSelected = PenMode.PenMode; + public PenMode PenModeSelected + { + get => _penModeSelected; + set + { + var pre = _penModeSelected; + _penModeSelected = value; + UpdatePenModeButtonsCheckedDisplayStatus(); + ChangedColorButtonsTransparentVisibility(_penModeSelected == PenMode.HighlighterMode); + + PenModeChanged?.Invoke(this, new PenModeChangedEventArgs() + { + PreviousMode = pre, + NowMode = value, + TriggerMode = TriggerMode.TriggeredByCode, + }); + } + } + + public class PenModeChangedEventArgs : EventArgs + { + public PenMode PreviousMode { get; set; } + public PenMode NowMode { get; set; } + public TriggerMode TriggerMode { get; set; } + } + + #endregion + + #region 筆觸模式 相關邏輯 + + private void UpdatePenModeButtonsCheckedDisplayStatus() { + foreach (var bd in PenModeTabButtonBorders) { + bd.Background = new SolidColorBrush(Colors.Transparent); + } + foreach (var indicator in PenModeTabButtonIndicators) { + indicator.Visibility = Visibility.Hidden; + } + foreach (var gd in PenModeTabButtonIcons) { + gd.Brush = new SolidColorBrush(Color.FromRgb(63, 63, 70)); + } + foreach (var text in PenModeTabButtonTexts) { + text.Foreground = new SolidColorBrush(Color.FromRgb(63, 63, 70)); + text.FontWeight = FontWeights.Normal; + } + + PenModeTabButtonBorders[(int)_penModeSelected].Background = new SolidColorBrush(Color.FromArgb(34, 59, 130, 246)); + PenModeTabButtonIndicators[(int)_penModeSelected].Visibility = Visibility.Visible; + PenModeTabButtonIcons[(int)_penModeSelected].Brush = new SolidColorBrush(Color.FromRgb(37, 99, 235)); + PenModeTabButtonTexts[(int)_penModeSelected].Foreground = new SolidColorBrush(Color.FromRgb(37, 99, 235)); + PenModeTabButtonTexts[(int)_penModeSelected].FontWeight = FontWeights.Bold; + } + + /// + /// 根據傳入的觸筆模式修改Quick Action 子項目的可見性。 + /// + /// + private void UpdateQuickActionItemsVisibilityByPenMode(PenMode penMode) { + _isDisplayQuickActions = true; + if (penMode == PenMode.PenMode) { + _isDisplayInkRecognitionQuickAction = true; + _isDisplayInkPressureQuickAction = true; + _isDisplayCircleTipShapeQuickAction = false; + _isDisplayFingerModeQuickAction = true; + } else if (penMode == PenMode.HighlighterMode) { + _isDisplayInkRecognitionQuickAction = true; + _isDisplayInkPressureQuickAction = false; + _isDisplayCircleTipShapeQuickAction = true; + _isDisplayFingerModeQuickAction = true; + } else if (penMode == PenMode.LaserPenMode) { + _isDisplayQuickActions = false; + } + UpdateQuickActionVisibility(_isDisplayQuickActions); + UpdateQuickActionItemsVisibility(new QuickActionItemsVisibility() { + InkRecognition = _isDisplayInkRecognitionQuickAction, + InkPressure = _isDisplayInkPressureQuickAction, + CircleTipShape = _isDisplayCircleTipShapeQuickAction, + FingerMode = _isDisplayFingerModeQuickAction + }); + } + + private void PenTabButton_MouseDown(object sender, MouseButtonEventArgs e) { + var pre = _penModeSelected; + _penModeSelected = (PenMode)Array.IndexOf(PenModeTabButtonBorders, (Border)sender); + QuickActionItemsScrollToLeft(); + UpdatePenModeButtonsCheckedDisplayStatus(); + ChangedColorButtonsTransparentVisibility(_penModeSelected == PenMode.HighlighterMode); + UpdateQuickActionItemsVisibilityByPenMode(_penModeSelected); + + PenModeChanged?.Invoke(this, new PenModeChangedEventArgs() + { + PreviousMode = pre, + NowMode = _penModeSelected, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + #endregion + + #region Quick Action 滾動邏輯 + + private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e) { + e.Handled = true; + } + + public static class ScrollViewerBehavior + { + public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(ScrollViewerBehavior), new UIPropertyMetadata(0.0, OnHorizontalOffsetChanged)); + public static void SetHorizontalOffset(FrameworkElement target, double value) => target.SetValue(HorizontalOffsetProperty, value); + public static double GetHorizontalOffset(FrameworkElement target) => (double)target.GetValue(HorizontalOffsetProperty); + private static void OnHorizontalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) => (target as ScrollViewer)?.ScrollToHorizontalOffset((double)e.NewValue); + + public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerBehavior), new UIPropertyMetadata(0.0, OnVerticalOffsetChanged)); + public static void SetVerticalOffset(FrameworkElement target, double value) => target.SetValue(VerticalOffsetProperty, value); + public static double GetVerticalOffset(FrameworkElement target) => (double)target.GetValue(VerticalOffsetProperty); + private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) => (target as ScrollViewer)?.ScrollToVerticalOffset((double)e.NewValue); + } + + private void UpdateQuickActionsDotsIndicatorDisplayStatus(int highlightedDotIndex) { + if (highlightedDotIndex == 1) { + QuickActionDot1.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); + QuickActionDot2.Background = new SolidColorBrush(Color.FromRgb(212, 212, 216)); + } else { + QuickActionDot2.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); + QuickActionDot1.Background = new SolidColorBrush(Color.FromRgb(212, 212, 216)); + } + } + + private void QuickActionsScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { + UpdateQuickActionsDotsIndicatorDisplayStatus(((ScrollViewer)sender).HorizontalOffset >= 110 ? 2 : 1); + } + + private void QuickActionsScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + ScrollViewer scrollViewer = (ScrollViewer)sender; + if (e.Delta < 0) { + //scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + 270); + var sb = new Storyboard(); + var ofs = scrollViewer.HorizontalOffset; + var animation = new DoubleAnimation + { + From = ofs, + To = 120, + Duration = TimeSpan.FromMilliseconds(200) + }; + animation.EasingFunction = new CubicEase() { + EasingMode = EasingMode.EaseOut, + }; + Storyboard.SetTargetProperty(animation, new PropertyPath(ScrollViewerBehavior.HorizontalOffsetProperty)); + Storyboard.SetTargetName(animation,"QuickActionScrollViewer"); + sb.Children.Add(animation); + scrollViewer.ScrollToHorizontalOffset(ofs); + sb.Begin(scrollViewer); + } else { + //scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - 270); + var sb = new Storyboard(); + var ofs = scrollViewer.HorizontalOffset; + var animation = new DoubleAnimation + { + From = ofs, + To = 0, + Duration = TimeSpan.FromMilliseconds(200) + }; + animation.EasingFunction = new CubicEase(){ + EasingMode = EasingMode.EaseOut, + }; + Storyboard.SetTargetProperty(animation, new PropertyPath(ScrollViewerBehavior.HorizontalOffsetProperty)); + Storyboard.SetTargetName(animation,"QuickActionScrollViewer"); + sb.Children.Add(animation); + scrollViewer.ScrollToHorizontalOffset(ofs); + sb.Begin(scrollViewer); + } + e.Handled = true; + } + + private void QuickActionItemsScrollToLeft() { + var sb = new Storyboard(); + var ofs = QuickActionScrollViewer.HorizontalOffset; + var animation = new DoubleAnimation + { + From = ofs, + To = 0, + Duration = TimeSpan.FromMilliseconds(200) + }; + animation.EasingFunction = new CubicEase(){ + EasingMode = EasingMode.EaseOut, + }; + Storyboard.SetTargetProperty(animation, new PropertyPath(ScrollViewerBehavior.HorizontalOffsetProperty)); + Storyboard.SetTargetName(animation,"QuickActionScrollViewer"); + sb.Children.Add(animation); + QuickActionScrollViewer.ScrollToHorizontalOffset(ofs); + sb.Begin(QuickActionScrollViewer); + } + + #endregion + + #region Quick Action 可見性管理 成員 + + private bool _isDisplayQuickActions = true; + public bool IsDisplayQuickActions + { + get => _isDisplayQuickActions; + set + { + var pre = _isDisplayQuickActions; + _isDisplayQuickActions = value; + UpdateQuickActionVisibility(value); + UpdateQuickActionItemsVisibility(new QuickActionItemsVisibility() { + InkRecognition = _isDisplayInkRecognitionQuickAction, + InkPressure = _isDisplayInkPressureQuickAction, + CircleTipShape = _isDisplayCircleTipShapeQuickAction, + FingerMode = _isDisplayFingerModeQuickAction + }); + + QuickActionsVisibilityChanged?.Invoke(this, new QuickActionsVisibilityChangedEventsArgs() + { + PreviousStatus = pre, + NowStatus = value, + TriggerMode = TriggerMode.TriggeredByCode, + }); + } + } + + public struct QuickActionItemsVisibility { + public bool InkRecognition; + public bool InkPressure; + public bool CircleTipShape; + public bool FingerMode; + } + + public class QuickActionsVisibilityChangedEventsArgs : EventArgs { + public bool PreviousStatus { get; set; } + public bool NowStatus { get; set; } + public TriggerMode TriggerMode { get; set; } + public bool IsItemsVisibilityChanged { get; set; } = false; + public QuickActionItemsVisibility ItemsVisibility { get; set; } + } + + private void InvokeQuickActionItemsVisibilityEvent() { + UpdateQuickActionItemsVisibility(new QuickActionItemsVisibility() { + InkRecognition = _isDisplayInkRecognitionQuickAction, + InkPressure = _isDisplayInkPressureQuickAction, + CircleTipShape = _isDisplayCircleTipShapeQuickAction, + FingerMode = _isDisplayFingerModeQuickAction + }); + QuickActionsVisibilityChanged?.Invoke(this, new QuickActionsVisibilityChangedEventsArgs() + { + PreviousStatus = _isDisplayQuickActions, + NowStatus = _isDisplayQuickActions, + TriggerMode = TriggerMode.TriggeredByCode, + IsItemsVisibilityChanged = true, + ItemsVisibility = new QuickActionItemsVisibility() { + InkRecognition = _isDisplayInkRecognitionQuickAction, + InkPressure = _isDisplayInkPressureQuickAction, + CircleTipShape = _isDisplayCircleTipShapeQuickAction, + FingerMode = _isDisplayFingerModeQuickAction + } + }); + } + + private bool _isDisplayInkRecognitionQuickAction = true; + + public bool IsDisplayInkRecognitionQuickAction { + get => _isDisplayInkRecognitionQuickAction; + set { + _isDisplayInkRecognitionQuickAction = value; + InvokeQuickActionItemsVisibilityEvent(); + } + } + + private bool _isDisplayInkPressureQuickAction = true; + + public bool IsDisplayInkPressureQuickAction { + get => _isDisplayInkPressureQuickAction; + set { + _isDisplayInkPressureQuickAction = value; + InvokeQuickActionItemsVisibilityEvent(); + } + } + + private bool _isDisplayCircleTipShapeQuickAction = true; + + public bool IsDisplayCircleTipShapeQuickAction { + get => _isDisplayCircleTipShapeQuickAction; + set { + _isDisplayCircleTipShapeQuickAction = value; + InvokeQuickActionItemsVisibilityEvent(); + } + } + + private bool _isDisplayFingerModeQuickAction = true; + + public bool IsDisplayFingerModeQuickAction { + get => _isDisplayFingerModeQuickAction; + set { + _isDisplayFingerModeQuickAction = value; + InvokeQuickActionItemsVisibilityEvent(); + } + } + + #endregion + + #region Quick Action 可見性管理 相關邏輯 + + private void UpdateQuickActionItemsVisibility(QuickActionItemsVisibility visibility) { + QuickActionItems.Children[0].Visibility = visibility.InkRecognition ? Visibility.Visible : Visibility.Collapsed; + QuickActionItems.Children[1].Visibility = visibility.InkPressure ? Visibility.Visible : Visibility.Collapsed; + QuickActionItems.Children[2].Visibility = visibility.CircleTipShape ? Visibility.Visible : Visibility.Collapsed; + QuickActionItems.Children[3].Visibility = visibility.FingerMode ? Visibility.Visible : Visibility.Collapsed; + } + + private void UpdateQuickActionVisibility(bool isVisible) { + foreach (var fe in new FrameworkElement[] { + _QuickAction_Grid, + _QuickAction_Dots, + _QuickAction_Line1, + _QuickAction_Line2 + }) { + fe.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed; + } + } + + #endregion + + public enum TriggerMode { + TriggeredByUser, + TriggeredByCode + } + + public enum PressureSimulation { + PointSimulate, + VelocitySimulate, + None + } + + private PressureSimulation _simulatePressure = PressureSimulation.PointSimulate; + public PressureSimulation SimulatePressure + { + get => _simulatePressure; + set + { + var pre = _simulatePressure; + _simulatePressure = value; + PressureSimulationChanged?.Invoke(this, new PressureSimulationChangedEventArgs() + { + PreviousMode = pre, + NowMode = value, + TriggerMode = TriggerMode.TriggeredByCode, + }); + } + } + + private void UpdateSimulatePressureContextMenuDisplayStatus() + { + var spcm = (ContextMenu)this.FindResource("SimulatePressureContextMenu"); + var pointSP = spcm.Items[0] as MenuItem; + var velocitySP = spcm.Items[1] as MenuItem; + var noneSP = spcm.Items[2] as MenuItem; + var pressSub = new MenuItem[] { + pointSP, velocitySP, noneSP + }; + isSimulatePressureCheckedByUser = false; + foreach (var mi in pressSub) { + if (mi.Name=="PointSP") mi.IsChecked = _simulatePressure == PressureSimulation.PointSimulate; + else if (mi.Name == "VelocitySP") mi.IsChecked = _simulatePressure == PressureSimulation.VelocitySimulate; + else if (mi.Name == "NoneSP") mi.IsChecked = _simulatePressure == PressureSimulation.None; + } + isSimulatePressureCheckedByUser = true; + } + + private void SimulatePressureContextMenu_Closed(object sender, RoutedEventArgs e) { + SimulatePressureMoreButton.Background = new SolidColorBrush(Colors.Transparent); + UpdateSimulatePressureContextMenuDisplayStatus(); + } + + private void SimulatePressureContextMenu_Opened(object sender, RoutedEventArgs e) { + SimulatePressureMoreButton.Background = new SolidColorBrush(Color.FromArgb(34, 39, 39, 42)); + UpdateSimulatePressureContextMenuDisplayStatus(); + } + + private bool isSimulatePressureCheckedByUser = true; + + private void SimulatePressureContextMenuItem_Checked(object sender, RoutedEventArgs e) { + if (!isSimulatePressureCheckedByUser) return; + var mi = (MenuItem)sender; + var pre = _simulatePressure; + Trace.WriteLine(mi.Name); + _simulatePressure = mi.Name == "PointSP" ? PressureSimulation.PointSimulate : mi.Name == "VelocitySP" ? PressureSimulation.VelocitySimulate : PressureSimulation.None; + SimulatePressureToggleSwitchImage.Source = + this.FindResource(_simulatePressure != PressureSimulation.None ? "SwitchOnImage" : "SwitchOffImage") as DrawingImage; + UpdateSimulatePressureContextMenuDisplayStatus(); + PressureSimulationChanged?.Invoke(this, new PressureSimulationChangedEventArgs() + { + PreviousMode = pre, + NowMode = _simulatePressure, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + private void SimulatePressureToggleSwitchButton_Clicked(object sender, RoutedEventArgs e) { + var pre = _simulatePressure; + _simulatePressure = _simulatePressure == PressureSimulation.None ? PressureSimulation.PointSimulate : PressureSimulation.None; + SimulatePressureToggleSwitchImage.Source = + this.FindResource(_simulatePressure != PressureSimulation.None ? "SwitchOnImage" : "SwitchOffImage") as DrawingImage; + UpdateSimulatePressureContextMenuDisplayStatus(); + PressureSimulationChanged?.Invoke(this, new PressureSimulationChangedEventArgs() + { + PreviousMode = pre, + NowMode = _simulatePressure, + TriggerMode = TriggerMode.TriggeredByUser, + }); + } + + private void SimulatePressureMoreButton_MouseDown(object sender, MouseButtonEventArgs e) { + var spcm = this.FindResource("SimulatePressureContextMenu") as ContextMenu; + var transform = SimulatePressureMoreButton.TransformToVisual(this); + var pt = transform.Transform(new Point(0, 0)); + spcm.VerticalOffset = pt.Y - 4; + spcm.HorizontalOffset = pt.X - 4; + spcm.IsOpen = !spcm.IsOpen; + } + + + public class PressureSimulationChangedEventArgs : EventArgs + { + public PressureSimulation PreviousMode { get; set; } + public PressureSimulation NowMode { get; set; } + public TriggerMode TriggerMode { get; set; } + } + + public class InkRecognitionOptions { + public bool isEnableInkRecognition; + public bool isRecognizeEllipse; + public bool isRecognizeTriangle; + public bool isRecognizeQuadrilateral; + public bool isRecognizePolygon; + } + + private void BackToPaletteButton_Clicked(object sender, RoutedEventArgs e) { + CustomColorPanel.Visibility = Visibility.Collapsed; + } + + public event EventHandler ColorSelectionChanged; + public event EventHandler CustomColorChanged; + public event EventHandler PenModeChanged; + public event EventHandler InkRecognitionChanged; + public event EventHandler PressureSimulationChanged; + public event EventHandler ColorModeChanged; + public event EventHandler QuickActionsVisibilityChanged; + public event EventHandler PaletteShouldCloseEvent; + + public ColorPalette() { + InitializeComponent(); + ColorPaletteColorButtonBorders = new Border[] { + Black, White, Red, Orange, Yellow, Lime, Green, Teal, Cyan, Blue, Indigo, Purple, Fuchsia, Pink, Rose + }; + PenModeTabButtonBorders = new Border[] { + PenTabButton, HighlighterTabButton, LaserPenTabButton + }; + PenModeTabButtonIndicators = new SimpleStackPanel[] { + PenTabButtonIndicator, HighlighterTabButtonIndicator, LaserPenTabButtonIndicator + }; + PenModeTabButtonIcons = new GeometryDrawing[] { + PenTabButtonIcon, HighlighterTabButtonIcon, LaserPenTabButtonIcon + }; + PenModeTabButtonTexts = new TextBlock[] { + PenTabButtonText, HighlighterTabButtonText, LaserPenTabButtonText + }; + foreach (var bd in ColorPaletteColorButtonBorders) { + bd.RenderTransformOrigin = new Point(0.5, 0.5); + bd.RenderTransform = new ScaleTransform(1, 1); + } + + UpdatePenModeButtonsCheckedDisplayStatus(); + UpdateColorButtonsCheckedDisplayStatus(); + UpdateColorPaletteColorsAndColorModeChangeButton(); + ChangedColorButtonsTransparentVisibility(false); + UpdateQuickActionsDotsIndicatorDisplayStatus(1); + UpdateQuickActionItemsVisibilityByPenMode(PenMode.PenMode); + } + } +} \ No newline at end of file diff --git a/InkCanvasForClass/Popups/FloatingToolBarV2.xaml b/InkCanvasForClass/Popups/FloatingToolBarV2.xaml new file mode 100644 index 00000000..0dea6b24 --- /dev/null +++ b/InkCanvasForClass/Popups/FloatingToolBarV2.xaml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Popups/FloatingToolBarV2.xaml.cs b/InkCanvasForClass/Popups/FloatingToolBarV2.xaml.cs new file mode 100644 index 00000000..ff2a3444 --- /dev/null +++ b/InkCanvasForClass/Popups/FloatingToolBarV2.xaml.cs @@ -0,0 +1,936 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using static Ink_Canvas.Popups.ColorPalette; +using static Ink_Canvas.Popups.SelectionPopup; +using static Ink_Canvas.Popups.ShapeDrawingPopup; + +namespace Ink_Canvas +{ + + public enum FloatingBarItemType { + Button, + StateButton, + Separator + } + + public class FloatingBarItem { + public string Name { get; set; } + public ImageSource IconSource { get; set; } + public bool Selected { get; set; } = false; + public bool IsVisible { get; set; } = true; + public string IconSourceResourceKey { get; set; } + public double IconHeight { get; set; } = 21; + public Color? IconColor { get; set; } = null; // 该属性仅用于纯色图标用于在动态透明模式下恢复图标原来的自定义颜色 + public bool IsSemiTransparent { get; set; } = false; + public Color? PressFeedbackColor { get; set; } = null; + public FloatingBarItemType Type { get; set; } + public MainWindow.ICCToolsEnum ToolType { get; set; } + public SolidColorBrush _backgroundBrush { + get { + if (Selected) return new SolidColorBrush(Color.FromArgb((byte)(IsSemiTransparent ? 128 : 255) ,37, 99, 235)); + return new SolidColorBrush(Colors.Transparent); + } + } + public Visibility _itemVisibility { + get { + if (!IsVisible) return Visibility.Collapsed; + return Type != FloatingBarItemType.Separator ? Visibility.Visible : Visibility.Collapsed; + } + } + public Visibility _separatorVisibility { + get { + if (!IsVisible) return Visibility.Collapsed; + return Type != FloatingBarItemType.Separator ? Visibility.Collapsed : Visibility.Visible; + } + } + public double _separatorOpacity { + get => IsSemiTransparent ? 0.35 : 1; + } + public SolidColorBrush _pressFeedbackColorBrush { + get { + if (PressFeedbackColor == null) return new SolidColorBrush(Color.FromArgb((byte)(IsSemiTransparent ? 96 : 255),225, 225, 225)); + return new SolidColorBrush(Color.FromArgb((byte)(IsSemiTransparent ? 96 : 255),((Color)PressFeedbackColor).R, + ((Color)PressFeedbackColor).G, ((Color)PressFeedbackColor).B)); + } + } + } + + /// + /// FloatingToolBarV2.xaml 的交互逻辑 + /// + public partial class FloatingToolBarV2 : Window { + + public FloatingToolBarV2() { + InitializeComponent(); + + ToolBarItemsControl.ItemsSource = ToolbarItems; + //var clonedDrawing = (FindResource("CursorIcon") as DrawingImage).Clone(); + //((clonedDrawing.Drawing as DrawingGroup).Children[0] as + // GeometryDrawing).Brush = new SolidColorBrush(Colors.Black); + + ToolbarItems.Add(new FloatingBarItem() { + Name = "Cursor", + IconSource = FindResource("CursorIcon") as DrawingImage, + IconSourceResourceKey = "CursorIcon", + IsVisible = true, + Selected = false, + IconHeight = 21.5, + ToolType = MainWindow.ICCToolsEnum.CursorMode, + Type = FloatingBarItemType.StateButton, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Pen", + IconSource = FindResource("PenIcon") as DrawingImage, + IconSourceResourceKey = "PenIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + ToolType = MainWindow.ICCToolsEnum.PenMode, + Type = FloatingBarItemType.StateButton, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Clear", + IconSource = FindResource("TrashBinIcon") as DrawingImage, + IconSourceResourceKey = "TrashBinIcon", + IsVisible = true, + IconColor = Color.FromRgb(224, 27, 36), + IconHeight = 22, + Selected = false, + PressFeedbackColor = Color.FromRgb(254, 226, 226), + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "SeparatorA", + IsVisible = true, + Type = FloatingBarItemType.Separator, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Eraser", + IconSource = FindResource("EraserIcon") as DrawingImage, + IconSourceResourceKey = "EraserIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + ToolType = MainWindow.ICCToolsEnum.EraseByGeometryMode, + Type = FloatingBarItemType.StateButton, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "ShapeDrawing", + IconSource = FindResource("ShapesIcon") as DrawingImage, + IconSourceResourceKey = "ShapesIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Select", + IconSource = FindResource("SelectIcon") as DrawingImage, + IconSourceResourceKey = "SelectIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + ToolType = MainWindow.ICCToolsEnum.LassoMode, + Type = FloatingBarItemType.StateButton, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "SeparatorB", + IsVisible = true, + Type = FloatingBarItemType.Separator, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Undo", + IconSource = FindResource("UndoIcon") as DrawingImage, + IconSourceResourceKey = "UndoIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Redo", + IconSource = FindResource("RedoIcon") as DrawingImage, + IconSourceResourceKey = "RedoIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "SeparatorC", + IsVisible = true, + Type = FloatingBarItemType.Separator, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Whiteboard", + IconSource = FindResource("WhiteboardIcon") as DrawingImage, + IconSourceResourceKey = "WhiteboardIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Gesture", + IconSource = FindResource("GestureIcon") as DrawingImage, + IconSourceResourceKey = "GestureIcon", + IsVisible = true, + IconHeight = 23, + Selected = false, + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Menu", + IconSource = FindResource("MoreIcon") as DrawingImage, + IconSourceResourceKey = "MoreIcon", + IsVisible = true, + IconHeight = 21.5, + Selected = false, + Type = FloatingBarItemType.Button, + }); + ToolbarItems.Add(new FloatingBarItem() { + Name = "Fold", + IconSource = FindResource("FoldIcon") as DrawingImage, + IconSourceResourceKey = "FoldIcon", + IsVisible = true, + IconHeight = 22.5, + Selected = false, + Type = FloatingBarItemType.Button, + }); + + ReMeasureToolBar(); + UpdateToolBarSelectedTool(MainWindow.ICCToolsEnum.CursorMode); + UpdateToolBarVariant(1); + UpdateToolBarDynamicOpacityVariant(1); + + double widthInDevicePixels = GetSystemMetrics(0); // SM_CXSCREEN + double widthInDIP = SystemParameters.WorkArea.Right; // Device independent pixels. + double scalingFactor = widthInDIP/widthInDevicePixels; + ScalingFactor = scalingFactor; + + } + + public void FloatingBarV2_Loaded(object sender, RoutedEventArgs e) { + + } + + public void HideAllPopups() { + PenPaletteV2Popup.IsOpen = false; + SelectionPopupV2.IsOpen = false; + ShapeDrawingPopupV2.IsOpen = false; + } + + #region PopupV2 事件转发和内部处理 + + /// + /// 绑定调色盘V2的事件,部分事件会在内部进行处理后转发到外部 + /// + /// + /// + private void PenPaletteV2_Loaded(object sender, RoutedEventArgs e) { + PenPaletteV2.ColorSelectionChanged += (o, args) => { + PenPaletteV2_ColorSelectionChanged?.Invoke(o, args); + }; + PenPaletteV2.CustomColorChanged += (o, args) => { + PenPaletteV2_CustomColorChanged?.Invoke(o, args); + }; + PenPaletteV2.PenModeChanged += (o, args) => { + PenPaletteV2_PenModeChanged?.Invoke(o, args); + }; + PenPaletteV2.InkRecognitionChanged += (o, args) => { + PenPaletteV2_InkRecognitionChanged?.Invoke(o, args); + }; + PenPaletteV2.PressureSimulationChanged += (o, args) => { + PenPaletteV2_PressureSimulationChanged?.Invoke(o, args); + }; + PenPaletteV2.ColorModeChanged += (o, args) => { + PenPaletteV2_ColorModeChanged?.Invoke(o, args); + }; + PenPaletteV2.QuickActionsVisibilityChanged += (o, args) => { + PenPaletteV2_QuickActionsVisibilityChanged?.Invoke(o, args); + }; + PenPaletteV2.PaletteShouldCloseEvent += (o, args) => { + PenPaletteV2Popup.IsOpen = false; + PenPaletteV2_PaletteShouldCloseEvent?.Invoke(o, args); + }; + } + + public event EventHandler PenPaletteV2_ColorSelectionChanged; + public event EventHandler PenPaletteV2_CustomColorChanged; + public event EventHandler PenPaletteV2_PenModeChanged; + public event EventHandler PenPaletteV2_InkRecognitionChanged; + public event EventHandler PenPaletteV2_PressureSimulationChanged; + public event EventHandler PenPaletteV2_ColorModeChanged; + public event EventHandler PenPaletteV2_QuickActionsVisibilityChanged; + public event EventHandler PenPaletteV2_PaletteShouldCloseEvent; + + /// + /// 绑定选择弹窗V2的事件,部分事件会在内部进行处理后转发到外部 + /// + /// + /// + private void SelectionV2_Loaded(object sender, RoutedEventArgs e) { + SelectionV2.SelectionPopupShouldCloseEvent += (o, args) => { + SelectionPopupV2.IsOpen = false; + SelectionV2_SelectionPopupShouldCloseEvent?.Invoke(o, args); + }; + SelectionV2.SelectAllEvent += (o, args) => { + SelectionV2_SelectAllEvent?.Invoke(o, args); + }; + SelectionV2.UnSelectEvent += (o, args) => { + SelectionV2_UnSelectEvent?.Invoke(o, args); + }; + SelectionV2.ReverseSelectEvent += (o, args) => { + SelectionV2_ReverseSelectEvent?.Invoke(o, args); + }; + SelectionV2.ApplyScaleToStylusTipChanged += (o, args) => { + SelectionV2_ApplyScaleToStylusTipChanged?.Invoke(o, args); + }; + SelectionV2.OnlyHitTestFullyContainedStrokesChanged += (o, args) => { + SelectionV2_OnlyHitTestFullyContainedStrokesChanged?.Invoke(o, args); + }; + SelectionV2.AllowClickToSelectLockedStrokeChanged += (o, args) => { + SelectionV2_AllowClickToSelectLockedStrokeChanged?.Invoke(o, args); + }; + SelectionV2.SelectionModeChanged += (o, args) => { + SelectionV2_SelectionModeChanged?.Invoke(o, args); + }; + } + + public event EventHandler SelectionV2_SelectionPopupShouldCloseEvent; + public event EventHandler SelectionV2_SelectAllEvent; + public event EventHandler SelectionV2_UnSelectEvent; + public event EventHandler SelectionV2_ReverseSelectEvent; + public event EventHandler SelectionV2_ApplyScaleToStylusTipChanged; + public event EventHandler SelectionV2_OnlyHitTestFullyContainedStrokesChanged; + public event EventHandler SelectionV2_AllowClickToSelectLockedStrokeChanged; + public event EventHandler SelectionV2_SelectionModeChanged; + + /// + /// 绑定形状绘制弹窗V2的事件,部分事件会在内部进行处理后转发到外部 + /// + /// + /// + private void ShapeDrawingV2_Loaded(object sender, RoutedEventArgs e) { + ShapeDrawingV2.ShapeDrawingPopupShouldCloseEvent += (o, args) => { + ShapeDrawingPopupV2.IsOpen = false; + ShapeDrawingV2_ShapeDrawingPopupShouldCloseEvent?.Invoke(o, args); + }; + ShapeDrawingV2.ShapeSelectedEvent += (o, args) => { + ShapeDrawingV2_ShapeSelectedEvent?.Invoke(o, args); + }; + } + + public event EventHandler ShapeDrawingV2_ShapeDrawingPopupShouldCloseEvent; + public event EventHandler ShapeDrawingV2_ShapeSelectedEvent; + + #endregion + + private double ScalingFactor; + + public ObservableCollection ToolbarItems { get; set; } = + new ObservableCollection(); + + private Grid _mouseDownButton = null; + private bool _isMouseDownHeadIcon = false; + + private double CaculateToolBarWindowWidth(Collection toolbarItems) { + // ------------------------------------------------------// + var containerMargin = 4D; // 内容StackPanel的Margin + var itemWidth = 42D; // 按钮宽度 + var itemsSpacing = 2D; // 按钮之间的间距 + var headIconSpacingRight = 4D; // icc图标的右间距 + var separatorWidth = 1D; // 分隔符宽度 + var separatorSpacing = 3D; // 分隔符左右的间距 + // ------------------------------------------------------// + + var _width = containerMargin * 2 + itemWidth + headIconSpacingRight - itemsSpacing; + foreach (var i in toolbarItems) { + if (!i.IsVisible) continue; + if (i.Type != FloatingBarItemType.Separator) _width += itemsSpacing + itemWidth; + else _width += separatorSpacing + separatorWidth + separatorSpacing - itemsSpacing; + } + + return _width; + } + + /// + /// 修改工具栏的变体 + /// + /// 0为全长,1为鼠标模式下变体,2为迷你,3为仅HeadIcon图标 + private void UpdateToolBarVariant(int variant) { + var _snapTypeTemp = SnapType; + ToolBarNowVariantMode = variant; + HideAllPopups(); + IEnumerable items; + if (variant == 0) items = ToolbarItems.AsEnumerable(); + else if (variant == 1) + items = ToolbarItems.Where((item, i) => (new string[] { + "Cursor", "Pen", "Clear", "SeparatorC", "Whiteboard", "Gesture", "Menu", "Fold" + }).Contains(item.Name)); + else if (variant == 2) + items = ToolbarItems.Where((item, i) => (new string[] { + "Cursor", "Pen", "Clear" + }).Contains(item.Name)); + else if (variant == 3) + items = ToolbarItems.Where((item, i) => item.Selected); + else return; + + foreach (var fi in ToolbarItems) { + fi.IsVisible = items.Contains(fi); + } + + CollectionViewSource.GetDefaultView(ToolbarItems).Refresh(); + ReMeasureToolBar(); + if (_snapTypeTemp == ToolBarSnapType.RightSide) { + Left = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24; + } else if (_snapTypeTemp == ToolBarSnapType.RightTopCorner) { + Top = -24; + Left = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24; + } else if (_snapTypeTemp == ToolBarSnapType.RightBottomCorner) { + Top = System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 24; + Left = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24; + } + } + + private void UpdateToolBarSelectedTool(MainWindow.ICCToolsEnum type) { + var _i = ToolbarItems.Where((item, i) => + item.Type == FloatingBarItemType.StateButton && + item.ToolType == type).Single(); + var nowSelected = ToolbarItems.Where((item, i) => + item.Selected); + if (nowSelected.Any() && nowSelected.Count()==1 && _i == nowSelected.Single()) return; + + foreach (var fi in ToolbarItems) { + if (fi.Type == FloatingBarItemType.Separator) continue; + fi.Selected = false; + fi.IconSource = FindResource(fi.IconSourceResourceKey) as ImageSource; + } + + _i.Selected = true; + var clonedIcon = _i.IconSource.Clone() as DrawingImage; + foreach (var d in (clonedIcon.Drawing as DrawingGroup).Children) + ((GeometryDrawing)d).Brush = new SolidColorBrush(Colors.White); + _i.IconSource = clonedIcon; + + CollectionViewSource.GetDefaultView(ToolbarItems).Refresh(); + } + + private void ReMeasureToolBar() { + var barWidth = CaculateToolBarWindowWidth(ToolbarItems); + ToolBarV2Grid.Width = barWidth; + Width = barWidth + 24 * 2; // 6是工具栏和窗口的Margin + Height = 48 + 24 * 2; // 48是工具栏高度 + var offset = ToolBarNowVariantMode == 3 ? 0 : 6; + var offsetCp = ToolBarNowVariantMode == 3 ? 0.5 : 6.5; + var path = GenerateSuperEllipsePathByContentWidth(barWidth + offset, 48); + ToolBarBackgroundBorder.Geometry = Geometry.Parse(path); + var cg = GenerateSuperEllipsePathClipGeometry(barWidth + offsetCp, 48); + BackgroundBorderDrawingGroup.ClipGeometry = Geometry.Parse(cg); + + Top = Math.Max(Math.Min(Top, + System.Windows.SystemParameters.PrimaryScreenHeight-ActualHeight +24),-24); + Left = Math.Max(Math.Min(Left, + System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth +24),-24); + } + + public int ToolBarNowVariantMode = -1; + + private string GenerateSuperEllipsePathClipGeometry(double width, double renderingHeight) { + double acutalHeight = 119.33; + double renderingScalingFactor = acutalHeight / renderingHeight; + double actualFullWidth = width * renderingScalingFactor; + double actualCenterWidth = actualFullWidth - 119.34 < 0 ? 0 : actualFullWidth - 119.34; + + var cg = $"M0,0 V120 H{Math.Round(actualFullWidth, 0)} V0 H0 Z"; + return cg; + } + + /// + /// 根据给定的渲染高度计算比例,并根据实际内容宽度计算超椭圆圆角矩形的SVG路径 + /// + /// + /// + /// + private string GenerateSuperEllipsePathByContentWidth(double width, double renderingHeight) { + double acutalHeight = 119.33; + double renderingScalingFactor = acutalHeight / renderingHeight; + double actualFullWidth = width * renderingScalingFactor; + double actualCenterWidth = actualFullWidth - 119.34 < 0 ? 0 : actualFullWidth - 119.34; + + var sb = "M64.3285 " + + "0.015625" + + $"H{Math.Round(64.3395 + actualCenterWidth,3)}" + + $"C{Math.Round(90.3185 + actualCenterWidth,3)} " + + "0.015625 " + + $"{Math.Round(99.3081 + actualCenterWidth, 3)} " + + "0.015625 " + + $"{Math.Round(108.103 + actualCenterWidth, 3)} " + + "7.11388" + + $"C{Math.Round(109.871 + actualCenterWidth, 3)} " + + "8.54031 " + + $"{Math.Round(111.481 + actualCenterWidth, 3)} " + + "10.1509 " + + $"{Math.Round(112.908 + actualCenterWidth, 3)} " + + "11.9183" + + $"C{Math.Round(120.006 + actualCenterWidth, 3)} " + + "20.7134 " + + $"{Math.Round(120.006 + actualCenterWidth, 3)} " + + "33.7029 " + + $"{Math.Round(120.006 + actualCenterWidth, 3)} " + + "59.6819" + + $"C{Math.Round(120.006 + actualCenterWidth, 3)} " + + "85.661 " + + $"{Math.Round(120.006 + actualCenterWidth, 3)} " + + "98.6505 " + + $"{Math.Round(112.908 + actualCenterWidth, 3)} " + + "107.446" + + $"C{Math.Round(111.481 + actualCenterWidth, 3)} " + + "109.213 " + + $"{Math.Round(109.871 + actualCenterWidth, 3)} " + + "110.824 " + + $"{Math.Round(108.103 + actualCenterWidth, 3)} " + + "112.25" + + $"C{Math.Round(99.3081 + actualCenterWidth, 3)} " + + "119.348 " + + $"{Math.Round(90.3185 + actualCenterWidth, 3)} " + + "119.348 " + + $"{Math.Round(64.3395 + actualCenterWidth, 3)} " + + "119.348" + + "H64.3285C38.3494 " + + "119.348 " + + "21.3599 " + + "119.348 " + + "12.5648 " + + "112.25" + + "C10.7973 " + + "110.824 " + + "9.1868 " + + "109.213 " + + "7.76037 " + + "107.446" + + "C0.662109 " + + "98.6505 " + + "0.662109 " + + "85.661 " + + "0.662109 " + + "59.6819" + + "C0.662109 " + + "33.7029 " + + "0.662109 " + + "20.7134 " + + "7.76037 " + + "11.9183" + + "C9.1868 " + + "10.1509 " + + "10.7973 " + + "8.54031 " + + "12.5648 " + + "7.11388" + + "C21.3599 " + + "0.015625 " + + "38.3494 " + + "0.015625 " + + "64.3285 " + + "0.015625" + + "Z"; + + return sb; + } + + public enum ToolBarSnapType { + NoSnap, + LeftTopCorner, + RightTopCorner, + LeftBottomCorner, + RightBottomCorner, + RightSide, + LeftSide, + BottomSide, + TopSide + } + + public ToolBarSnapType SnapType { + get { + if (Top <= -24 && Left <= -24) return ToolBarSnapType.LeftTopCorner; + if (Top >= System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 24 && Left <= -24) return ToolBarSnapType.LeftBottomCorner; + if (Top <= -24 && Left >= System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24) return ToolBarSnapType.RightTopCorner; + if (Top >= System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 24 && + Left >= System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24) return ToolBarSnapType.RightBottomCorner; + if (Top <= -24) return ToolBarSnapType.TopSide; + if (Top >= System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 24) + return ToolBarSnapType.BottomSide; + if (Left <= -24) return ToolBarSnapType.LeftSide; + if (Left >= System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24) + return ToolBarSnapType.RightSide; + return ToolBarSnapType.NoSnap; + } + } + + public event EventHandler FloatingBarToolSelectionChanged; + public event EventHandler FloatingBarToolButtonClicked; + + private void OnToolSelectionChanged(FloatingBarItem sender) { + if (ToolBarNowVariantMode == 3) return; + Dispatcher.InvokeAsync(() => HideAllPopups()); + if (sender.Selected && sender.ToolType != MainWindow.ICCToolsEnum.CursorMode) { + if (ToolBarNowVariantMode != 0) UpdateToolBarVariant(0); + } else { + if (ToolBarNowVariantMode != 1) UpdateToolBarVariant(1); + } + FloatingBarToolSelectionChanged?.Invoke(sender,EventArgs.Empty); + } + + private void OnToolButtonClicked(FloatingBarItem sender, Grid container) { + if (sender.ToolType == MainWindow.ICCToolsEnum.PenMode) { + if (PenPaletteV2Popup.IsOpen) { + HideAllPopups(); + } else { + var containerPoint = container.TranslatePoint(new Point(0, 0), ToolBarV2Grid); + PenPaletteV2Popup.HorizontalOffset = containerPoint.X; + if (System.Windows.SystemParameters.PrimaryScreenHeight - Top - Height + 24 > PenPaletteV2.Height) PenPaletteV2Popup.VerticalOffset = Height -24 ; + else PenPaletteV2Popup.VerticalOffset = 0 - PenPaletteV2.Height + 24; + PenPaletteV2Popup.IsOpen = true; + } + } else if (sender.ToolType == MainWindow.ICCToolsEnum.LassoMode) { + if (SelectionPopupV2.IsOpen) { + HideAllPopups(); + } else { + var containerPoint = container.TranslatePoint(new Point(0, 0), ToolBarV2Grid); + SelectionPopupV2.HorizontalOffset = containerPoint.X; + if (System.Windows.SystemParameters.PrimaryScreenHeight - Top - Height + 24 > SelectionV2.Height) SelectionPopupV2.VerticalOffset = Height - 24 ; + else SelectionPopupV2.VerticalOffset = 0 - SelectionV2.Height + 24; + SelectionPopupV2.IsOpen = true; + } + } else if (sender.Name == "ShapeDrawing") { + if (ShapeDrawingPopupV2.IsOpen) { + HideAllPopups(); + } else { + var containerPoint = container.TranslatePoint(new Point(0, 0), ToolBarV2Grid); + ShapeDrawingPopupV2.HorizontalOffset = containerPoint.X; + if (System.Windows.SystemParameters.PrimaryScreenHeight - Top - Height + 24 > ShapeDrawingV2.Height) ShapeDrawingPopupV2.VerticalOffset = Height - 24 ; + else ShapeDrawingPopupV2.VerticalOffset = 0 - ShapeDrawingV2.Height + 24; + ShapeDrawingPopupV2.IsOpen = true; + } + } + FloatingBarToolButtonClicked?.Invoke(sender,EventArgs.Empty); + } + + private void ToolbarButton_MouseDown(object sender, MouseButtonEventArgs e) { + if (_mouseDownButton != null) return; + var gd = sender as Grid; + var itemData = gd.Tag as FloatingBarItem; + _mouseDownButton = gd; + if (!itemData.Selected) { + var bgImg = gd.Children[1] as Image; + bgImg.Opacity = 1; + } + gd.RenderTransform = new ScaleTransform(0.9, 0.9); + } + + private void ToolbarButton_MouseUp(object sender, MouseButtonEventArgs e) { + if (_mouseDownButton == null || _mouseDownButton != sender) return; + ToolbarButton_MouseLeave(sender, null); + + var gd = sender as Grid; + var itemData = gd.Tag as FloatingBarItem; + if (itemData.Type == FloatingBarItemType.StateButton && !itemData.Selected) { + UpdateToolBarSelectedTool(itemData.ToolType); + OnToolSelectionChanged(itemData); + } else { + OnToolButtonClicked(itemData, gd); + } + } + + public int DynamicTransparentVariant { get; set; } = 1; + + /// + /// 根据提供的变体ID修改工具栏动态透明的变体类型 + /// + /// 0为透明,1为不透明 + private void UpdateToolBarDynamicOpacityVariant(int variant) { + DynamicTransparentVariant = variant; + if (variant == 0) { + FallbackBackgroundLayer.Opacity = 0.05; + ToolBarBackgroundBorder.Pen = new Pen() { + Brush = new SolidColorBrush(Color.FromArgb(48, 34, 34, 34)), + Thickness = 3, + }; + ToolBarBackgroundBorder.Brush = new SolidColorBrush(Color.FromArgb(16, 255,255,255)); + HeadIcon.Opacity = 0.5; + ToolBarItemsControl.Opacity = 0.92; + foreach (var fi in ToolbarItems) { + fi.IsSemiTransparent = true; + if (fi.Type == FloatingBarItemType.Separator) continue; + if (fi.IconSourceResourceKey == "") continue; + var icon = FindResource(fi.IconSourceResourceKey) as DrawingImage; + foreach (var gd in (icon.Drawing as DrawingGroup).Children) { + var _gd = gd as GeometryDrawing; + _gd.Pen = new Pen() { + Brush = new SolidColorBrush(Color.FromArgb(148, 34,34,34)), + Thickness = 1, + }; + if (fi.IconColor != null) + _gd.Brush = new SolidColorBrush(Color.FromArgb(96, (byte)Math.Min(((Color)fi.IconColor).R * 4.25,255), + (byte)Math.Min(((Color)fi.IconColor).G * 4.25,255),(byte)Math.Min(((Color)fi.IconColor).B * 4.25,255))); + else _gd.Brush = new SolidColorBrush(Color.FromArgb(96, 255,255,255)); + } + + if (fi.Selected) { + var clonedIcon = icon.Clone(); + foreach (var d in (clonedIcon.Drawing as DrawingGroup).Children) + ((GeometryDrawing)d).Brush = new SolidColorBrush(Colors.White); + fi.IconSource = clonedIcon; + } + } + } else if (variant == 1) { + FallbackBackgroundLayer.Opacity = 1; + ToolBarBackgroundBorder.Pen = new Pen() { + Brush = new SolidColorBrush(Color.FromRgb(212,212,216)), + Thickness = 3, + }; + ToolBarBackgroundBorder.Brush = new SolidColorBrush(Color.FromRgb(250, 250, 250)); + HeadIcon.Opacity = 1; + ToolBarItemsControl.Opacity = 1; + foreach (var fi in ToolbarItems) { + fi.IsSemiTransparent = false; + if (fi.Type == FloatingBarItemType.Separator) continue; + if (fi.IconSourceResourceKey == "") continue; + var icon = FindResource(fi.IconSourceResourceKey) as DrawingImage; + foreach (var gd in (icon.Drawing as DrawingGroup).Children) { + var _gd = gd as GeometryDrawing; + _gd.Pen = null; + _gd.Brush = new SolidColorBrush(fi.IconColor??Color.FromRgb(34,34,34)); + } + + if (fi.Selected) { + var clonedIcon = icon.Clone(); + foreach (var d in (clonedIcon.Drawing as DrawingGroup).Children) + ((GeometryDrawing)d).Brush = new SolidColorBrush(Colors.White); + fi.IconSource = clonedIcon; + } + } + } + + CollectionViewSource.GetDefaultView(ToolbarItems).Refresh(); + ReMeasureToolBar(); + } + + [DllImport("USER32.DLL", SetLastError = true)] + public static extern int GetSystemMetrics(int nIndex); + + private void ToolbarButton_MouseLeave(object sender, MouseEventArgs e) { + if (_mouseDownButton == null || _mouseDownButton != sender) return; + var itemData = _mouseDownButton.Tag as FloatingBarItem; + if (!itemData.Selected) { + var bgImg = _mouseDownButton.Children[1] as Image; + bgImg.Opacity = 0; + } + _mouseDownButton.RenderTransform = null; + _mouseDownButton = null; + } + + #region icc按钮 + + public Point prevPoint = new Point(0,0); + public bool isInMovingMode = false; + public double winLeft = 0; + public double winTop = 0; + public ToolBarSnapType _snapTypeTemp = ToolBarSnapType.NoSnap; + + private void HeadIconButton_MouseDown(object sender, MouseButtonEventArgs e) { + if (_isMouseDownHeadIcon) return; + if (e.RightButton == MouseButtonState.Pressed) { + UpdateToolBarDynamicOpacityVariant(Math.Abs(DynamicTransparentVariant-1)); + } else { + _isMouseDownHeadIcon = true; + var gd = sender as Grid; + prevPoint = e.GetPosition(ToolBarV2Grid); + winLeft = Left; + winTop = Top; + Trace.WriteLine(prevPoint); + gd.CaptureMouse(); + gd.RenderTransform = new ScaleTransform(0.92, 0.92); + isInMovingMode = false; + _snapTypeTemp = SnapType; + } + + } + + private void HeadIconButton_MouseMove(object sender, MouseEventArgs e) { + if (_isMouseDownHeadIcon == false) return; + var mp = System.Windows.Forms.Control.MousePosition; + var mpLogical = new Point(mp.X * ScalingFactor, mp.Y * ScalingFactor); + var deltaX = mpLogical.X - prevPoint.X - winLeft - 24; + var deltaY = mpLogical.Y - prevPoint.Y - winTop - 24; + var movingLimitation = _snapTypeTemp == ToolBarSnapType.NoSnap ? 12 : 24; + if (Math.Abs(deltaY) > movingLimitation || Math.Abs(deltaX) > movingLimitation) isInMovingMode = true; + if (isInMovingMode) { + HideAllPopups(); + ToolbarV2.RenderTransform = null; + ToolBarV2Grid.RenderTransform = null; + HeadIconImage.RenderTransform = null; + UpdateToolbarPlacementFeedback(); + Top = Math.Max(Math.Min(mp.Y * ScalingFactor - prevPoint.Y - 24, + System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight +24),-24); + Left = Math.Max(Math.Min(mp.X * ScalingFactor - prevPoint.X - 24, + System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth +24),-24); + } else { + double tbMovingX = deltaX/3, tbMovingY = deltaY/3; + tbMovingX = (new int[] { 1, 3, 6 }).Contains((int)_snapTypeTemp)?Math.Max(deltaX / 3, 0):tbMovingX; + tbMovingX = (new int[] { 2, 4, 5 }).Contains((int)_snapTypeTemp)?Math.Min(deltaX / 3, 0):tbMovingX; + tbMovingY = (new int[] { 1, 2, 8 }).Contains((int)_snapTypeTemp)?Math.Max(deltaY / 3, 0):tbMovingY; + tbMovingY = (new int[] { 3, 4, 7 }).Contains((int)_snapTypeTemp)?Math.Min(deltaY / 3, 0):tbMovingY; + ToolBarV2Grid.RenderTransformOrigin = new Point( + (new int[] { 1, 3, 6 }).Contains((int)_snapTypeTemp) ? 0 : + (new int[] { 7, 8 }).Contains((int)_snapTypeTemp) ? 0.5 : 1, + (new int[] { 1, 2, 8 }).Contains((int)_snapTypeTemp) ? 0 : + (new int[] { 5, 6 }).Contains((int)_snapTypeTemp) ? 0.5 : 1 + ); + ToolBarV2Grid.RenderTransform = new ScaleTransform( + 1 + (tbMovingX /350)*((new int[] { 1, 3, 6 }).Contains((int)_snapTypeTemp)?1:(new int[] { 2, 4, 5 }).Contains((int)_snapTypeTemp)?-1:0), + 1 + (tbMovingY /120)*((new int[] { 1, 2, 8 }).Contains((int)_snapTypeTemp)?1:(new int[] { 3, 4, 7 }).Contains((int)_snapTypeTemp)?-1:0)); + ToolbarV2.RenderTransform = new TranslateTransform(tbMovingX, tbMovingY); + HeadIconImage.RenderTransform = new TranslateTransform(tbMovingX / 2, tbMovingY / 2); + } + } + + #endregion + + private CornerRadius _ltCornerRadius = new CornerRadius(0, 0, 4, 0); + private CornerRadius _lbCornerRadius = new CornerRadius(0, 4, 0, 0); + private CornerRadius _rtCornerRadius = new CornerRadius(0, 0, 0, 4); + private CornerRadius _rbCornerRadius = new CornerRadius(4, 0, 0, 0); + private CornerRadius _tsCornerRadius = new CornerRadius(0, 0, 4, 4); + private CornerRadius _bsCornerRadius = new CornerRadius(4, 4, 0, 0); + + private void UpdateToolbarPlacementFeedback() { + var snapT = SnapType; + var xSideCenter = (System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth) / 2; + var xDeltaCenter = xSideCenter - Left; + PlaceFeedbackBorder.Width = ActualWidth - 16; + PlaceFeedbackBorder.Height = ActualHeight - 16; + PlacementFeedbackPopup.IsOpen = true; + if (Math.Abs(24 + Top) <= 24 && Math.Abs(24 + Left) <= 24) { + PlacementFeedbackPopup.VerticalOffset = 0; + PlacementFeedbackPopup.HorizontalOffset = 0; + PlaceFeedbackBorder.CornerRadius = _ltCornerRadius; + PlacementFeedbackTipText.VerticalAlignment = VerticalAlignment.Bottom; + PlacementFeedbackTipText.HorizontalAlignment = HorizontalAlignment.Left; + PlacementFeedbackTipText.Text = "左上角"; + } else if (Math.Abs(24 + Left) <= 24 && 24 - (Top + ActualHeight - System.Windows.SystemParameters.PrimaryScreenHeight) <= 24) { + PlacementFeedbackPopup.VerticalOffset = System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 16; + PlacementFeedbackPopup.HorizontalOffset = 0; + PlaceFeedbackBorder.CornerRadius = _lbCornerRadius; + PlacementFeedbackTipText.VerticalAlignment = VerticalAlignment.Top; + PlacementFeedbackTipText.HorizontalAlignment = HorizontalAlignment.Left; + PlacementFeedbackTipText.Text = "左下角"; + } else if (24 - (Left + ActualWidth - System.Windows.SystemParameters.PrimaryScreenWidth) <= 24 && Math.Abs(24 + Top) <= 24) { + PlacementFeedbackPopup.VerticalOffset = 0; + PlacementFeedbackPopup.HorizontalOffset = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 16; + PlaceFeedbackBorder.CornerRadius = _rtCornerRadius; + PlacementFeedbackTipText.VerticalAlignment = VerticalAlignment.Bottom; + PlacementFeedbackTipText.HorizontalAlignment = HorizontalAlignment.Right; + PlacementFeedbackTipText.Text = "右上角"; + } else if (24 - (Left + ActualWidth - System.Windows.SystemParameters.PrimaryScreenWidth) <= 24 && + 24 - (Top + ActualHeight - System.Windows.SystemParameters.PrimaryScreenHeight) <= 24) { + PlacementFeedbackPopup.VerticalOffset = System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 16; + PlacementFeedbackPopup.HorizontalOffset = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 16; + PlaceFeedbackBorder.CornerRadius = _rbCornerRadius; + PlacementFeedbackTipText.VerticalAlignment = VerticalAlignment.Top; + PlacementFeedbackTipText.HorizontalAlignment = HorizontalAlignment.Right; + PlacementFeedbackTipText.Text = "右下角"; + } else if (snapT == ToolBarSnapType.TopSide && Math.Abs(xDeltaCenter)<=50) { + PlacementFeedbackPopup.VerticalOffset = 0; + PlacementFeedbackPopup.HorizontalOffset = + (System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 16) / 2; + PlaceFeedbackBorder.CornerRadius = _tsCornerRadius; + PlacementFeedbackTipText.VerticalAlignment = VerticalAlignment.Bottom; + PlacementFeedbackTipText.HorizontalAlignment = HorizontalAlignment.Center; + PlacementFeedbackTipText.Text = "顶部居中"; + } else if (snapT == ToolBarSnapType.BottomSide && Math.Abs(xDeltaCenter)<=90) { + PlacementFeedbackPopup.VerticalOffset = System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 16; + PlacementFeedbackPopup.HorizontalOffset = + (System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 16) / 2; + PlaceFeedbackBorder.CornerRadius = _bsCornerRadius; + PlacementFeedbackTipText.VerticalAlignment = VerticalAlignment.Top; + PlacementFeedbackTipText.HorizontalAlignment = HorizontalAlignment.Center; + PlacementFeedbackTipText.Text = "底部居中"; + } else { + PlacementFeedbackPopup.IsOpen = false; + } + } + + public FloatingBarItem SelectedItem { + get => ToolbarItems.Single(item => item.Selected); + } + + private void UseNearSnap() { + if (Math.Abs(24 + Top) <= 24 && Math.Abs(24 + Left) <= 24) { + Top = -24; + Left = -24; + } else if ( + 24 - (Left + ActualWidth - System.Windows.SystemParameters.PrimaryScreenWidth) <= 24 && + Math.Abs(24 + Top) <= 24 + ) { + Top = -24; + Left = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24; + } else if ( + Math.Abs(24 + Left) <= 24 && + 24 - (Top + ActualHeight - System.Windows.SystemParameters.PrimaryScreenHeight) <= 24 + ) { + Top = System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 24; + Left = -24; + } else if ( + 24 - (Left + ActualWidth - System.Windows.SystemParameters.PrimaryScreenWidth) <= 24 && + 24 - (Top + ActualHeight - System.Windows.SystemParameters.PrimaryScreenHeight) <= 24 + ) { + Top = System.Windows.SystemParameters.PrimaryScreenHeight - ActualHeight + 24; + Left = System.Windows.SystemParameters.PrimaryScreenWidth - ActualWidth + 24; + } + } + + private void HeadIconButton_MouseUp(object sender, MouseButtonEventArgs e) { + if (_isMouseDownHeadIcon == false) return; + _isMouseDownHeadIcon = false; + var gd = sender as Grid; + gd.ReleaseMouseCapture(); + gd.RenderTransform = null; + ToolbarV2.RenderTransform = null; + ToolBarV2Grid.RenderTransform = null; + HeadIconImage.RenderTransform = null; + PlacementFeedbackPopup.IsOpen = false; + UseNearSnap(); + if (!isInMovingMode) { + var mp = System.Windows.Forms.Control.MousePosition; + var mpLogical = new Point(mp.X * ScalingFactor, mp.Y * ScalingFactor); + if (Math.Abs(mpLogical.X - prevPoint.X - winLeft - 24) < 2.5 || Math.Abs(mpLogical.Y - prevPoint.Y - winTop - 24) < 2.5) { + if (ToolBarNowVariantMode == 3) { + if (SelectedItem.ToolType != MainWindow.ICCToolsEnum.CursorMode) + UpdateToolBarVariant(0); + else UpdateToolBarVariant(1); + } else { + UpdateToolBarVariant(3); + } + } + } + isInMovingMode = false; + } + } +} diff --git a/InkCanvasForClass/Popups/ScreenshotWindow.xaml b/InkCanvasForClass/Popups/ScreenshotWindow.xaml new file mode 100644 index 00000000..74bde1c5 --- /dev/null +++ b/InkCanvasForClass/Popups/ScreenshotWindow.xaml @@ -0,0 +1,281 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 无法截取 + ICC的权限不足 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs b/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs new file mode 100644 index 00000000..72af71c3 --- /dev/null +++ b/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs @@ -0,0 +1,400 @@ +using Ink_Canvas.Helpers; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using System.Windows.Shell; +using System.Xml.Linq; +using OSVersionExtension; +using Vanara.PInvoke; +using Color = System.Windows.Media.Color; +using Shell32; +using static Ink_Canvas.MainWindow; +using OperatingSystem = OSVersionExtension.OperatingSystem; + +namespace Ink_Canvas.Popups +{ + /// + /// ScreenshotWindow.xaml 的交互逻辑 + /// + public partial class ScreenshotWindow : Window { + + private MainWindow mainWindow; + private Settings settings; + + public ScreenshotWindow(MainWindow mainWin, Settings s) { + InitializeComponent(); + + mainWindow = mainWin; + settings = s; + + iconList = new Border[] { + FullScreenIcon, + WindowIcon, + SelectionIcon, + DesktopIcon + }; + + WindowIcon.IsHitTestVisible = OSVersion.GetOperatingSystem() >= OperatingSystem.Windows10; + WindowIcon.Opacity = OSVersion.GetOperatingSystem() >= OperatingSystem.Windows10 ? 1 : 0.5; + + + foreach (var b in iconList) { + b.MouseLeave += IconMouseLeave; + b.MouseUp += IconMouseUp; + b.MouseDown += IconMouseDown; + b.Background = new SolidColorBrush(Colors.Transparent); + } + + ReArrangeWindowPosition(); + mainWin.Hide(); + + if (DwmCompositionHelper.DwmIsCompositionEnabled()) { + AllowsTransparency = false; + Background = new SolidColorBrush(Colors.Transparent); + WindowChrome.SetWindowChrome(this, new WindowChrome() { + GlassFrameThickness = new Thickness(-1), + CaptionHeight = 0, + CornerRadius = new CornerRadius(0), + ResizeBorderThickness = new Thickness(0), + }); + } else { + AllowsTransparency = true; + Background = new SolidColorBrush(Color.FromArgb(1,0,0,0)); + } + + ToggleSwitchCopyToClipBoard.IsChecked = settings.Snapshot.CopyScreenshotToClipboard; + ToggleSwitchAttachInk.IsChecked = settings.Snapshot.AttachInkWhenScreenshot; + ToggleSwitchCopyToClipBoard.Checked += ToggleSwitchCopyToClipBoard_CheckChanged; + ToggleSwitchCopyToClipBoard.Unchecked += ToggleSwitchCopyToClipBoard_CheckChanged; + ToggleSwitchAttachInk.Checked += ToggleSwitchAttachInk_CheckChanged; + ToggleSwitchAttachInk.Unchecked += ToggleSwitchAttachInk_CheckChanged; + + WindowsItemsControl.ItemsSource = _winInfos; + WindowScreenshotOverlay.Visibility = Visibility.Collapsed; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Collapsed; + + EscBorder.MouseDown += EscBorder_MouseDown; + EscBorder.MouseUp += EscBorder_MouseUp; + EscBorder.MouseLeave += EscBorder_MouseLeave; + + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + NeedAdminTextPanel.Visibility = principal.IsInRole(WindowsBuiltInRole.Administrator) ? Visibility.Collapsed : Visibility.Visible; + if (principal.IsInRole(WindowsBuiltInRole.Administrator)) { + WindowsItemsScrollViewer.Margin = new Thickness(36, 148, 36, 0); + } else { + WindowsItemsScrollViewer.Margin = new Thickness(36, 178, 36, 0); + } + } + + private bool isEscBorderDown = false; + + private void EscBorder_MouseLeave(object sender, MouseEventArgs e) { + if (!isEscBorderDown) return; + isEscBorderDown = false; + EscBorder.Background = new SolidColorBrush(Colors.Transparent); + } + + private void EscBorder_MouseUp(object sender, MouseButtonEventArgs e) { + if (!isEscBorderDown) return; + if (isWindowsSnapshotLoaded == false) return; + EscBorder_MouseLeave(null, null); + ScreenshotPanel.Visibility = Visibility.Visible; + WindowScreenshotOverlay.Visibility = Visibility.Collapsed; + _winInfos.Clear(); + isWindowsSnapshotLoaded = null; + } + + private void EscBorder_MouseDown(object sender, MouseButtonEventArgs e) { + if (isEscBorderDown) return; + isEscBorderDown = true; + EscBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); + } + + private ObservableCollection _winInfos = new ObservableCollection(); + + private bool AllOneColor(Bitmap bmp) + { + // Lock the bitmap's bits. + System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height); + BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat); + + // Get the address of the first line. + IntPtr ptr = bmpData.Scan0; + + // Declare an array to hold the bytes of the bitmap. + int bytes = bmpData.Stride * bmp.Height; + byte[] rgbValues = new byte[bytes]; + + // Copy the RGB values into the array. + System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); + + bool AllOneColor = true; + for (int index = 0; index < rgbValues.Length; index++) { + //compare the current A or R or G or B with the A or R or G or B at position 0,0. + if (rgbValues[index] != rgbValues[index % 4]) { + AllOneColor= false; + break; + } + } + // Unlock the bits. + bmp.UnlockBits(bmpData); + return AllOneColor; + } + + private async Task AllOneColorAsync(Bitmap bmp) { + var result = await Task.Run(() => AllOneColor(bmp)); + return result; + } + + public BitmapImage BitmapToImageSource(Bitmap bitmap) + { + using (MemoryStream memory = new MemoryStream()) + { + bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp); + memory.Position = 0; + BitmapImage bitmapimage = new BitmapImage(); + bitmapimage.BeginInit(); + bitmapimage.StreamSource = memory; + bitmapimage.CacheOption = BitmapCacheOption.OnLoad; + bitmapimage.EndInit(); + + return bitmapimage; + } + } + + private static ImageSource IconToImageSource(Icon icon) + { + ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + return imageSource; + } + + private class WinInfo { + public string Title { get; set; } + public BitmapImage Snapshot { get; set; } + public ImageSource Icon { get; set; } + public HWND Handle { get; set; } + public Bitmap OriginBitmap { get; set; } + public double Width { get; set; } + public double TextBlockWidth { get; set; } + public bool IsAllOneColor { get; set; } + public bool IsDisplayFailedBorder { get; set; } + public Visibility ShouldDisplayFailedBorder { + get => IsDisplayFailedBorder ? Visibility.Visible : Visibility.Collapsed; + } + public bool IsHidden { get; set; } + public Visibility Visibility { + get => IsHidden ? Visibility.Collapsed : Visibility.Visible; + } + } + + private Border lastDownIcon; + + private void ToggleSwitchCopyToClipBoard_CheckChanged(object sender, RoutedEventArgs e) { + if (!mainWindow.isLoaded) return; + mainWindow.ToggleSwitchCopyScreenshotToClipboard.IsOn = ToggleSwitchCopyToClipBoard.IsChecked ?? true; + } + + private void ToggleSwitchAttachInk_CheckChanged(object sender, RoutedEventArgs e) { + if (!mainWindow.isLoaded) return; + mainWindow.ToggleSwitchAttachInkWhenScreenshot.IsOn = ToggleSwitchAttachInk.IsChecked ?? true; + } + + private void ReArrangeWindowPosition() { + var workAreaWidth = SystemParameters.WorkArea.Width; + var workAreaHeight = SystemParameters.WorkArea.Height; + var toolbarHeight = SystemParameters.PrimaryScreenHeight - SystemParameters.FullPrimaryScreenHeight - + SystemParameters.WindowCaptionHeight; + Left = (workAreaWidth - Width) / 2; + Top = workAreaHeight - Height - toolbarHeight - 64; + } + + private bool isCaptureButtonDown = false; + + private async void CaptureFullScreen() { + LoadingOverlay.Visibility = Visibility.Visible; + MainFuncPanel.Effect = new BlurEffect() { + KernelType = KernelType.Gaussian, + Radius = 24, + RenderingBias = RenderingBias.Performance, + }; + try { + var bm = await mainWindow.FullscreenSnapshot(new MainWindow.SnapshotConfig() { + BitmapSavePath = + new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)), + ExcludedHwnds = new HWND[] { + new HWND(new WindowInteropHelper(this).Handle) + }, + IsCopyToClipboard = settings.Snapshot.CopyScreenshotToClipboard, + IsSaveToLocal = true, + OutputMIMEType = MainWindow.OutputImageMIMEFormat.Png, + }); + bm.Dispose(); + LoadingOverlay.Visibility = Visibility.Collapsed; + MainFuncPanel.Effect = null; + mainWindow.ShowNewToast("已保存截图到桌面!", MW_Toast.ToastType.Success, 3000); + await Task.Delay(1); + Close(); + } + catch (Exception e) { + LoadingOverlay.Visibility = Visibility.Collapsed; + MainFuncPanel.Effect = null; + mainWindow.ShowNewToast($"截图失败!{e.Message}", MW_Toast.ToastType.Error, 3000); + await Task.Delay(1); + Close(); + } + } + + private void IconMouseLeave(object sender, MouseEventArgs e) { + if (lastDownIcon == null) return; + lastDownIcon = null; + var b = (Border)sender; + b.Background = new SolidColorBrush(Colors.Transparent); + } + + private void IconMouseDown(object sender, MouseButtonEventArgs e) { + if (lastDownIcon != null) return; + lastDownIcon = (Border)sender; + var b = (Border)sender; + b.Background = new SolidColorBrush(Color.FromArgb(22, 24, 24, 27)); + } + + private bool? isWindowsSnapshotLoaded = false; + + + private async void IconMouseUp(object sender, MouseButtonEventArgs e) { + if (lastDownIcon == null) return; + IconMouseLeave(sender, null); + + + /*if (selectedMode == 1) { + try { + MainWindow.WindowInformation[] windows = await mainWindow.GetAllWindowsAsync(new HWND[] { + new HWND(new WindowInteropHelper(this).Handle), new HWND(new WindowInteropHelper(mainWindow).Handle) + }); + _screenshotGridWindow = new WindowScreenshotGridWindow(windows, mainWindow); + _screenshotGridWindow.Show(); + } + catch (TaskCanceledException) {} + catch (Exception ex) {} + } else { + try { + _screenshotGridWindow.Close(); + } catch (Exception ex) { } + + }*/ + + if (Array.IndexOf(iconList, (Border)sender) == 0) { + CaptureFullScreen(); + } else if (Array.IndexOf(iconList, (Border)sender) == 3) { + Shell shellObject = new Shell(); + shellObject.ToggleDesktop(); + } else if (Array.IndexOf(iconList, ((Border)sender)) == 1) { + isWindowsSnapshotLoaded = false; + await Dispatcher.InvokeAsync(() => { + _winInfos.Clear(); + ScreenshotPanel.Visibility = Visibility.Collapsed; + WindowScreenshotOverlay.Visibility = Visibility.Visible; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Visible; + WindowScreenshotWindowsGrid.Effect = new BlurEffect() { + KernelType = KernelType.Gaussian, + RenderingBias = RenderingBias.Performance, + Radius = 32, + }; + }); + var wins = await mainWindow.GetAllWindowsAsync(new HWND[] { + new HWND(new WindowInteropHelper(this).Handle) + }); + foreach (var windowInformation in wins) { + var bitmapHeight = windowInformation.WindowBitmap.Height; + var w = windowInformation.WindowBitmap.Width * (226D / bitmapHeight); + var allonecolor = await AllOneColorAsync(windowInformation.WindowBitmap); + _winInfos.Add(new WinInfo() { + Title = windowInformation.Title, + Snapshot = BitmapToImageSource(windowInformation.WindowBitmap.Clone(windowInformation.ContentRect,windowInformation.WindowBitmap.PixelFormat)), + Handle = windowInformation.hwnd, + OriginBitmap = windowInformation.WindowBitmap, + Icon = windowInformation.AppIcon == null ? new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/program-icon.png")) : IconToImageSource(windowInformation.AppIcon), + Width = w, + TextBlockWidth = w - 48 - 8, + IsAllOneColor = allonecolor, + IsDisplayFailedBorder = allonecolor, + IsHidden = settings.Snapshot.OnlySnapshotMaximizeWindow, + }); + if (Array.IndexOf(wins, windowInformation)>= wins.Length - 1) Dispatcher.InvokeAsync(() => { + WindowScreenshotWindowsGrid.Effect = null; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Collapsed;}); + } + Dispatcher.InvokeAsync(() => { + WindowScreenshotWindowsGrid.Effect = null; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Collapsed;}); + isWindowsSnapshotLoaded = true; + } + } + + private Border[] iconList = new Border[] { }; + + private void CloseButton_CloseWindow(object sender, MouseButtonEventArgs e) { + Close(); + } + + protected override void OnClosed(EventArgs e) { + mainWindow.Show(); + base.OnClosed(e); + } + + private void ScreenshotWindow_OnKeyDown(object sender, KeyEventArgs e) { + if (e.Key == Key.Escape) { + if (isWindowsSnapshotLoaded==false) return; + ScreenshotPanel.Visibility = Visibility.Visible; + WindowScreenshotOverlay.Visibility = Visibility.Collapsed; + _winInfos.Clear(); + isWindowsSnapshotLoaded = null; + } + } + + public void WindowsItemsScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { + var scrollViewer = (ScrollViewer)sender; + var sb = new Storyboard(); + var ofs = scrollViewer.VerticalOffset; + var animation = new DoubleAnimation + { + From = ofs, + To = ofs - e.Delta * 2.5, + Duration = TimeSpan.FromMilliseconds(155) + }; + animation.EasingFunction = new CubicEase() { + EasingMode = EasingMode.EaseOut, + }; + Storyboard.SetTargetProperty(animation, new PropertyPath(ColorPalette.ScrollViewerBehavior.VerticalOffsetProperty)); + Storyboard.SetTargetName(animation,"WindowsItemsScrollViewer"); + sb.Children.Add(animation); + scrollViewer.ScrollToVerticalOffset(ofs); + sb.Begin(scrollViewer); + } + } +} diff --git a/InkCanvasForClass/Popups/SelectionPopup.xaml b/InkCanvasForClass/Popups/SelectionPopup.xaml new file mode 100644 index 00000000..c8695cc1 --- /dev/null +++ b/InkCanvasForClass/Popups/SelectionPopup.xaml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Popups/SelectionPopup.xaml.cs b/InkCanvasForClass/Popups/SelectionPopup.xaml.cs new file mode 100644 index 00000000..94e6ce9c --- /dev/null +++ b/InkCanvasForClass/Popups/SelectionPopup.xaml.cs @@ -0,0 +1,179 @@ +using iNKORE.UI.WPF.Modern.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using static Ink_Canvas.Popups.ColorPalette; + +namespace Ink_Canvas.Popups { + public partial class SelectionPopup : UserControl { + public SelectionPopup() { + InitializeComponent(); + SelectionModeTabButtonBorders = new Border[] { + LassoTabButton, RectangleTabButton + }; + SelectionModeTabButtonIndicators = new SimpleStackPanel[] { + LassoTabButtonIndicator, RectangleTabButtonIndicator + }; + SelectionModeTabButtonIcons = new GeometryDrawing[] { + LassoTabButtonIcon, RectangleTabButtonIcon + }; + SelectionModeTabButtonTexts = new TextBlock[] { + LassoTabButtonText, RectangleTabButtonText + }; + UpdateSelectionModeButtonsCheckedDisplayStatus(); + } + + private bool isCloseButtonDown = false; + public event EventHandler SelectionPopupShouldCloseEvent; + public event EventHandler SelectAllEvent; + public event EventHandler UnSelectEvent; + public event EventHandler ReverseSelectEvent; + public event EventHandler ApplyScaleToStylusTipChanged; + public event EventHandler OnlyHitTestFullyContainedStrokesChanged; + public event EventHandler AllowClickToSelectLockedStrokeChanged; + + public bool ApplyScaleToStylusTip { + get => cb1.IsChecked??false; + set { + isProgramicallyChangeCheckBox = true; + cb1.IsChecked = value; + isProgramicallyChangeCheckBox = false; + } + } + + public bool OnlyHitTestFullyContainedStrokes { + get => cb2.IsChecked ?? false; + set { + isProgramicallyChangeCheckBox = true; + cb2.IsChecked = value; + isProgramicallyChangeCheckBox = false; + } + } + + public bool AllowClickToSelectLockedStroke { + get => cb3.IsChecked ?? false; + set { + isProgramicallyChangeCheckBox = true; + cb3.IsChecked = value; + isProgramicallyChangeCheckBox = false; + } + } + + public Border[] SelectionModeTabButtonBorders; + public SimpleStackPanel[] SelectionModeTabButtonIndicators; + public GeometryDrawing[] SelectionModeTabButtonIcons; + public TextBlock[] SelectionModeTabButtonTexts; + + public enum SelectionMode { + LassoMode, + RectangleMode + } + + private SelectionMode _selectionModeSelected = SelectionMode.LassoMode; + public SelectionMode SelectionModeSelected { + get => _selectionModeSelected; + set { + _selectionModeSelected = value; + UpdateSelectionModeButtonsCheckedDisplayStatus(); + } + } + + public event EventHandler SelectionModeChanged; + + public class SelectionModeChangedEventArgs : EventArgs + { + public SelectionMode PreviousMode { get; set; } + public SelectionMode NowMode { get; set; } + } + + private void UpdateSelectionModeButtonsCheckedDisplayStatus() { + foreach (var bd in SelectionModeTabButtonBorders) { + bd.Background = new SolidColorBrush(Colors.Transparent); + } + foreach (var indicator in SelectionModeTabButtonIndicators) { + indicator.Visibility = Visibility.Hidden; + } + foreach (var gd in SelectionModeTabButtonIcons) { + gd.Brush = new SolidColorBrush(Color.FromRgb(63, 63, 70)); + } + foreach (var text in SelectionModeTabButtonTexts) { + text.Foreground = new SolidColorBrush(Color.FromRgb(63, 63, 70)); + text.FontWeight = FontWeights.Normal; + } + + SelectionModeTabButtonBorders[(int)_selectionModeSelected].Background = new SolidColorBrush(Color.FromArgb(34, 59, 130, 246)); + SelectionModeTabButtonIndicators[(int)_selectionModeSelected].Visibility = Visibility.Visible; + SelectionModeTabButtonIcons[(int)_selectionModeSelected].Brush = new SolidColorBrush(Color.FromRgb(37, 99, 235)); + SelectionModeTabButtonTexts[(int)_selectionModeSelected].Foreground = new SolidColorBrush(Color.FromRgb(37, 99, 235)); + SelectionModeTabButtonTexts[(int)_selectionModeSelected].FontWeight = FontWeights.Bold; + } + + private bool isProgramicallyChangeCheckBox = false; + + private void cb1cked(object sender, RoutedEventArgs e) { + if (isProgramicallyChangeCheckBox) return; + ApplyScaleToStylusTipChanged?.Invoke(null,new RoutedEventArgs()); + } + + private void cb2cked(object sender, RoutedEventArgs e) { + if (isProgramicallyChangeCheckBox) return; + OnlyHitTestFullyContainedStrokesChanged?.Invoke(null,new RoutedEventArgs()); + } + + private void cb3cked(object sender, RoutedEventArgs e) { + if (isProgramicallyChangeCheckBox) return; + AllowClickToSelectLockedStrokeChanged?.Invoke(null,new RoutedEventArgs()); + } + + private void CloseButtonBorder_MouseDown(object sender, MouseButtonEventArgs e) { + isCloseButtonDown = true; + CloseButtonBorder.Background = new SolidColorBrush(Color.FromArgb(34, 220, 38, 38)); + } + + private void CloseButtonBorder_MouseLeave(object sender, MouseEventArgs e) { + isCloseButtonDown = false; + CloseButtonBorder.Background = new SolidColorBrush(Colors.Transparent); + } + + private void SelectionModeTabButton_MouseDown(object sender, MouseButtonEventArgs e) { + var pre = _selectionModeSelected; + _selectionModeSelected = (SelectionMode)Array.IndexOf(SelectionModeTabButtonBorders, (Border)sender); + UpdateSelectionModeButtonsCheckedDisplayStatus(); + + SelectionModeChanged?.Invoke(this, new SelectionModeChangedEventArgs() { + PreviousMode = pre, + NowMode = _selectionModeSelected, + }); + } + + private void CloseButtonBorder_MouseUp(object sender, MouseButtonEventArgs e) { + if (!isCloseButtonDown) return; + + CloseButtonBorder_MouseLeave(null, null); + SelectionPopupShouldCloseEvent?.Invoke(this,new RoutedEventArgs()); + } + + private void SelectAllButtonClicked(object sender, RoutedEventArgs e) { + SelectAllEvent?.Invoke(this,new RoutedEventArgs()); + } + + private void UnSelectButtonClicked(object sender, RoutedEventArgs e) { + UnSelectEvent?.Invoke(this,new RoutedEventArgs()); + } + + private void ReverseSelectButtonClicked(object sender, RoutedEventArgs e) { + ReverseSelectEvent?.Invoke(this,new RoutedEventArgs()); + } + } +} diff --git a/InkCanvasForClass/Popups/ShapeDrawingPopup.xaml b/InkCanvasForClass/Popups/ShapeDrawingPopup.xaml new file mode 100644 index 00000000..ad0d6f84 --- /dev/null +++ b/InkCanvasForClass/Popups/ShapeDrawingPopup.xaml @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Popups/ShapeDrawingPopup.xaml.cs b/InkCanvasForClass/Popups/ShapeDrawingPopup.xaml.cs new file mode 100644 index 00000000..b2c14a9d --- /dev/null +++ b/InkCanvasForClass/Popups/ShapeDrawingPopup.xaml.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Ink_Canvas.Popups { + + public partial class ShapeDrawingPopup : UserControl { + public ShapeDrawingPopup() { + InitializeComponent(); + ShapeDrawingItemsControl.ItemsSource = _items; + _items.Add(new ShapeDrawingItem() { + Name = "直线", + Image = FindResource("LineIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Line, + }); + _items.Add(new ShapeDrawingItem() { + Name = "虚线", + Image = FindResource("DashedLineIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.DashedLine, + }); + _items.Add(new ShapeDrawingItem() { + Name = "点虚线", + Image = FindResource("DottedLineIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.DottedLine, + }); + _items.Add(new ShapeDrawingItem() { + Name = "箭头", + Image = FindResource("ArrowLineIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.ArrowOneSide, + }); + _items.Add(new ShapeDrawingItem() { + Name = "双箭头", + Image = FindResource("ArrowLineTwoSideIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.ArrowTwoSide, + }); + _items.Add(new ShapeDrawingItem() { + Name = "矩形", + Image = FindResource("RectIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Rectangle, + }); + _items.Add(new ShapeDrawingItem() { + Name = "椭圆", + Image = FindResource("CircleIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Ellipse, + }); + _items.Add(new ShapeDrawingItem() { + Name = "饼图形", + Image = FindResource("PieIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.PieEllipse, + }); + _items.Add(new ShapeDrawingItem() { + Name = "三角形", + Image = FindResource("TriangleIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Triangle, + }); + _items.Add(new ShapeDrawingItem() { + Name = "直角三角", + Image = FindResource("RightTriangleIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.RightTriangle, + }); + _items.Add(new ShapeDrawingItem() { + Name = "菱形", + Image = FindResource("DiamondIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Diamond, + }); + _items.Add(new ShapeDrawingItem() { + Name = "平行四边形", + Image = FindResource("ParallelogramIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Parallelogram, + }); + _items.Add(new ShapeDrawingItem() { + Name = "四线三格", + Image = FindResource("FourLineIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.FourLine, + }); + _items.Add(new ShapeDrawingItem() { + Name = "五线谱", + Image = FindResource("FiveLineIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Staff, + }); + _items.Add(new ShapeDrawingItem() { + Name = "平面坐标轴", + Image = FindResource("DefaultAxisIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Axis2D, + }); + _items.Add(new ShapeDrawingItem() { + Name = "平面坐标轴2", + Image = FindResource("Axis2Icon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Axis2DA, + }); + _items.Add(new ShapeDrawingItem() { + Name = "平面坐标轴3", + Image = FindResource("Axis3Icon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Axis2DB, + }); + _items.Add(new ShapeDrawingItem() { + Name = "平面坐标轴4", + Image = FindResource("Axis4Icon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Axis2DC, + }); + _items.Add(new ShapeDrawingItem() { + Name = "三维坐标轴", + Image = FindResource("ThreeDimensionAxisIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Axis3D, + }); + _items.Add(new ShapeDrawingItem() { + Name = "双曲线", + Image = FindResource("HyperbolaIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Hyperbola, + }); + _items.Add(new ShapeDrawingItem() { + Name = "带焦点的双曲线", + Image = FindResource("HyperbolaWithFocalPointIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.HyperbolaF, + }); + _items.Add(new ShapeDrawingItem() { + Name = "抛物线1", + Image = FindResource("ParabolaIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Parabola, + }); + _items.Add(new ShapeDrawingItem() { + Name = "抛物线2", + Image = FindResource("Parabola2Icon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.ParabolaA, + }); + _items.Add(new ShapeDrawingItem() { + Name = "带焦点的抛物线2", + Image = FindResource("Parabola2WithFocalPointIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.ParabolaAF, + }); + _items.Add(new ShapeDrawingItem() { + Name = "圆柱体", + Image = FindResource("CylinderIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Cylinder, + }); + _items.Add(new ShapeDrawingItem() { + Name = "立方体", + Image = FindResource("CubeIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Cube, + }); + _items.Add(new ShapeDrawingItem() { + Name = "圆锥体", + Image = FindResource("ConeIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.Cone, + }); + _items.Add(new ShapeDrawingItem() { + Name = "带圆心的圆形", + Image = FindResource("CircleWithCenterPointIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.EllipseC, + }); + _items.Add(new ShapeDrawingItem() { + Name = "带中心点的矩形", + Image = FindResource("RectWithCenterPointIcon") as DrawingImage, + Type = MainWindow.ShapeDrawingType.RectangleC, + }); + } + + private class ShapeDrawingItem { + public string Name { get; set; } + public DrawingImage Image { get; set; } + public MainWindow.ShapeDrawingType Type { get; set; } + } + private ObservableCollection _items = new ObservableCollection(); + private bool isCloseButtonDown = false; + public event EventHandler ShapeDrawingPopupShouldCloseEvent; + public class ShapeSelectedEventArgs : EventArgs { + public MainWindow.ShapeDrawingType Type { get; set; } + public string Name { get; set; } + } + public event EventHandler ShapeSelectedEvent; + private void CloseButtonBorder_MouseDown(object sender, MouseButtonEventArgs e) { + isCloseButtonDown = true; + CloseButtonBorder.Background = new SolidColorBrush(Color.FromArgb(34, 220, 38, 38)); + } + private void CloseButtonBorder_MouseLeave(object sender, MouseEventArgs e) { + isCloseButtonDown = false; + CloseButtonBorder.Background = new SolidColorBrush(Colors.Transparent); + } + private void CloseButtonBorder_MouseUp(object sender, MouseButtonEventArgs e) { + if (!isCloseButtonDown) return; + + CloseButtonBorder_MouseLeave(null, null); + ShapeDrawingPopupShouldCloseEvent?.Invoke(this,new RoutedEventArgs()); + } + private Border shapeDrawingButtonDownBorder = null; + private void ShapeDrawingButtonBorder_MouseDown(object sender, MouseButtonEventArgs e) { + if (shapeDrawingButtonDownBorder != null) return; + shapeDrawingButtonDownBorder = (Border)sender; + shapeDrawingButtonDownBorder.Background = new SolidColorBrush(Color.FromRgb(228, 228, 231)); + } + private void ShapeDrawingButtonBorder_MouseLeave(object sender, MouseEventArgs e) { + if (shapeDrawingButtonDownBorder == null || shapeDrawingButtonDownBorder != sender) return; + shapeDrawingButtonDownBorder.Background = new SolidColorBrush(Colors.Transparent); + shapeDrawingButtonDownBorder = null; + } + private void ShapeDrawingButtonBorder_MouseUp(object sender, MouseButtonEventArgs e) { + if (shapeDrawingButtonDownBorder == null || shapeDrawingButtonDownBorder != sender) return; + ShapeDrawingButtonBorder_MouseLeave(sender, null); + ShapeDrawingPopupShouldCloseEvent?.Invoke(this,new RoutedEventArgs()); + ShapeSelectedEvent?.Invoke(this, new ShapeSelectedEventArgs() { + Type = ((ShapeDrawingItem)((Border)sender).Tag).Type, + Name = ((ShapeDrawingItem)((Border)sender).Tag).Name + }); + } + } +} diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/InkCanvasForClass/Properties/AssemblyInfo.cs similarity index 94% rename from Ink Canvas/Properties/AssemblyInfo.cs rename to InkCanvasForClass/Properties/AssemblyInfo.cs index 9c2ed554..76242813 100644 --- a/Ink Canvas/Properties/AssemblyInfo.cs +++ b/InkCanvasForClass/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Windows; [assembly: AssemblyTitle("InkCanvasForClass")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("CJK_mkp")] +[assembly: AssemblyCompany("HARKOTEK Studio")] [assembly: AssemblyProduct("InkCanvasForClass")] [assembly: AssemblyCopyright("© Copyright HARKOTEK Studio 2024-now")] [assembly: AssemblyTrademark("")] @@ -49,5 +49,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.6.0")] -[assembly: AssemblyFileVersion("1.7.6.0")] +[assembly: AssemblyVersion("6.0.0.0")] +[assembly: AssemblyFileVersion("6.0.0.0")] \ No newline at end of file diff --git a/Ink Canvas/Properties/PublishProfiles/FolderProfile.pubxml b/InkCanvasForClass/Properties/PublishProfiles/FolderProfile.pubxml similarity index 100% rename from Ink Canvas/Properties/PublishProfiles/FolderProfile.pubxml rename to InkCanvasForClass/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/Ink Canvas/Properties/PublishProfiles/FolderProfile.pubxml.user b/InkCanvasForClass/Properties/PublishProfiles/FolderProfile.pubxml.user similarity index 100% rename from Ink Canvas/Properties/PublishProfiles/FolderProfile.pubxml.user rename to InkCanvasForClass/Properties/PublishProfiles/FolderProfile.pubxml.user diff --git a/Ink Canvas/Properties/Resources.Designer.cs b/InkCanvasForClass/Properties/Resources.Designer.cs similarity index 58% rename from Ink Canvas/Properties/Resources.Designer.cs rename to InkCanvasForClass/Properties/Resources.Designer.cs index 725984ff..fd581c62 100644 --- a/Ink Canvas/Properties/Resources.Designer.cs +++ b/InkCanvasForClass/Properties/Resources.Designer.cs @@ -8,16 +8,10 @@ // //------------------------------------------------------------------------------ -using System.CodeDom.Compiler; -using System.ComponentModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Resources; -using System.Runtime.CompilerServices; - namespace Ink_Canvas.Properties { + using System; + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -25,27 +19,27 @@ namespace Ink_Canvas.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [GeneratedCode("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [DebuggerNonUserCode()] - [CompilerGenerated()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - private static ResourceManager resourceMan; + private static global::System.Resources.ResourceManager resourceMan; - private static CultureInfo resourceCulture; + private static global::System.Globalization.CultureInfo resourceCulture; - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - internal static ResourceManager ResourceManager { + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { get { - if (ReferenceEquals(resourceMan, null)) { - ResourceManager temp = new ResourceManager("Ink_Canvas.Properties.Resources", typeof(Resources).Assembly); + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ink_Canvas.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -56,8 +50,8 @@ namespace Ink_Canvas.Properties { /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - internal static CultureInfo Culture { + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -69,7 +63,7 @@ namespace Ink_Canvas.Properties { /// /// Looks up a localized resource of type System.IO.UnmanagedMemoryStream similar to System.IO.MemoryStream. /// - internal static UnmanagedMemoryStream TimerDownNotice { + internal static System.IO.UnmanagedMemoryStream TimerDownNotice { get { return ResourceManager.GetStream("TimerDownNotice", resourceCulture); } diff --git a/Ink Canvas/Properties/Resources.resx b/InkCanvasForClass/Properties/Resources.resx similarity index 100% rename from Ink Canvas/Properties/Resources.resx rename to InkCanvasForClass/Properties/Resources.resx diff --git a/Ink Canvas/Properties/Settings.Designer.cs b/InkCanvasForClass/Properties/Settings.Designer.cs similarity index 56% rename from Ink Canvas/Properties/Settings.Designer.cs rename to InkCanvasForClass/Properties/Settings.Designer.cs index fea09439..9c56a3a2 100644 --- a/Ink Canvas/Properties/Settings.Designer.cs +++ b/InkCanvasForClass/Properties/Settings.Designer.cs @@ -8,18 +8,14 @@ // //------------------------------------------------------------------------------ -using System.CodeDom.Compiler; -using System.Configuration; -using System.Runtime.CompilerServices; - namespace Ink_Canvas.Properties { - [CompilerGenerated()] - [GeneratedCode("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] - internal sealed partial class Settings : ApplicationSettingsBase { + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - private static Settings defaultInstance = ((Settings)(Synchronized(new Settings()))); + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { diff --git a/InkCanvasForClass/Properties/Settings.settings b/InkCanvasForClass/Properties/Settings.settings new file mode 100644 index 00000000..033d7a5e --- /dev/null +++ b/InkCanvasForClass/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/InkCanvasForClass/Properties/launchSettings.json b/InkCanvasForClass/Properties/launchSettings.json new file mode 100644 index 00000000..525abc70 --- /dev/null +++ b/InkCanvasForClass/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "InkCanvasForClass": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Resources/ChickenSoup.cs b/InkCanvasForClass/Resources/ChickenSoup.cs similarity index 93% rename from Ink Canvas/Resources/ChickenSoup.cs rename to InkCanvasForClass/Resources/ChickenSoup.cs index f18f3e73..0d461c44 100644 --- a/Ink Canvas/Resources/ChickenSoup.cs +++ b/InkCanvasForClass/Resources/ChickenSoup.cs @@ -1,7 +1,13 @@ -namespace Ink_Canvas +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Ink_Canvas { public static class ChickenSoup { - public static string[] OSUPlayerYuLu = { + public static string[] OSUPlayerYuLu = new string[] { "澳洲原神,启动!", "一眼丁真,鉴定为玩osu!玩的", "喊MimosaM给我炒两菜", @@ -32,7 +38,7 @@ "又有人2000了" }; - public static string[] MingYanJingJu = { + public static string[] MingYanJingJu = new string[] { "老骥伏枥,志在千里;烈士暮年,壮心不已", "有志者,事竟成", "有志者自有千方百计,无志者只感千难万难", @@ -98,7 +104,7 @@ "有志不在年高,无志空活百岁", }; - public static string[] GaoKaoPhrases = { + public static string[] GaoKaoPhrases = new string[] { "以梦为马,不负韶华。祝你高考顺利,未来可期!", "高考顺利,愿你乘风破浪,前程似锦!", "愿你高考顺利,不负韶华,梦想成真!", diff --git a/Ink Canvas/Resources/Cursors/Pen.cur b/InkCanvasForClass/Resources/Cursors/Pen.cur similarity index 100% rename from Ink Canvas/Resources/Cursors/Pen.cur rename to InkCanvasForClass/Resources/Cursors/Pen.cur diff --git a/InkCanvasForClass/Resources/Cursors/close-hand-cursor.cur b/InkCanvasForClass/Resources/Cursors/close-hand-cursor.cur new file mode 100644 index 00000000..f8085925 Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/close-hand-cursor.cur differ diff --git a/InkCanvasForClass/Resources/Cursors/cursor-move.cur b/InkCanvasForClass/Resources/Cursors/cursor-move.cur new file mode 100644 index 00000000..168593ea Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/cursor-move.cur differ diff --git a/InkCanvasForClass/Resources/Cursors/cursor-resize-lr.cur b/InkCanvasForClass/Resources/Cursors/cursor-resize-lr.cur new file mode 100644 index 00000000..a29801b8 Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/cursor-resize-lr.cur differ diff --git a/InkCanvasForClass/Resources/Cursors/cursor-resize-lt-rb.cur b/InkCanvasForClass/Resources/Cursors/cursor-resize-lt-rb.cur new file mode 100644 index 00000000..01c3e194 Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/cursor-resize-lt-rb.cur differ diff --git a/InkCanvasForClass/Resources/Cursors/cursor-resize-rt-lb.cur b/InkCanvasForClass/Resources/Cursors/cursor-resize-rt-lb.cur new file mode 100644 index 00000000..a0e31268 Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/cursor-resize-rt-lb.cur differ diff --git a/InkCanvasForClass/Resources/Cursors/cursor-resize-tb.cur b/InkCanvasForClass/Resources/Cursors/cursor-resize-tb.cur new file mode 100644 index 00000000..b0649957 Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/cursor-resize-tb.cur differ diff --git a/InkCanvasForClass/Resources/Cursors/open-hand-cursor.cur b/InkCanvasForClass/Resources/Cursors/open-hand-cursor.cur new file mode 100644 index 00000000..baa77795 Binary files /dev/null and b/InkCanvasForClass/Resources/Cursors/open-hand-cursor.cur differ diff --git a/Ink Canvas/Resources/DeveloperAvatars/ChangSakura.png b/InkCanvasForClass/Resources/DeveloperAvatars/ChangSakura.png similarity index 100% rename from Ink Canvas/Resources/DeveloperAvatars/ChangSakura.png rename to InkCanvasForClass/Resources/DeveloperAvatars/ChangSakura.png diff --git a/Ink Canvas/Resources/DeveloperAvatars/WXRIW.png b/InkCanvasForClass/Resources/DeveloperAvatars/WXRIW.png similarity index 100% rename from Ink Canvas/Resources/DeveloperAvatars/WXRIW.png rename to InkCanvasForClass/Resources/DeveloperAvatars/WXRIW.png diff --git a/Ink Canvas/Resources/DeveloperAvatars/dubi906w.jpg b/InkCanvasForClass/Resources/DeveloperAvatars/dubi906w.jpg similarity index 100% rename from Ink Canvas/Resources/DeveloperAvatars/dubi906w.jpg rename to InkCanvasForClass/Resources/DeveloperAvatars/dubi906w.jpg diff --git a/Ink Canvas/Resources/DrawShapeImageDictionary.xaml b/InkCanvasForClass/Resources/DrawShapeImageDictionary.xaml similarity index 100% rename from Ink Canvas/Resources/DrawShapeImageDictionary.xaml rename to InkCanvasForClass/Resources/DrawShapeImageDictionary.xaml diff --git a/InkCanvasForClass/Resources/GeometryIcons.xaml b/InkCanvasForClass/Resources/GeometryIcons.xaml new file mode 100644 index 00000000..919eb517 --- /dev/null +++ b/InkCanvasForClass/Resources/GeometryIcons.xaml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/InkCanvasForClass/Resources/ICCConfiguration.cs b/InkCanvasForClass/Resources/ICCConfiguration.cs new file mode 100644 index 00000000..5a866e0d --- /dev/null +++ b/InkCanvasForClass/Resources/ICCConfiguration.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; + +namespace Ink_Canvas.Resources.ICCConfiguration { + public enum InitialPositionTypes { + TopLeft, TopRight, BottomLeft, BottomRight, TopCenter, BottomCenter, Custom + } + public enum ElementCornerRadiusTypes { + SuperEllipse, Circle, Custom, None + } + public class NearSnapAreaSize { + public double[] TopLeft { get; set; } = {24,24}; + public double[] TopRight { get; set; } = {24,24}; + public double[] BottomLeft { get; set; } = {24,24}; + public double[] BottomRight { get; set; } = {24,24}; + public double TopCenter { get; set; } = 24; + public double BottomCenter { get; set; } = 24; + } + public class ICCFloatingBarConfiguration { + public bool SemiTransparent { get; set; } = false; + public bool NearSnap { get; set; } = true; + public InitialPositionTypes InitialPosition { get; set; } = InitialPositionTypes.BottomCenter; + public Point InitialPositionPoint { get; set; } = new Point(0, 0); + public double ElementCornerRadiusValue = 0; + public ElementCornerRadiusTypes ElementCornerRadiusType { get; set; } = ElementCornerRadiusTypes.SuperEllipse; + + public bool ParallaxEffect { get; set; } = true; + public bool MiniMode { get; set; } = false; + public Color ClearButtonColor { get; set; } = Color.FromRgb(224, 27, 36); + public Color ClearButtonPressColor { get; set; } = Color.FromRgb(254, 226, 226); + public Color ToolButtonSelectedBgColor { get; set; } = Color.FromRgb(37, 99, 235); + public double MovingLimitationNoSnap { get; set; } = 12; + public double MovingLimitationSnapped { get; set; } = 24; + + public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize() { + TopLeft = new double[] { 24, 24 }, + TopRight = new double[] { 24, 24 }, + BottomLeft = new double[] { 24, 24 }, + BottomRight = new double[] { 24, 24 }, + }; + + public string[] ToolBarItemsInCursorMode { get; set; } = new string[] { + "Cursor", "Pen", "Clear", "Separator", "Whiteboard", "Gesture", "Menu", "Fold" + }; + public string[] ToolBarItemsInMiniMode { get; set; } = new string[] { + "Cursor", "Pen", "Clear" + }; + public string[] ToolBarItemsInAnnotationMode { get; set; } = new string[] { + "Cursor", "Pen", "Clear", "Separator", "Eraser", "ShapeDrawing", "Select", "Separator", "Undo", "Redo", "Separator", "Whiteboard", "Gesture", "Menu", "Fold" + }; + } + + public class ICCConfiguration { + public ICCFloatingBarConfiguration FloatingBar { get; set; } = new ICCFloatingBarConfiguration(); + } +} diff --git a/Ink Canvas/Resources/IconImageDictionary.xaml b/InkCanvasForClass/Resources/IconImageDictionary.xaml similarity index 100% rename from Ink Canvas/Resources/IconImageDictionary.xaml rename to InkCanvasForClass/Resources/IconImageDictionary.xaml diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_add_circle_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_add_circle_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_add_circle_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_add_circle_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_circle_left_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_circle_left_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_circle_left_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_circle_left_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_circle_right_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_circle_right_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_circle_right_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_circle_right_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_clockwise_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_clockwise_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_clockwise_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_clockwise_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_rotate_clockwise_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_rotate_clockwise_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_arrow_rotate_clockwise_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_arrow_rotate_clockwise_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_book_question_mark_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_book_question_mark_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_book_question_mark_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_book_question_mark_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_calendar_sync_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_calendar_sync_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_calendar_sync_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_calendar_sync_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_camera_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_camera_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_camera_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_camera_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_clock_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_clock_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_clock_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_clock_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_control_button_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_control_button_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_control_button_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_control_button_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_copy_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_copy_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_copy_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_copy_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_copy_add_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_copy_add_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_copy_add_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_copy_add_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_cursorWITHdelete_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_cursorWITHdelete_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_cursorWITHdelete_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_cursorWITHdelete_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_cursor_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_cursor_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_cursor_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_cursor_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_dark_theme_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_dark_theme_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_dark_theme_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_dark_theme_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_delete_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_delete_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_delete_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_delete_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_dismiss_circle_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_dismiss_circle_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_dismiss_circle_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_dismiss_circle_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_drag_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_drag_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_drag_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_drag_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_dual_screen_span_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_dual_screen_span_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_dual_screen_span_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_dual_screen_span_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_edit_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_edit_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_edit_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_edit_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_flip_horizontal_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_flip_horizontal_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_flip_horizontal_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_flip_horizontal_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_flip_vertical_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_flip_vertical_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_flip_vertical_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_flip_vertical_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_folder_open_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_folder_open_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_folder_open_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_folder_open_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_keyboard_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_keyboard_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_keyboard_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_keyboard_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_lasso_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_lasso_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_lasso_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_lasso_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_people_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_people_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_people_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_people_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_people_money_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_people_money_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_people_money_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_people_money_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_person_money_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_person_money_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_person_money_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_person_money_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_power_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_power_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_power_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_power_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_save_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_save_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_save_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_save_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_scale_fit_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_scale_fit_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_scale_fit_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_scale_fit_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_scales_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_scales_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_scales_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_scales_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_shapes_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_shapes_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_shapes_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_shapes_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_signature_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_signature_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_signature_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_signature_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_timer_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_timer_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_timer_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_timer_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_weather_moon_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_weather_moon_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_weather_moon_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_weather_moon_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_weather_sunny_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_weather_sunny_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_weather_sunny_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_weather_sunny_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/ic_fluent_whiteboard_24_regular.png b/InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_whiteboard_24_regular.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/ic_fluent_whiteboard_24_regular.png rename to InkCanvasForClass/Resources/Icons-Fluent/ic_fluent_whiteboard_24_regular.png diff --git a/Ink Canvas/Resources/Icons-Fluent/party.png b/InkCanvasForClass/Resources/Icons-Fluent/party.png similarity index 100% rename from Ink Canvas/Resources/Icons-Fluent/party.png rename to InkCanvasForClass/Resources/Icons-Fluent/party.png diff --git a/Ink Canvas/Resources/Icons-png/AdmoxBooth.png b/InkCanvasForClass/Resources/Icons-png/AdmoxBooth.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/AdmoxBooth.png rename to InkCanvasForClass/Resources/Icons-png/AdmoxBooth.png diff --git a/Ink Canvas/Resources/Icons-png/AdmoxWhiteboard.png b/InkCanvasForClass/Resources/Icons-png/AdmoxWhiteboard.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/AdmoxWhiteboard.png rename to InkCanvasForClass/Resources/Icons-png/AdmoxWhiteboard.png diff --git a/Ink Canvas/Resources/Icons-png/Desmos.png b/InkCanvasForClass/Resources/Icons-png/Desmos.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/Desmos.png rename to InkCanvasForClass/Resources/Icons-png/Desmos.png diff --git a/Ink Canvas/Resources/Icons-png/Donview.png b/InkCanvasForClass/Resources/Icons-png/Donview.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/Donview.png rename to InkCanvasForClass/Resources/Icons-png/Donview.png diff --git a/Ink Canvas/Resources/Icons-png/EasiCamera.png b/InkCanvasForClass/Resources/Icons-png/EasiCamera.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/EasiCamera.png rename to InkCanvasForClass/Resources/Icons-png/EasiCamera.png diff --git a/Ink Canvas/Resources/Icons-png/EasiNote.png b/InkCanvasForClass/Resources/Icons-png/EasiNote.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/EasiNote.png rename to InkCanvasForClass/Resources/Icons-png/EasiNote.png diff --git a/Ink Canvas/Resources/Icons-png/EasiNote3.png b/InkCanvasForClass/Resources/Icons-png/EasiNote3.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/EasiNote3.png rename to InkCanvasForClass/Resources/Icons-png/EasiNote3.png diff --git a/Ink Canvas/Resources/Icons-png/EasiNote3C.png b/InkCanvasForClass/Resources/Icons-png/EasiNote3C.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/EasiNote3C.png rename to InkCanvasForClass/Resources/Icons-png/EasiNote3C.png diff --git a/Ink Canvas/Resources/Icons-png/EasiNote5C.png b/InkCanvasForClass/Resources/Icons-png/EasiNote5C.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/EasiNote5C.png rename to InkCanvasForClass/Resources/Icons-png/EasiNote5C.png diff --git a/Ink Canvas/Resources/Icons-png/HiteAnnotation.png b/InkCanvasForClass/Resources/Icons-png/HiteAnnotation.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/HiteAnnotation.png rename to InkCanvasForClass/Resources/Icons-png/HiteAnnotation.png diff --git a/Ink Canvas/Resources/Icons-png/HiteBoard.png b/InkCanvasForClass/Resources/Icons-png/HiteBoard.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/HiteBoard.png rename to InkCanvasForClass/Resources/Icons-png/HiteBoard.png diff --git a/Ink Canvas/Resources/Icons-png/HiteCamera.png b/InkCanvasForClass/Resources/Icons-png/HiteCamera.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/HiteCamera.png rename to InkCanvasForClass/Resources/Icons-png/HiteCamera.png diff --git a/Ink Canvas/Resources/Icons-png/HiteLightBoard.png b/InkCanvasForClass/Resources/Icons-png/HiteLightBoard.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/HiteLightBoard.png rename to InkCanvasForClass/Resources/Icons-png/HiteLightBoard.png diff --git a/Ink Canvas/Resources/Icons-png/InkCanvas.png b/InkCanvasForClass/Resources/Icons-png/InkCanvas.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/InkCanvas.png rename to InkCanvasForClass/Resources/Icons-png/InkCanvas.png diff --git a/Ink Canvas/Resources/Icons-png/MaxHubWhiteboard.png b/InkCanvasForClass/Resources/Icons-png/MaxHubWhiteboard.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/MaxHubWhiteboard.png rename to InkCanvasForClass/Resources/Icons-png/MaxHubWhiteboard.png diff --git a/Ink Canvas/Resources/Icons-png/PPTTools.png b/InkCanvasForClass/Resources/Icons-png/PPTTools.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/PPTTools.png rename to InkCanvasForClass/Resources/Icons-png/PPTTools.png diff --git a/Ink Canvas/Resources/Icons-png/Powerpoint.png b/InkCanvasForClass/Resources/Icons-png/Powerpoint.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/Powerpoint.png rename to InkCanvasForClass/Resources/Icons-png/Powerpoint.png diff --git a/Ink Canvas/Resources/Icons-png/Seewo2Annotation.png b/InkCanvasForClass/Resources/Icons-png/Seewo2Annotation.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/Seewo2Annotation.png rename to InkCanvasForClass/Resources/Icons-png/Seewo2Annotation.png diff --git a/Ink Canvas/Resources/Icons-png/SeewoPinco.png b/InkCanvasForClass/Resources/Icons-png/SeewoPinco.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/SeewoPinco.png rename to InkCanvasForClass/Resources/Icons-png/SeewoPinco.png diff --git a/Ink Canvas/Resources/Icons-png/VComYouJiao.png b/InkCanvasForClass/Resources/Icons-png/VComYouJiao.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/VComYouJiao.png rename to InkCanvasForClass/Resources/Icons-png/VComYouJiao.png diff --git a/Ink Canvas/Resources/Icons-png/WPS.png b/InkCanvasForClass/Resources/Icons-png/WPS.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/WPS.png rename to InkCanvasForClass/Resources/Icons-png/WPS.png diff --git a/Ink Canvas/Resources/Icons-png/WenXiang.png b/InkCanvasForClass/Resources/Icons-png/WenXiang.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/WenXiang.png rename to InkCanvasForClass/Resources/Icons-png/WenXiang.png diff --git a/Ink Canvas/Resources/Icons-png/Whiteboard.png b/InkCanvasForClass/Resources/Icons-png/Whiteboard.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/Whiteboard.png rename to InkCanvasForClass/Resources/Icons-png/Whiteboard.png diff --git a/Ink Canvas/Resources/Icons-png/YiYunVisualPresenter.png b/InkCanvasForClass/Resources/Icons-png/YiYunVisualPresenter.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/YiYunVisualPresenter.png rename to InkCanvasForClass/Resources/Icons-png/YiYunVisualPresenter.png diff --git a/Ink Canvas/Resources/Icons-png/YiYunWhiteboard.png b/InkCanvasForClass/Resources/Icons-png/YiYunWhiteboard.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/YiYunWhiteboard.png rename to InkCanvasForClass/Resources/Icons-png/YiYunWhiteboard.png diff --git a/Ink Canvas/Resources/Icons-png/check-box-background.png b/InkCanvasForClass/Resources/Icons-png/check-box-background.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/check-box-background.png rename to InkCanvasForClass/Resources/Icons-png/check-box-background.png diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-folder.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-folder.png new file mode 100644 index 00000000..66291766 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-folder.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-small-icon.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-small-icon.png new file mode 100644 index 00000000..4f6235ef Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-small-icon.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/disk-drive.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/disk-drive.png new file mode 100644 index 00000000..7ab6fd20 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/disk-drive.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/documents-folder.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/documents-folder.png new file mode 100644 index 00000000..8b18500d Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/documents-folder.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/folder.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/folder.png new file mode 100644 index 00000000..32706ec6 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/folder.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/photo-small-icon.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/photo-small-icon.png new file mode 100644 index 00000000..eb1b6ceb Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/photo-small-icon.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/program-icon.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/program-icon.png new file mode 100644 index 00000000..1c61bf41 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/program-icon.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/user-folder.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/user-folder.png new file mode 100644 index 00000000..851d5c68 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/classic-icons/user-folder.png differ diff --git a/Ink Canvas/Resources/Icons-png/close-circle.png b/InkCanvasForClass/Resources/Icons-png/close-circle.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/close-circle.png rename to InkCanvasForClass/Resources/Icons-png/close-circle.png diff --git a/Ink Canvas/Resources/Icons-png/down.png b/InkCanvasForClass/Resources/Icons-png/down.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/down.png rename to InkCanvasForClass/Resources/Icons-png/down.png diff --git a/Ink Canvas/Resources/Icons-png/eraser-line.png b/InkCanvasForClass/Resources/Icons-png/eraser-line.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/eraser-line.png rename to InkCanvasForClass/Resources/Icons-png/eraser-line.png diff --git a/Ink Canvas/Resources/Icons-png/eraser-outline.png b/InkCanvasForClass/Resources/Icons-png/eraser-outline.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/eraser-outline.png rename to InkCanvasForClass/Resources/Icons-png/eraser-outline.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/arrow.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/arrow.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/arrow.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/arrow.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/centered-circle-dashed.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/centered-circle-dashed.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/centered-circle-dashed.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/centered-circle-dashed.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/centered-circle.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/centered-circle.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/centered-circle.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/centered-circle.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/centered-oval.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/centered-oval.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/centered-oval.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/centered-oval.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/centered-square.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/centered-square.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/centered-square.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/centered-square.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/cone.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/cone.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/cone.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/cone.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/cube.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/cube.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/cube.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/cube.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/cylinder.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/cylinder.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/cylinder.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/cylinder.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/dashed-line.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/dashed-line.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/dashed-line.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/dashed-line.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/dotted-line.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/dotted-line.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/dotted-line.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/dotted-line.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/line.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/line.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/line.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/line.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/paralle-lines.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/paralle-lines.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/paralle-lines.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/paralle-lines.png diff --git a/Ink Canvas/Resources/Icons-png/geo-icons/square.png b/InkCanvasForClass/Resources/Icons-png/geo-icons/square.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/geo-icons/square.png rename to InkCanvasForClass/Resources/Icons-png/geo-icons/square.png diff --git a/Ink Canvas/Resources/Icons-png/ica.png b/InkCanvasForClass/Resources/Icons-png/ica.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/ica.png rename to InkCanvasForClass/Resources/Icons-png/ica.png diff --git a/InkCanvasForClass/Resources/Icons-png/icc-toolbar-v2.png b/InkCanvasForClass/Resources/Icons-png/icc-toolbar-v2.png new file mode 100644 index 00000000..b64fa6e4 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/icc-toolbar-v2.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/icc-transparent-dark-small.png b/InkCanvasForClass/Resources/Icons-png/icc-transparent-dark-small.png new file mode 100644 index 00000000..570b09ff Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/icc-transparent-dark-small.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/icc-transparent-dark.png b/InkCanvasForClass/Resources/Icons-png/icc-transparent-dark.png new file mode 100644 index 00000000..c34b515e Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/icc-transparent-dark.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/icc-transparent.png b/InkCanvasForClass/Resources/Icons-png/icc-transparent.png new file mode 100644 index 00000000..9c87b47e Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/icc-transparent.png differ diff --git a/InkCanvasForClass/Resources/Icons-png/icc.png b/InkCanvasForClass/Resources/Icons-png/icc.png new file mode 100644 index 00000000..99017b97 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/icc.png differ diff --git a/Ink Canvas/Resources/Icons-png/idt.png b/InkCanvasForClass/Resources/Icons-png/idt.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/idt.png rename to InkCanvasForClass/Resources/Icons-png/idt.png diff --git a/Ink Canvas/Resources/Icons-png/kuanciya.png b/InkCanvasForClass/Resources/Icons-png/kuanciya.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/kuanciya.png rename to InkCanvasForClass/Resources/Icons-png/kuanciya.png diff --git a/Ink Canvas/Resources/Icons-png/kuandogeyuanliangwo.png b/InkCanvasForClass/Resources/Icons-png/kuandogeyuanliangwo.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/kuandogeyuanliangwo.png rename to InkCanvasForClass/Resources/Icons-png/kuandogeyuanliangwo.png diff --git a/Ink Canvas/Resources/Icons-png/kuandoujiyanhuaji.png b/InkCanvasForClass/Resources/Icons-png/kuandoujiyanhuaji.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/kuandoujiyanhuaji.png rename to InkCanvasForClass/Resources/Icons-png/kuandoujiyanhuaji.png diff --git a/Ink Canvas/Resources/Icons-png/kuanneikuhuaji.png b/InkCanvasForClass/Resources/Icons-png/kuanneikuhuaji.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/kuanneikuhuaji.png rename to InkCanvasForClass/Resources/Icons-png/kuanneikuhuaji.png diff --git a/Ink Canvas/Resources/Icons-png/kuanshounvhuaji.png b/InkCanvasForClass/Resources/Icons-png/kuanshounvhuaji.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/kuanshounvhuaji.png rename to InkCanvasForClass/Resources/Icons-png/kuanshounvhuaji.png diff --git a/Ink Canvas/Resources/Icons-png/menu-back-highlighted.png b/InkCanvasForClass/Resources/Icons-png/menu-back-highlighted.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/menu-back-highlighted.png rename to InkCanvasForClass/Resources/Icons-png/menu-back-highlighted.png diff --git a/Ink Canvas/Resources/Icons-png/menu-back.png b/InkCanvasForClass/Resources/Icons-png/menu-back.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/menu-back.png rename to InkCanvasForClass/Resources/Icons-png/menu-back.png diff --git a/Ink Canvas/Resources/Icons-png/minimize.png b/InkCanvasForClass/Resources/Icons-png/minimize.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/minimize.png rename to InkCanvasForClass/Resources/Icons-png/minimize.png diff --git a/Ink Canvas/Resources/Icons-png/penUpright.png b/InkCanvasForClass/Resources/Icons-png/penUpright.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/penUpright.png rename to InkCanvasForClass/Resources/Icons-png/penUpright.png diff --git a/Ink Canvas/Resources/Icons-png/playCircle.png b/InkCanvasForClass/Resources/Icons-png/playCircle.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/playCircle.png rename to InkCanvasForClass/Resources/Icons-png/playCircle.png diff --git a/Ink Canvas/Resources/Icons-png/pressdown-background.png b/InkCanvasForClass/Resources/Icons-png/pressdown-background.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/pressdown-background.png rename to InkCanvasForClass/Resources/Icons-png/pressdown-background.png diff --git a/Ink Canvas/Resources/Icons-png/redo.png b/InkCanvasForClass/Resources/Icons-png/redo.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/redo.png rename to InkCanvasForClass/Resources/Icons-png/redo.png diff --git a/Ink Canvas/Resources/Icons-png/setting.png b/InkCanvasForClass/Resources/Icons-png/setting.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/setting.png rename to InkCanvasForClass/Resources/Icons-png/setting.png diff --git a/Ink Canvas/Resources/Icons-png/tiebahuaji.png b/InkCanvasForClass/Resources/Icons-png/tiebahuaji.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/tiebahuaji.png rename to InkCanvasForClass/Resources/Icons-png/tiebahuaji.png diff --git a/Ink Canvas/Resources/Icons-png/transparent-grid.png b/InkCanvasForClass/Resources/Icons-png/transparent-grid.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/transparent-grid.png rename to InkCanvasForClass/Resources/Icons-png/transparent-grid.png diff --git a/Ink Canvas/Resources/Icons-png/twoFingelMove-Blue.png b/InkCanvasForClass/Resources/Icons-png/twoFingelMove-Blue.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/twoFingelMove-Blue.png rename to InkCanvasForClass/Resources/Icons-png/twoFingelMove-Blue.png diff --git a/Ink Canvas/Resources/Icons-png/twoFingelMove.png b/InkCanvasForClass/Resources/Icons-png/twoFingelMove.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/twoFingelMove.png rename to InkCanvasForClass/Resources/Icons-png/twoFingelMove.png diff --git a/InkCanvasForClass/Resources/Icons-png/uac_icon.png b/InkCanvasForClass/Resources/Icons-png/uac_icon.png new file mode 100644 index 00000000..9c90eba8 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/uac_icon.png differ diff --git a/Ink Canvas/Resources/Icons-png/undo.png b/InkCanvasForClass/Resources/Icons-png/undo.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/undo.png rename to InkCanvasForClass/Resources/Icons-png/undo.png diff --git a/Ink Canvas/Resources/Icons-png/up.png b/InkCanvasForClass/Resources/Icons-png/up.png similarity index 100% rename from Ink Canvas/Resources/Icons-png/up.png rename to InkCanvasForClass/Resources/Icons-png/up.png diff --git a/InkCanvasForClass/Resources/Icons-png/windows-ink.png b/InkCanvasForClass/Resources/Icons-png/windows-ink.png new file mode 100644 index 00000000..1026b335 Binary files /dev/null and b/InkCanvasForClass/Resources/Icons-png/windows-ink.png differ diff --git a/Ink Canvas/Resources/Icons/DrawShapeIcon.ai b/InkCanvasForClass/Resources/Icons/DrawShapeIcon.ai similarity index 100% rename from Ink Canvas/Resources/Icons/DrawShapeIcon.ai rename to InkCanvasForClass/Resources/Icons/DrawShapeIcon.ai diff --git a/Ink Canvas/Resources/Icons/DrawShapeIcon.svg b/InkCanvasForClass/Resources/Icons/DrawShapeIcon.svg similarity index 100% rename from Ink Canvas/Resources/Icons/DrawShapeIcon.svg rename to InkCanvasForClass/Resources/Icons/DrawShapeIcon.svg diff --git a/Ink Canvas/Resources/Icons/Flip.ai b/InkCanvasForClass/Resources/Icons/Flip.ai similarity index 100% rename from Ink Canvas/Resources/Icons/Flip.ai rename to InkCanvasForClass/Resources/Icons/Flip.ai diff --git a/Ink Canvas/Resources/Icons/Flip.svg b/InkCanvasForClass/Resources/Icons/Flip.svg similarity index 100% rename from Ink Canvas/Resources/Icons/Flip.svg rename to InkCanvasForClass/Resources/Icons/Flip.svg diff --git a/Ink Canvas/Resources/Icons/Rotate.ai b/InkCanvasForClass/Resources/Icons/Rotate.ai similarity index 100% rename from Ink Canvas/Resources/Icons/Rotate.ai rename to InkCanvasForClass/Resources/Icons/Rotate.ai diff --git a/Ink Canvas/Resources/Icons/Rotate.svg b/InkCanvasForClass/Resources/Icons/Rotate.svg similarity index 100% rename from Ink Canvas/Resources/Icons/Rotate.svg rename to InkCanvasForClass/Resources/Icons/Rotate.svg diff --git a/Ink Canvas/Resources/Icons/Rotate45.svg b/InkCanvasForClass/Resources/Icons/Rotate45.svg similarity index 100% rename from Ink Canvas/Resources/Icons/Rotate45.svg rename to InkCanvasForClass/Resources/Icons/Rotate45.svg diff --git a/Ink Canvas/Resources/Icons/Rotate90.svg b/InkCanvasForClass/Resources/Icons/Rotate90.svg similarity index 100% rename from Ink Canvas/Resources/Icons/Rotate90.svg rename to InkCanvasForClass/Resources/Icons/Rotate90.svg diff --git a/Ink Canvas/Resources/Icons/ShapeArrowLine.ai b/InkCanvasForClass/Resources/Icons/ShapeArrowLine.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeArrowLine.ai rename to InkCanvasForClass/Resources/Icons/ShapeArrowLine.ai diff --git a/Ink Canvas/Resources/Icons/ShapeArrowLine.svg b/InkCanvasForClass/Resources/Icons/ShapeArrowLine.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeArrowLine.svg rename to InkCanvasForClass/Resources/Icons/ShapeArrowLine.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCircle.ai b/InkCanvasForClass/Resources/Icons/ShapeCircle.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCircle.ai rename to InkCanvasForClass/Resources/Icons/ShapeCircle.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCircle.svg b/InkCanvasForClass/Resources/Icons/ShapeCircle.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCircle.svg rename to InkCanvasForClass/Resources/Icons/ShapeCircle.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCone.ai b/InkCanvasForClass/Resources/Icons/ShapeCone.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCone.ai rename to InkCanvasForClass/Resources/Icons/ShapeCone.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCone.svg b/InkCanvasForClass/Resources/Icons/ShapeCone.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCone.svg rename to InkCanvasForClass/Resources/Icons/ShapeCone.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate1.ai b/InkCanvasForClass/Resources/Icons/ShapeCoordinate1.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate1.ai rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate1.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate1.svg b/InkCanvasForClass/Resources/Icons/ShapeCoordinate1.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate1.svg rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate1.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate2.ai b/InkCanvasForClass/Resources/Icons/ShapeCoordinate2.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate2.ai rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate2.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate2.svg b/InkCanvasForClass/Resources/Icons/ShapeCoordinate2.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate2.svg rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate2.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate3.ai b/InkCanvasForClass/Resources/Icons/ShapeCoordinate3.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate3.ai rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate3.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate3.svg b/InkCanvasForClass/Resources/Icons/ShapeCoordinate3.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate3.svg rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate3.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate4.ai b/InkCanvasForClass/Resources/Icons/ShapeCoordinate4.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate4.ai rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate4.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate4.svg b/InkCanvasForClass/Resources/Icons/ShapeCoordinate4.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate4.svg rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate4.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate5.ai b/InkCanvasForClass/Resources/Icons/ShapeCoordinate5.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate5.ai rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate5.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCoordinate5.svg b/InkCanvasForClass/Resources/Icons/ShapeCoordinate5.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCoordinate5.svg rename to InkCanvasForClass/Resources/Icons/ShapeCoordinate5.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCuboid.ai b/InkCanvasForClass/Resources/Icons/ShapeCuboid.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCuboid.ai rename to InkCanvasForClass/Resources/Icons/ShapeCuboid.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCuboid.svg b/InkCanvasForClass/Resources/Icons/ShapeCuboid.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCuboid.svg rename to InkCanvasForClass/Resources/Icons/ShapeCuboid.svg diff --git a/Ink Canvas/Resources/Icons/ShapeCylinder.ai b/InkCanvasForClass/Resources/Icons/ShapeCylinder.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCylinder.ai rename to InkCanvasForClass/Resources/Icons/ShapeCylinder.ai diff --git a/Ink Canvas/Resources/Icons/ShapeCylinder.svg b/InkCanvasForClass/Resources/Icons/ShapeCylinder.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeCylinder.svg rename to InkCanvasForClass/Resources/Icons/ShapeCylinder.svg diff --git a/Ink Canvas/Resources/Icons/ShapeDashedCircle.ai b/InkCanvasForClass/Resources/Icons/ShapeDashedCircle.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeDashedCircle.ai rename to InkCanvasForClass/Resources/Icons/ShapeDashedCircle.ai diff --git a/Ink Canvas/Resources/Icons/ShapeDashedCircle.svg b/InkCanvasForClass/Resources/Icons/ShapeDashedCircle.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeDashedCircle.svg rename to InkCanvasForClass/Resources/Icons/ShapeDashedCircle.svg diff --git a/Ink Canvas/Resources/Icons/ShapeDashedLine.ai b/InkCanvasForClass/Resources/Icons/ShapeDashedLine.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeDashedLine.ai rename to InkCanvasForClass/Resources/Icons/ShapeDashedLine.ai diff --git a/Ink Canvas/Resources/Icons/ShapeDashedLine.svg b/InkCanvasForClass/Resources/Icons/ShapeDashedLine.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeDashedLine.svg rename to InkCanvasForClass/Resources/Icons/ShapeDashedLine.svg diff --git a/Ink Canvas/Resources/Icons/ShapeDotLine.ai b/InkCanvasForClass/Resources/Icons/ShapeDotLine.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeDotLine.ai rename to InkCanvasForClass/Resources/Icons/ShapeDotLine.ai diff --git a/Ink Canvas/Resources/Icons/ShapeDotLine.svg b/InkCanvasForClass/Resources/Icons/ShapeDotLine.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeDotLine.svg rename to InkCanvasForClass/Resources/Icons/ShapeDotLine.svg diff --git a/Ink Canvas/Resources/Icons/ShapeEllipse.ai b/InkCanvasForClass/Resources/Icons/ShapeEllipse.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeEllipse.ai rename to InkCanvasForClass/Resources/Icons/ShapeEllipse.ai diff --git a/Ink Canvas/Resources/Icons/ShapeEllipse.svg b/InkCanvasForClass/Resources/Icons/ShapeEllipse.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeEllipse.svg rename to InkCanvasForClass/Resources/Icons/ShapeEllipse.svg diff --git a/Ink Canvas/Resources/Icons/ShapeEllipseCenter.ai b/InkCanvasForClass/Resources/Icons/ShapeEllipseCenter.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeEllipseCenter.ai rename to InkCanvasForClass/Resources/Icons/ShapeEllipseCenter.ai diff --git a/Ink Canvas/Resources/Icons/ShapeEllipseCenter.svg b/InkCanvasForClass/Resources/Icons/ShapeEllipseCenter.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeEllipseCenter.svg rename to InkCanvasForClass/Resources/Icons/ShapeEllipseCenter.svg diff --git a/Ink Canvas/Resources/Icons/ShapeEllipseCenterWithFocalPoint.ai b/InkCanvasForClass/Resources/Icons/ShapeEllipseCenterWithFocalPoint.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeEllipseCenterWithFocalPoint.ai rename to InkCanvasForClass/Resources/Icons/ShapeEllipseCenterWithFocalPoint.ai diff --git a/Ink Canvas/Resources/Icons/ShapeEllipseCenterWithFocalPoint.svg b/InkCanvasForClass/Resources/Icons/ShapeEllipseCenterWithFocalPoint.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeEllipseCenterWithFocalPoint.svg rename to InkCanvasForClass/Resources/Icons/ShapeEllipseCenterWithFocalPoint.svg diff --git a/Ink Canvas/Resources/Icons/ShapeHyperbola.ai b/InkCanvasForClass/Resources/Icons/ShapeHyperbola.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeHyperbola.ai rename to InkCanvasForClass/Resources/Icons/ShapeHyperbola.ai diff --git a/Ink Canvas/Resources/Icons/ShapeHyperbola.svg b/InkCanvasForClass/Resources/Icons/ShapeHyperbola.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeHyperbola.svg rename to InkCanvasForClass/Resources/Icons/ShapeHyperbola.svg diff --git a/Ink Canvas/Resources/Icons/ShapeHyperbolaWithFocalPoint.ai b/InkCanvasForClass/Resources/Icons/ShapeHyperbolaWithFocalPoint.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeHyperbolaWithFocalPoint.ai rename to InkCanvasForClass/Resources/Icons/ShapeHyperbolaWithFocalPoint.ai diff --git a/Ink Canvas/Resources/Icons/ShapeHyperbolaWithFocalPoint.svg b/InkCanvasForClass/Resources/Icons/ShapeHyperbolaWithFocalPoint.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeHyperbolaWithFocalPoint.svg rename to InkCanvasForClass/Resources/Icons/ShapeHyperbolaWithFocalPoint.svg diff --git a/Ink Canvas/Resources/Icons/ShapeLine.ai b/InkCanvasForClass/Resources/Icons/ShapeLine.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeLine.ai rename to InkCanvasForClass/Resources/Icons/ShapeLine.ai diff --git a/Ink Canvas/Resources/Icons/ShapeLine.svg b/InkCanvasForClass/Resources/Icons/ShapeLine.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeLine.svg rename to InkCanvasForClass/Resources/Icons/ShapeLine.svg diff --git a/Ink Canvas/Resources/Icons/ShapeParabola.ai b/InkCanvasForClass/Resources/Icons/ShapeParabola.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeParabola.ai rename to InkCanvasForClass/Resources/Icons/ShapeParabola.ai diff --git a/Ink Canvas/Resources/Icons/ShapeParabola.svg b/InkCanvasForClass/Resources/Icons/ShapeParabola.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeParabola.svg rename to InkCanvasForClass/Resources/Icons/ShapeParabola.svg diff --git a/Ink Canvas/Resources/Icons/ShapeParabolaWithFocalPoint.ai b/InkCanvasForClass/Resources/Icons/ShapeParabolaWithFocalPoint.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeParabolaWithFocalPoint.ai rename to InkCanvasForClass/Resources/Icons/ShapeParabolaWithFocalPoint.ai diff --git a/Ink Canvas/Resources/Icons/ShapeParabolaWithFocalPoint.svg b/InkCanvasForClass/Resources/Icons/ShapeParabolaWithFocalPoint.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeParabolaWithFocalPoint.svg rename to InkCanvasForClass/Resources/Icons/ShapeParabolaWithFocalPoint.svg diff --git a/Ink Canvas/Resources/Icons/ShapeParallelLine.ai b/InkCanvasForClass/Resources/Icons/ShapeParallelLine.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeParallelLine.ai rename to InkCanvasForClass/Resources/Icons/ShapeParallelLine.ai diff --git a/Ink Canvas/Resources/Icons/ShapeParallelLine.svg b/InkCanvasForClass/Resources/Icons/ShapeParallelLine.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeParallelLine.svg rename to InkCanvasForClass/Resources/Icons/ShapeParallelLine.svg diff --git a/Ink Canvas/Resources/Icons/ShapeRectangle.ai b/InkCanvasForClass/Resources/Icons/ShapeRectangle.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeRectangle.ai rename to InkCanvasForClass/Resources/Icons/ShapeRectangle.ai diff --git a/Ink Canvas/Resources/Icons/ShapeRectangle.svg b/InkCanvasForClass/Resources/Icons/ShapeRectangle.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeRectangle.svg rename to InkCanvasForClass/Resources/Icons/ShapeRectangle.svg diff --git a/Ink Canvas/Resources/Icons/ShapeRectangleCenter.ai b/InkCanvasForClass/Resources/Icons/ShapeRectangleCenter.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeRectangleCenter.ai rename to InkCanvasForClass/Resources/Icons/ShapeRectangleCenter.ai diff --git a/Ink Canvas/Resources/Icons/ShapeRectangleCenter.svg b/InkCanvasForClass/Resources/Icons/ShapeRectangleCenter.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeRectangleCenter.svg rename to InkCanvasForClass/Resources/Icons/ShapeRectangleCenter.svg diff --git a/Ink Canvas/Resources/Icons/ShapeTetrahedron.ai b/InkCanvasForClass/Resources/Icons/ShapeTetrahedron.ai similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeTetrahedron.ai rename to InkCanvasForClass/Resources/Icons/ShapeTetrahedron.ai diff --git a/Ink Canvas/Resources/Icons/ShapeTetrahedron.svg b/InkCanvasForClass/Resources/Icons/ShapeTetrahedron.svg similarity index 100% rename from Ink Canvas/Resources/Icons/ShapeTetrahedron.svg rename to InkCanvasForClass/Resources/Icons/ShapeTetrahedron.svg diff --git a/InkCanvasForClass/Resources/Illustrations/settingsv2-powerpoint-pagebtns-bottom.png b/InkCanvasForClass/Resources/Illustrations/settingsv2-powerpoint-pagebtns-bottom.png new file mode 100644 index 00000000..c947e78c Binary files /dev/null and b/InkCanvasForClass/Resources/Illustrations/settingsv2-powerpoint-pagebtns-bottom.png differ diff --git a/InkCanvasForClass/Resources/Illustrations/settingsv2-powerpoint-pagebtns-side.png b/InkCanvasForClass/Resources/Illustrations/settingsv2-powerpoint-pagebtns-side.png new file mode 100644 index 00000000..b53e586e Binary files /dev/null and b/InkCanvasForClass/Resources/Illustrations/settingsv2-powerpoint-pagebtns-side.png differ diff --git a/InkCanvasForClass/Resources/Illustrations/wait-a-moment.png b/InkCanvasForClass/Resources/Illustrations/wait-a-moment.png new file mode 100644 index 00000000..0448f36c Binary files /dev/null and b/InkCanvasForClass/Resources/Illustrations/wait-a-moment.png differ diff --git a/Ink Canvas/Resources/PresentationExample/bottombar-dark.png b/InkCanvasForClass/Resources/PresentationExample/bottombar-dark.png similarity index 100% rename from Ink Canvas/Resources/PresentationExample/bottombar-dark.png rename to InkCanvasForClass/Resources/PresentationExample/bottombar-dark.png diff --git a/Ink Canvas/Resources/PresentationExample/bottombar-white.png b/InkCanvasForClass/Resources/PresentationExample/bottombar-white.png similarity index 100% rename from Ink Canvas/Resources/PresentationExample/bottombar-white.png rename to InkCanvasForClass/Resources/PresentationExample/bottombar-white.png diff --git a/Ink Canvas/Resources/PresentationExample/page.jpg b/InkCanvasForClass/Resources/PresentationExample/page.jpg similarity index 100% rename from Ink Canvas/Resources/PresentationExample/page.jpg rename to InkCanvasForClass/Resources/PresentationExample/page.jpg diff --git a/Ink Canvas/Resources/PresentationExample/sidebar-dark.png b/InkCanvasForClass/Resources/PresentationExample/sidebar-dark.png similarity index 100% rename from Ink Canvas/Resources/PresentationExample/sidebar-dark.png rename to InkCanvasForClass/Resources/PresentationExample/sidebar-dark.png diff --git a/Ink Canvas/Resources/PresentationExample/sidebar-white.png b/InkCanvasForClass/Resources/PresentationExample/sidebar-white.png similarity index 100% rename from Ink Canvas/Resources/PresentationExample/sidebar-white.png rename to InkCanvasForClass/Resources/PresentationExample/sidebar-white.png diff --git a/Ink Canvas/Resources/PresentationExample/toolbar.png b/InkCanvasForClass/Resources/PresentationExample/toolbar.png similarity index 100% rename from Ink Canvas/Resources/PresentationExample/toolbar.png rename to InkCanvasForClass/Resources/PresentationExample/toolbar.png diff --git a/Ink Canvas/Resources/SeewoImageDictionary.xaml b/InkCanvasForClass/Resources/SeewoImageDictionary.xaml similarity index 100% rename from Ink Canvas/Resources/SeewoImageDictionary.xaml rename to InkCanvasForClass/Resources/SeewoImageDictionary.xaml diff --git a/Ink Canvas/Resources/Settings.cs b/InkCanvasForClass/Resources/Settings.cs similarity index 57% rename from Ink Canvas/Resources/Settings.cs rename to InkCanvasForClass/Resources/Settings.cs index 8d9291f2..da16e8f3 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/InkCanvasForClass/Resources/Settings.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; using Newtonsoft.Json; namespace Ink_Canvas @@ -25,6 +23,32 @@ namespace Ink_Canvas public Startup Startup { get; set; } = new Startup(); [JsonProperty("randSettings")] public RandSettings RandSettings { get; set; } = new RandSettings(); + [JsonProperty("snapshot")] + public Snapshot Snapshot { get; set; } = new Snapshot(); + [JsonProperty("storage")] + public Storage Storage { get; set; } = new Storage(); + } + + public class Snapshot { + [JsonProperty("usingMagnificationAPI")] + public bool ScreenshotUsingMagnificationAPI { get; set; } = false; + [JsonProperty("copyScreenshotToClipboard")] + public bool CopyScreenshotToClipboard { get; set; } = true; + [JsonProperty("hideMainWinWhenScreenshot")] + public bool HideMainWinWhenScreenshot { get; set; } = true; + [JsonProperty("attachInkWhenScreenshot")] + public bool AttachInkWhenScreenshot { get; set; } = true; + [JsonProperty("onlySnapshotMaximizeWindow")] + public bool OnlySnapshotMaximizeWindow { get; set; } = false; + [JsonProperty("screenshotFileName")] + public string ScreenshotFileName { get; set; } = "Screenshot-[YYYY]-[MM]-[DD]-[HH]-[mm]-[ss].png"; + } + + public class Storage { + [JsonProperty("storageLocation")] + public string StorageLocation { get; set; } = "a-"; + [JsonProperty("userStorageLocation")] + public string UserStorageLocation { get; set; } = ""; } public class Canvas @@ -36,60 +60,46 @@ namespace Ink_Canvas [JsonProperty("inkAlpha")] public double InkAlpha { get; set; } = 255; [JsonProperty("isShowCursor")] - public bool IsShowCursor { get; set; } + public bool IsShowCursor { get; set; } = false; [JsonProperty("inkStyle")] - public int InkStyle { get; set; } + public int InkStyle { get; set; } = 0; [JsonProperty("eraserSize")] public int EraserSize { get; set; } = 2; [JsonProperty("eraserType")] - public int EraserType { get; set; } // 0 - 图标切换模式 1 - 面积擦 2 - 线条擦 + public int EraserType { get; set; } = 0; // 0 - 图标切换模式 1 - 面积擦 2 - 线条擦 [JsonProperty("eraserShapeType")] - public int EraserShapeType { get; set; } // 0 - 圆形擦 1 - 黑板擦 + public int EraserShapeType { get; set; } = 0; // 0 - 圆形擦 1 - 黑板擦 [JsonProperty("hideStrokeWhenSelecting")] public bool HideStrokeWhenSelecting { get; set; } = true; [JsonProperty("fitToCurve")] - public bool FitToCurve { get; set; } // 默认关闭原来的贝塞尔平滑 - [JsonProperty("useAdvancedBezierSmoothing")] - public bool UseAdvancedBezierSmoothing { get; set; } = true; // 默认启用高级贝塞尔曲线平滑 - [JsonProperty("useAsyncInkSmoothing")] - public bool UseAsyncInkSmoothing { get; set; } = true; // 默认启用异步墨迹平滑 - [JsonProperty("useHardwareAcceleration")] - public bool UseHardwareAcceleration { get; set; } = true; // 默认启用硬件加速 - [JsonProperty("inkSmoothingQuality")] - public int InkSmoothingQuality { get; set; } = 2; // 0-低质量高性能, 1-平衡, 2-高质量低性能,默认为高质量 - [JsonProperty("maxConcurrentSmoothingTasks")] - public int MaxConcurrentSmoothingTasks { get; set; } // 0表示自动检测CPU核心数 + public bool FitToCurve { get; set; } = true; [JsonProperty("clearCanvasAndClearTimeMachine")] - public bool ClearCanvasAndClearTimeMachine { get; set; } - [JsonProperty("enablePressureTouchMode")] - public bool EnablePressureTouchMode { get; set; } // 是否启用压感触屏模式 - [JsonProperty("disablePressure")] - public bool DisablePressure { get; set; } // 是否屏蔽压感 - [JsonProperty("autoStraightenLine")] - public bool AutoStraightenLine { get; set; } = true; // 是否启用直线自动拉直 - [JsonProperty("autoStraightenLineThreshold")] - public int AutoStraightenLineThreshold { get; set; } = 80; // 直线自动拉直的长度阈值(像素) - [JsonProperty("highPrecisionLineStraighten")] - public bool HighPrecisionLineStraighten { get; set; } = true; // 是否启用高精度直线拉直 - [JsonProperty("lineEndpointSnapping")] - public bool LineEndpointSnapping { get; set; } = true; // 是否启用直线端点吸附 - [JsonProperty("lineEndpointSnappingThreshold")] - public int LineEndpointSnappingThreshold { get; set; } = 15; // 直线端点吸附的距离阈值(像素) - + public bool ClearCanvasAndClearTimeMachine { get; set; } = false; + [Obsolete("已经使用多背景色“blackboardBackgroundColor”替换该选项")] [JsonProperty("usingWhiteboard")] public bool UsingWhiteboard { get; set; } - - [JsonProperty("customBackgroundColor")] - public string CustomBackgroundColor { get; set; } = "#162924"; - [JsonProperty("hyperbolaAsymptoteOption")] public OptionalOperation HyperbolaAsymptoteOption { get; set; } = OptionalOperation.Ask; - [JsonProperty("isCompressPicturesUploaded")] - public bool IsCompressPicturesUploaded { get; set; } - [JsonProperty("enablePalmEraser")] - public bool EnablePalmEraser { get; set; } = true; - [JsonProperty("clearCanvasAlsoClearImages")] - public bool ClearCanvasAlsoClearImages { get; set; } = true; + [JsonProperty("blackboardBackgroundColor")] + public BlackboardBackgroundColorEnum BlackboardBackgroundColor { get; set; } = + BlackboardBackgroundColorEnum.White; + [JsonProperty("blackboardBackgroundPattern")] + public BlackboardBackgroundPatternEnum BlackboardBackgroundPattern { get; set; } = + BlackboardBackgroundPatternEnum.None; + [JsonProperty("useDefaultBackgroundColorForEveryNewAddedBlackboardPage")] + public bool UseDefaultBackgroundColorForEveryNewAddedBlackboardPage { get; set; } = false; + [JsonProperty("useDefaultBackgroundPatternForEveryNewAddedBlackboardPage")] + public bool UseDefaultBackgroundPatternForEveryNewAddedBlackboardPage { get; set; } = false; + [JsonProperty("isEnableAutoConvertInkColorWhenBackgroundChanged")] + public bool IsEnableAutoConvertInkColorWhenBackgroundChanged { get; set; } = false; + [JsonProperty("ApplyScaleToStylusTip")] + public bool ApplyScaleToStylusTip { get; set; } = false; + [JsonProperty("onlyHitTestFullyContainedStrokes")] + public bool OnlyHitTestFullyContainedStrokes { get; set; } = false; + [JsonProperty("allowClickToSelectLockedStroke")] + public bool AllowClickToSelectLockedStroke { get; set; } = false; + [JsonProperty("selectionMethod")] + public int SelectionMethod { get; set; } = 0; } public enum OptionalOperation @@ -99,6 +109,23 @@ namespace Ink_Canvas Ask } + public enum BlackboardBackgroundColorEnum + { + GrayBlack, + BlackBoardGreen, + White, + BlueBlack, + EyeProtectionGreen, + RealBlack + } + + public enum BlackboardBackgroundPatternEnum + { + None, + Dots, + Grid + } + public class Gesture { [JsonIgnore] @@ -114,16 +141,25 @@ namespace Ink_Canvas [JsonProperty("AutoSwitchTwoFingerGesture")] public bool AutoSwitchTwoFingerGesture { get; set; } = true; [JsonProperty("isEnableTwoFingerRotation")] - public bool IsEnableTwoFingerRotation { get; set; } + public bool IsEnableTwoFingerRotation { get; set; } = false; [JsonProperty("isEnableTwoFingerRotationOnSelection")] - public bool IsEnableTwoFingerRotationOnSelection { get; set; } - } - - // 更新通道枚举 - public enum UpdateChannel - { - Release, - Beta + public bool IsEnableTwoFingerRotationOnSelection { get; set; } = false; + [JsonProperty("disableGestureEraser")] + public bool DisableGestureEraser { get; set; } = true; + [JsonProperty("defaultMultiPointHandWritingMode")] + public int DefaultMultiPointHandWritingMode { get; set; } = 2; + [JsonProperty("hideCursorWhenUsingTouchDevice")] + public bool HideCursorWhenUsingTouchDevice { get; set; } = true; + [JsonProperty("enableMouseGesture")] + public bool EnableMouseGesture { get; set; } = true; + [JsonProperty("enableMouseRightBtnGesture")] + public bool EnableMouseRightBtnGesture { get; set; } = true; + [JsonProperty("enableMouseWheelGesture")] + public bool EnableMouseWheelGesture { get; set; } = true; + [JsonProperty("windowsInkEraserButtonAction")] + public int WindowsInkEraserButtonAction { get; set; } = 2; + [JsonProperty("windowsInkBarrelButtonAction")] + public int WindowsInkBarrelButtonAction { get; set; } = 0; } public class Startup @@ -131,21 +167,23 @@ namespace Ink_Canvas [JsonProperty("isAutoUpdate")] public bool IsAutoUpdate { get; set; } = true; [JsonProperty("isAutoUpdateWithSilence")] - public bool IsAutoUpdateWithSilence { get; set; } + public bool IsAutoUpdateWithSilence { get; set; } = false; [JsonProperty("isAutoUpdateWithSilenceStartTime")] - public string AutoUpdateWithSilenceStartTime { get; set; } = "06:00"; + public string AutoUpdateWithSilenceStartTime { get; set; } = "00:00"; [JsonProperty("isAutoUpdateWithSilenceEndTime")] - public string AutoUpdateWithSilenceEndTime { get; set; } = "22:00"; - [JsonProperty("updateChannel")] - public UpdateChannel UpdateChannel { get; set; } = UpdateChannel.Release; - [JsonProperty("skippedVersion")] - public string SkippedVersion { get; set; } = ""; + public string AutoUpdateWithSilenceEndTime { get; set; } = "00:00"; + [JsonProperty("isEnableNibMode")] - public bool IsEnableNibMode { get; set; } + public bool IsEnableNibMode { get; set; } = false; + /* + [JsonProperty("isAutoHideCanvas")] + public bool IsAutoHideCanvas { get; set; } = true; + [JsonProperty("isAutoEnterModeFinger")] + public bool IsAutoEnterModeFinger { get; set; } = false;*/ [JsonProperty("isFoldAtStartup")] - public bool IsFoldAtStartup { get; set; } - [JsonProperty("crashAction")] - public int CrashAction { get; set; } + public bool IsFoldAtStartup { get; set; } = false; + [JsonProperty("enableWindowChromeRendering")] + public bool EnableWindowChromeRendering { get; set; } = false; } public class Appearance @@ -153,15 +191,13 @@ namespace Ink_Canvas [JsonProperty("isEnableDisPlayNibModeToggler")] public bool IsEnableDisPlayNibModeToggler { get; set; } = true; [JsonProperty("isColorfulViewboxFloatingBar")] - public bool IsColorfulViewboxFloatingBar { get; set; } + public bool IsColorfulViewboxFloatingBar { get; set; } = false; // [JsonProperty("enableViewboxFloatingBarScaleTransform")] // public bool EnableViewboxFloatingBarScaleTransform { get; set; } = false; [JsonProperty("viewboxFloatingBarScaleTransformValue")] public double ViewboxFloatingBarScaleTransformValue { get; set; } = 1.0; [JsonProperty("floatingBarImg")] - public int FloatingBarImg { get; set; } - [JsonProperty("customFloatingBarImgs")] - public List CustomFloatingBarImgs { get; set; } = new List(); + public int FloatingBarImg { get; set; } = 0; [JsonProperty("viewboxFloatingBarOpacityValue")] public double ViewboxFloatingBarOpacityValue { get; set; } = 1.0; [JsonProperty("enableTrayIcon")] @@ -169,7 +205,7 @@ namespace Ink_Canvas [JsonProperty("viewboxFloatingBarOpacityInPPTValue")] public double ViewboxFloatingBarOpacityInPPTValue { get; set; } = 0.5; [JsonProperty("enableViewboxBlackBoardScaleTransform")] - public bool EnableViewboxBlackBoardScaleTransform { get; set; } + public bool EnableViewboxBlackBoardScaleTransform { get; set; } = false; [JsonProperty("isTransparentButtonBackground")] public bool IsTransparentButtonBackground { get; set; } = true; [JsonProperty("isShowExitButton")] @@ -181,11 +217,11 @@ namespace Ink_Canvas [JsonProperty("enableChickenSoupInWhiteboardMode")] public bool EnableChickenSoupInWhiteboardMode { get; set; } = true; [JsonProperty("isShowHideControlButton")] - public bool IsShowHideControlButton { get; set; } + public bool IsShowHideControlButton { get; set; } = false; [JsonProperty("unFoldButtonImageType")] - public int UnFoldButtonImageType { get; set; } + public int UnFoldButtonImageType { get; set; } = 0; [JsonProperty("isShowLRSwitchButton")] - public bool IsShowLRSwitchButton { get; set; } + public bool IsShowLRSwitchButton { get; set; } = false; [JsonProperty("isShowQuickPanel")] public bool IsShowQuickPanel { get; set; } = true; [JsonProperty("chickenSoupSource")] @@ -193,7 +229,15 @@ namespace Ink_Canvas [JsonProperty("isShowModeFingerToggleSwitch")] public bool IsShowModeFingerToggleSwitch { get; set; } = true; [JsonProperty("theme")] - public int Theme { get; set; } + public int Theme { get; set; } = 0; + [JsonProperty("floatingBarButtonLabelVisibility")] + public bool FloatingBarButtonLabelVisibility = true; + [JsonProperty("floatingBarIconsVisibility")] + public string FloatingBarIconsVisibility = "1111111111"; + [JsonProperty("eraserButtonsVisibility")] + public int EraserButtonsVisibility = 0; + [JsonProperty("onlyDisplayEraserBtn")] + public bool OnlyDisplayEraserBtn = false; } public class PowerPointSettings @@ -209,11 +253,11 @@ namespace Ink_Canvas // 0居中,+就是往上,-就是往下 [JsonProperty("pptLSButtonPosition")] - public int PPTLSButtonPosition { get; set; } + public int PPTLSButtonPosition { get; set; } = 0; // 0居中,+就是往上,-就是往下 [JsonProperty("pptRSButtonPosition")] - public int PPTRSButtonPosition { get; set; } + public int PPTRSButtonPosition { get; set; } = 0; [JsonProperty("pptSButtonsOption")] public int PPTSButtonsOption { get; set; } = 221; @@ -232,28 +276,27 @@ namespace Ink_Canvas public bool IsShowCanvasAtNewSlideShow { get; set; } = true; [JsonProperty("isNoClearStrokeOnSelectWhenInPowerPoint")] public bool IsNoClearStrokeOnSelectWhenInPowerPoint { get; set; } = true; - [JsonProperty("isShowStrokeOnSelectInPowerPoint")] - public bool IsShowStrokeOnSelectInPowerPoint { get; set; } + [JsonProperty("isAutoSaveStrokesInPowerPoint")] public bool IsAutoSaveStrokesInPowerPoint { get; set; } = true; [JsonProperty("isAutoSaveScreenShotInPowerPoint")] - public bool IsAutoSaveScreenShotInPowerPoint { get; set; } + public bool IsAutoSaveScreenShotInPowerPoint { get; set; } = false; [JsonProperty("isNotifyPreviousPage")] - public bool IsNotifyPreviousPage { get; set; } + public bool IsNotifyPreviousPage { get; set; } = false; [JsonProperty("isNotifyHiddenPage")] public bool IsNotifyHiddenPage { get; set; } = true; [JsonProperty("isNotifyAutoPlayPresentation")] public bool IsNotifyAutoPlayPresentation { get; set; } = true; [JsonProperty("isEnableTwoFingerGestureInPresentationMode")] - public bool IsEnableTwoFingerGestureInPresentationMode { get; set; } - [JsonProperty("isEnableFingerGestureSlideShowControl")] - public bool IsEnableFingerGestureSlideShowControl { get; set; } = true; + public bool IsEnableTwoFingerGestureInPresentationMode { get; set; } = false; [JsonProperty("isSupportWPS")] - public bool IsSupportWPS { get; set; } - [JsonProperty("enableWppProcessKill")] - public bool EnableWppProcessKill { get; set; } = true; - [JsonProperty("isAlwaysGoToFirstPageOnReenter")] - public bool IsAlwaysGoToFirstPageOnReenter { get; set; } + public bool IsSupportWPS { get; set; } = true; + + [JsonProperty("registryShowSlideShowToolbar")] + public bool RegistryShowSlideShowToolbar { get; set; } = false; + + [JsonProperty("registryShowBlackScreenLastSlideShow")] + public bool RegistryShowBlackScreenLastSlideShow { get; set; } = false; } public class Automation @@ -277,132 +320,126 @@ namespace Ink_Canvas || IsAutoFoldInYiYunVisualPresenter || IsAutoFoldInMaxHubWhiteboard; - [JsonProperty("isAutoEnterAnnotationModeWhenExitFoldMode")] - public bool IsAutoEnterAnnotationModeWhenExitFoldMode { get; set; } - [JsonProperty("isAutoFoldInEasiNote")] - public bool IsAutoFoldInEasiNote { get; set; } + public bool IsAutoFoldInEasiNote { get; set; } = false; [JsonProperty("isAutoFoldInEasiNoteIgnoreDesktopAnno")] - public bool IsAutoFoldInEasiNoteIgnoreDesktopAnno { get; set; } + public bool IsAutoFoldInEasiNoteIgnoreDesktopAnno { get; set; } = false; [JsonProperty("isAutoFoldInEasiCamera")] - public bool IsAutoFoldInEasiCamera { get; set; } + public bool IsAutoFoldInEasiCamera { get; set; } = false; [JsonProperty("isAutoFoldInEasiNote3")] - public bool IsAutoFoldInEasiNote3 { get; set; } + public bool IsAutoFoldInEasiNote3 { get; set; } = false; [JsonProperty("isAutoFoldInEasiNote3C")] - public bool IsAutoFoldInEasiNote3C { get; set; } + public bool IsAutoFoldInEasiNote3C { get; set; } = false; [JsonProperty("isAutoFoldInEasiNote5C")] - public bool IsAutoFoldInEasiNote5C { get; set; } + public bool IsAutoFoldInEasiNote5C { get; set; } = false; [JsonProperty("isAutoFoldInSeewoPincoTeacher")] - public bool IsAutoFoldInSeewoPincoTeacher { get; set; } + public bool IsAutoFoldInSeewoPincoTeacher { get; set; } = false; [JsonProperty("isAutoFoldInHiteTouchPro")] - public bool IsAutoFoldInHiteTouchPro { get; set; } + public bool IsAutoFoldInHiteTouchPro { get; set; } = false; [JsonProperty("isAutoFoldInHiteLightBoard")] - public bool IsAutoFoldInHiteLightBoard { get; set; } + public bool IsAutoFoldInHiteLightBoard { get; set; } = false; [JsonProperty("isAutoFoldInHiteCamera")] - public bool IsAutoFoldInHiteCamera { get; set; } + public bool IsAutoFoldInHiteCamera { get; set; } = false; [JsonProperty("isAutoFoldInWxBoardMain")] - public bool IsAutoFoldInWxBoardMain { get; set; } + public bool IsAutoFoldInWxBoardMain { get; set; } = false; /* [JsonProperty("isAutoFoldInZySmartBoard")] public bool IsAutoFoldInZySmartBoard { get; set; } = false; */ [JsonProperty("isAutoFoldInOldZyBoard")] - public bool IsAutoFoldInOldZyBoard { get; set; } + public bool IsAutoFoldInOldZyBoard { get; set; } = false; [JsonProperty("isAutoFoldInMSWhiteboard")] - public bool IsAutoFoldInMSWhiteboard { get; set; } + public bool IsAutoFoldInMSWhiteboard { get; set; } = false; [JsonProperty("isAutoFoldInAdmoxWhiteboard")] - public bool IsAutoFoldInAdmoxWhiteboard { get; set; } + public bool IsAutoFoldInAdmoxWhiteboard { get; set; } = false; [JsonProperty("isAutoFoldInAdmoxBooth")] - public bool IsAutoFoldInAdmoxBooth { get; set; } + public bool IsAutoFoldInAdmoxBooth { get; set; } = false; [JsonProperty("isAutoFoldInQPoint")] - public bool IsAutoFoldInQPoint { get; set; } + public bool IsAutoFoldInQPoint { get; set; } = false; [JsonProperty("isAutoFoldInYiYunVisualPresenter")] - public bool IsAutoFoldInYiYunVisualPresenter { get; set; } + public bool IsAutoFoldInYiYunVisualPresenter { get; set; } = false; [JsonProperty("isAutoFoldInMaxHubWhiteboard")] - public bool IsAutoFoldInMaxHubWhiteboard { get; set; } + public bool IsAutoFoldInMaxHubWhiteboard { get; set; } = false; [JsonProperty("isAutoFoldInPPTSlideShow")] - public bool IsAutoFoldInPPTSlideShow { get; set; } - - [JsonProperty("isAutoFoldAfterPPTSlideShow")] - public bool IsAutoFoldAfterPPTSlideShow { get; set; } + public bool IsAutoFoldInPPTSlideShow { get; set; } = false; [JsonProperty("isAutoKillPptService")] - public bool IsAutoKillPptService { get; set; } + public bool IsAutoKillPptService { get; set; } = false; [JsonProperty("isAutoKillEasiNote")] - public bool IsAutoKillEasiNote { get; set; } + public bool IsAutoKillEasiNote { get; set; } = false; [JsonProperty("isAutoKillHiteAnnotation")] - public bool IsAutoKillHiteAnnotation { get; set; } + public bool IsAutoKillHiteAnnotation { get; set; } = false; [JsonProperty("isAutoKillVComYouJiao")] - public bool IsAutoKillVComYouJiao { get; set; } + public bool IsAutoKillVComYouJiao { get; set; } = false; [JsonProperty("isAutoKillSeewoLauncher2DesktopAnnotation")] - public bool IsAutoKillSeewoLauncher2DesktopAnnotation { get; set; } + public bool IsAutoKillSeewoLauncher2DesktopAnnotation { get; set; } = false; [JsonProperty("isAutoKillInkCanvas")] - public bool IsAutoKillInkCanvas { get; set; } + public bool IsAutoKillInkCanvas { get; set; } = false; [JsonProperty("isAutoKillICA")] - public bool IsAutoKillICA { get; set; } + public bool IsAutoKillICA { get; set; } = false; [JsonProperty("isAutoKillIDT")] - public bool IsAutoKillIDT { get; set; } + public bool IsAutoKillIDT { get; set; } = true; [JsonProperty("isSaveScreenshotsInDateFolders")] - public bool IsSaveScreenshotsInDateFolders { get; set; } + public bool IsSaveScreenshotsInDateFolders { get; set; } = false; [JsonProperty("isAutoSaveStrokesAtScreenshot")] - public bool IsAutoSaveStrokesAtScreenshot { get; set; } + public bool IsAutoSaveStrokesAtScreenshot { get; set; } = false; [JsonProperty("isAutoSaveStrokesAtClear")] - public bool IsAutoSaveStrokesAtClear { get; set; } + public bool IsAutoSaveStrokesAtClear { get; set; } = false; [JsonProperty("isAutoClearWhenExitingWritingMode")] - public bool IsAutoClearWhenExitingWritingMode { get; set; } + public bool IsAutoClearWhenExitingWritingMode { get; set; } = false; [JsonProperty("minimumAutomationStrokeNumber")] - public int MinimumAutomationStrokeNumber { get; set; } + public int MinimumAutomationStrokeNumber { get; set; } = 0; [JsonProperty("autoSavedStrokesLocation")] - public string AutoSavedStrokesLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves"); + public string AutoSavedStrokesLocation = @"D:\Ink Canvas"; [JsonProperty("autoDelSavedFiles")] - public bool AutoDelSavedFiles; + public bool AutoDelSavedFiles = false; [JsonProperty("autoDelSavedFilesDaysThreshold")] public int AutoDelSavedFilesDaysThreshold = 15; - - [JsonProperty("isSaveFullPageStrokes")] - public bool IsSaveFullPageStrokes; - [JsonProperty("isAutoEnterAnnotationAfterKillHite")] - public bool IsAutoEnterAnnotationAfterKillHite { get; set; } + [JsonProperty("isEnableLimitAutoSaveAmount")] + public bool IsEnableLimitAutoSaveAmount { get; set; } = false; + + [JsonProperty("limitAutoSaveAmount")] + public int LimitAutoSaveAmount { get; set; } = 3; } public class Advanced { [JsonProperty("isSpecialScreen")] - public bool IsSpecialScreen { get; set; } + public bool IsSpecialScreen { get; set; } = false; [JsonProperty("isQuadIR")] - public bool IsQuadIR { get; set; } + public bool IsQuadIR { get; set; } = false; [JsonProperty("touchMultiplier")] public double TouchMultiplier { get; set; } = 0.25; @@ -414,43 +451,45 @@ namespace Ink_Canvas public int FingerModeBoundsWidth { get; set; } = 30; [JsonProperty("eraserBindTouchMultiplier")] - public bool EraserBindTouchMultiplier { get; set; } + public bool EraserBindTouchMultiplier { get; set; } = false; + + [JsonProperty("nibModeBoundsWidthThresholdValue")] + public double NibModeBoundsWidthThresholdValue { get; set; } = 2.5; + + [JsonProperty("fingerModeBoundsWidthThresholdValue")] + public double FingerModeBoundsWidthThresholdValue { get; set; } = 2.5; + + [JsonProperty("nibModeBoundsWidthEraserSize")] + public double NibModeBoundsWidthEraserSize { get; set; } = 0.8; + + [JsonProperty("fingerModeBoundsWidthEraserSize")] + public double FingerModeBoundsWidthEraserSize { get; set; } = 0.8; [JsonProperty("isLogEnabled")] public bool IsLogEnabled { get; set; } = true; - - [JsonProperty("isSaveLogByDate")] - public bool IsSaveLogByDate { get; set; } = true; [JsonProperty("isEnableFullScreenHelper")] - public bool IsEnableFullScreenHelper { get; set; } + public bool IsEnableFullScreenHelper { get; set; } = false; [JsonProperty("isEnableEdgeGestureUtil")] - public bool IsEnableEdgeGestureUtil { get; set; } + public bool IsEnableEdgeGestureUtil { get; set; } = false; [JsonProperty("edgeGestureUtilOnlyAffectBlackboardMode")] - public bool EdgeGestureUtilOnlyAffectBlackboardMode { get; set; } + public bool EdgeGestureUtilOnlyAffectBlackboardMode { get; set; } = false; [JsonProperty("isEnableForceFullScreen")] - public bool IsEnableForceFullScreen { get; set; } + public bool IsEnableForceFullScreen { get; set; } = false; [JsonProperty("isEnableResolutionChangeDetection")] - public bool IsEnableResolutionChangeDetection { get; set; } + public bool IsEnableResolutionChangeDetection { get; set; } = false; [JsonProperty("isEnableDPIChangeDetection")] - public bool IsEnableDPIChangeDetection { get; set; } + public bool IsEnableDPIChangeDetection { get; set; } = false; - [JsonProperty("isSecondConfirmWhenShutdownApp")] - public bool IsSecondConfirmWhenShutdownApp { get; set; } - - [JsonProperty("isEnableAvoidFullScreenHelper")] - public bool IsEnableAvoidFullScreenHelper { get; set; } - - [JsonProperty("isAutoBackupBeforeUpdate")] - public bool IsAutoBackupBeforeUpdate { get; set; } = true; - - [JsonProperty("isNoFocusMode")] - public bool IsNoFocusMode { get; set; } = true; + [JsonProperty("isDisableCloseWindow")] + public bool IsDisableCloseWindow { get; set; } = true; + [JsonProperty("enableForceTopMost")] + public bool EnableForceTopMost { get; set; } = false; } public class InkToShape @@ -458,69 +497,23 @@ namespace Ink_Canvas [JsonProperty("isInkToShapeEnabled")] public bool IsInkToShapeEnabled { get; set; } = true; [JsonProperty("isInkToShapeNoFakePressureRectangle")] - public bool IsInkToShapeNoFakePressureRectangle { get; set; } + public bool IsInkToShapeNoFakePressureRectangle { get; set; } = false; [JsonProperty("isInkToShapeNoFakePressureTriangle")] - public bool IsInkToShapeNoFakePressureTriangle { get; set; } + public bool IsInkToShapeNoFakePressureTriangle { get; set; } = false; [JsonProperty("isInkToShapeTriangle")] public bool IsInkToShapeTriangle { get; set; } = true; [JsonProperty("isInkToShapeRectangle")] public bool IsInkToShapeRectangle { get; set; } = true; [JsonProperty("isInkToShapeRounded")] public bool IsInkToShapeRounded { get; set; } = true; - [JsonProperty("lineStraightenSensitivity")] - public double LineStraightenSensitivity { get; set; } = 0.20; // 直线检测灵敏度,值越小越严格(0.05-2.0) } public class RandSettings { [JsonProperty("displayRandWindowNamesInputBtn")] - public bool DisplayRandWindowNamesInputBtn { get; set; } + public bool DisplayRandWindowNamesInputBtn { get; set; } = false; [JsonProperty("randWindowOnceCloseLatency")] public double RandWindowOnceCloseLatency { get; set; } = 2.5; [JsonProperty("randWindowOnceMaxStudents")] public int RandWindowOnceMaxStudents { get; set; } = 10; - [JsonProperty("showRandomAndSingleDraw")] - public bool ShowRandomAndSingleDraw { get; set; } = true; - [JsonProperty("directCallCiRand")] - public bool DirectCallCiRand { get; set; } - [JsonProperty("selectedBackgroundIndex")] - public int SelectedBackgroundIndex { get; set; } - [JsonProperty("customPickNameBackgrounds")] - public List CustomPickNameBackgrounds { get; set; } = new List(); } - - public class CustomPickNameBackground - { - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("filePath")] - public string FilePath { get; set; } - - public CustomPickNameBackground(string name, string filePath) - { - Name = name; - FilePath = filePath; - } - - // 用于JSON序列化 - public CustomPickNameBackground() { } - } - - public class CustomFloatingBarIcon - { - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("filePath")] - public string FilePath { get; set; } - - public CustomFloatingBarIcon(string name, string filePath) - { - Name = name; - FilePath = filePath; - } - - // 用于JSON序列化 - public CustomFloatingBarIcon() { } - } -} +} \ No newline at end of file diff --git a/Ink Canvas/Resources/Styles/Dark.xaml b/InkCanvasForClass/Resources/Styles/Dark.xaml similarity index 100% rename from Ink Canvas/Resources/Styles/Dark.xaml rename to InkCanvasForClass/Resources/Styles/Dark.xaml diff --git a/Ink Canvas/Resources/Styles/Light.xaml b/InkCanvasForClass/Resources/Styles/Light.xaml similarity index 100% rename from Ink Canvas/Resources/Styles/Light.xaml rename to InkCanvasForClass/Resources/Styles/Light.xaml diff --git a/Ink Canvas/Resources/TimerDownNotice.wav b/InkCanvasForClass/Resources/TimerDownNotice.wav similarity index 100% rename from Ink Canvas/Resources/TimerDownNotice.wav rename to InkCanvasForClass/Resources/TimerDownNotice.wav diff --git a/InkCanvasForClass/Resources/contributors.png b/InkCanvasForClass/Resources/contributors.png new file mode 100644 index 00000000..989ba357 Binary files /dev/null and b/InkCanvasForClass/Resources/contributors.png differ diff --git a/Ink Canvas/Resources/hatsune-miku1.png b/InkCanvasForClass/Resources/hatsune-miku1.png similarity index 100% rename from Ink Canvas/Resources/hatsune-miku1.png rename to InkCanvasForClass/Resources/hatsune-miku1.png diff --git a/InkCanvasForClass/Resources/icc.ico b/InkCanvasForClass/Resources/icc.ico new file mode 100644 index 00000000..5cf30b07 Binary files /dev/null and b/InkCanvasForClass/Resources/icc.ico differ diff --git a/Ink Canvas/Resources/new-icons/blackboard.png b/InkCanvasForClass/Resources/new-icons/blackboard.png similarity index 100% rename from Ink Canvas/Resources/new-icons/blackboard.png rename to InkCanvasForClass/Resources/new-icons/blackboard.png diff --git a/Ink Canvas/Resources/new-icons/checked-black.png b/InkCanvasForClass/Resources/new-icons/checked-black.png similarity index 100% rename from Ink Canvas/Resources/new-icons/checked-black.png rename to InkCanvasForClass/Resources/new-icons/checked-black.png diff --git a/Ink Canvas/Resources/new-icons/checked-white.png b/InkCanvasForClass/Resources/new-icons/checked-white.png similarity index 100% rename from Ink Canvas/Resources/new-icons/checked-white.png rename to InkCanvasForClass/Resources/new-icons/checked-white.png diff --git a/Ink Canvas/Resources/new-icons/chevron-left.png b/InkCanvasForClass/Resources/new-icons/chevron-left.png similarity index 100% rename from Ink Canvas/Resources/new-icons/chevron-left.png rename to InkCanvasForClass/Resources/new-icons/chevron-left.png diff --git a/Ink Canvas/Resources/new-icons/circle-eraser-lined.png b/InkCanvasForClass/Resources/new-icons/circle-eraser-lined.png similarity index 100% rename from Ink Canvas/Resources/new-icons/circle-eraser-lined.png rename to InkCanvasForClass/Resources/new-icons/circle-eraser-lined.png diff --git a/Ink Canvas/Resources/new-icons/circle-eraser-solid.png b/InkCanvasForClass/Resources/new-icons/circle-eraser-solid.png similarity index 100% rename from Ink Canvas/Resources/new-icons/circle-eraser-solid.png rename to InkCanvasForClass/Resources/new-icons/circle-eraser-solid.png diff --git a/Ink Canvas/Resources/new-icons/close-white.png b/InkCanvasForClass/Resources/new-icons/close-white.png similarity index 100% rename from Ink Canvas/Resources/new-icons/close-white.png rename to InkCanvasForClass/Resources/new-icons/close-white.png diff --git a/Ink Canvas/Resources/new-icons/cursor-clear.png b/InkCanvasForClass/Resources/new-icons/cursor-clear.png similarity index 100% rename from Ink Canvas/Resources/new-icons/cursor-clear.png rename to InkCanvasForClass/Resources/new-icons/cursor-clear.png diff --git a/Ink Canvas/Resources/new-icons/cursor-lined.png b/InkCanvasForClass/Resources/new-icons/cursor-lined.png similarity index 100% rename from Ink Canvas/Resources/new-icons/cursor-lined.png rename to InkCanvasForClass/Resources/new-icons/cursor-lined.png diff --git a/Ink Canvas/Resources/new-icons/cursor-solid.png b/InkCanvasForClass/Resources/new-icons/cursor-solid.png similarity index 100% rename from Ink Canvas/Resources/new-icons/cursor-solid.png rename to InkCanvasForClass/Resources/new-icons/cursor-solid.png diff --git a/Ink Canvas/Resources/new-icons/end-slides-show.png b/InkCanvasForClass/Resources/new-icons/end-slides-show.png similarity index 100% rename from Ink Canvas/Resources/new-icons/end-slides-show.png rename to InkCanvasForClass/Resources/new-icons/end-slides-show.png diff --git a/Ink Canvas/Resources/new-icons/eraser-lined.png b/InkCanvasForClass/Resources/new-icons/eraser-lined.png similarity index 100% rename from Ink Canvas/Resources/new-icons/eraser-lined.png rename to InkCanvasForClass/Resources/new-icons/eraser-lined.png diff --git a/Ink Canvas/Resources/new-icons/eraser-solid.png b/InkCanvasForClass/Resources/new-icons/eraser-solid.png similarity index 100% rename from Ink Canvas/Resources/new-icons/eraser-solid.png rename to InkCanvasForClass/Resources/new-icons/eraser-solid.png diff --git a/Ink Canvas/Resources/new-icons/eye-off.png b/InkCanvasForClass/Resources/new-icons/eye-off.png similarity index 100% rename from Ink Canvas/Resources/new-icons/eye-off.png rename to InkCanvasForClass/Resources/new-icons/eye-off.png diff --git a/Ink Canvas/Resources/new-icons/eye.png b/InkCanvasForClass/Resources/new-icons/eye.png similarity index 100% rename from Ink Canvas/Resources/new-icons/eye.png rename to InkCanvasForClass/Resources/new-icons/eye.png diff --git a/InkCanvasForClass/Resources/new-icons/gesture-enabled.png b/InkCanvasForClass/Resources/new-icons/gesture-enabled.png new file mode 100644 index 00000000..afbf60d6 Binary files /dev/null and b/InkCanvasForClass/Resources/new-icons/gesture-enabled.png differ diff --git a/InkCanvasForClass/Resources/new-icons/gesture.png b/InkCanvasForClass/Resources/new-icons/gesture.png new file mode 100644 index 00000000..c0b72600 Binary files /dev/null and b/InkCanvasForClass/Resources/new-icons/gesture.png differ diff --git a/Ink Canvas/Resources/new-icons/grid.png b/InkCanvasForClass/Resources/new-icons/grid.png similarity index 100% rename from Ink Canvas/Resources/new-icons/grid.png rename to InkCanvasForClass/Resources/new-icons/grid.png diff --git a/Ink Canvas/Resources/new-icons/hand-move.png b/InkCanvasForClass/Resources/new-icons/hand-move.png similarity index 100% rename from Ink Canvas/Resources/new-icons/hand-move.png rename to InkCanvasForClass/Resources/new-icons/hand-move.png diff --git a/Ink Canvas/Resources/new-icons/highlighter-white.png b/InkCanvasForClass/Resources/new-icons/highlighter-white.png similarity index 100% rename from Ink Canvas/Resources/new-icons/highlighter-white.png rename to InkCanvasForClass/Resources/new-icons/highlighter-white.png diff --git a/Ink Canvas/Resources/new-icons/lasso-select-lined.png b/InkCanvasForClass/Resources/new-icons/lasso-select-lined.png similarity index 100% rename from Ink Canvas/Resources/new-icons/lasso-select-lined.png rename to InkCanvasForClass/Resources/new-icons/lasso-select-lined.png diff --git a/Ink Canvas/Resources/new-icons/lasso-select-solid.png b/InkCanvasForClass/Resources/new-icons/lasso-select-solid.png similarity index 100% rename from Ink Canvas/Resources/new-icons/lasso-select-solid.png rename to InkCanvasForClass/Resources/new-icons/lasso-select-solid.png diff --git a/Ink Canvas/Resources/new-icons/multi-touch.png b/InkCanvasForClass/Resources/new-icons/multi-touch.png similarity index 100% rename from Ink Canvas/Resources/new-icons/multi-touch.png rename to InkCanvasForClass/Resources/new-icons/multi-touch.png diff --git a/Ink Canvas/Resources/new-icons/osu-lazer-triangles.png b/InkCanvasForClass/Resources/new-icons/osu-lazer-triangles.png similarity index 100% rename from Ink Canvas/Resources/new-icons/osu-lazer-triangles.png rename to InkCanvasForClass/Resources/new-icons/osu-lazer-triangles.png diff --git a/Ink Canvas/Resources/new-icons/pen-lined.png b/InkCanvasForClass/Resources/new-icons/pen-lined.png similarity index 100% rename from Ink Canvas/Resources/new-icons/pen-lined.png rename to InkCanvasForClass/Resources/new-icons/pen-lined.png diff --git a/Ink Canvas/Resources/new-icons/pen-solid.png b/InkCanvasForClass/Resources/new-icons/pen-solid.png similarity index 100% rename from Ink Canvas/Resources/new-icons/pen-solid.png rename to InkCanvasForClass/Resources/new-icons/pen-solid.png diff --git a/Ink Canvas/Resources/new-icons/pen-white.png b/InkCanvasForClass/Resources/new-icons/pen-white.png similarity index 100% rename from Ink Canvas/Resources/new-icons/pen-white.png rename to InkCanvasForClass/Resources/new-icons/pen-white.png diff --git a/Ink Canvas/Resources/new-icons/redo.png b/InkCanvasForClass/Resources/new-icons/redo.png similarity index 100% rename from Ink Canvas/Resources/new-icons/redo.png rename to InkCanvasForClass/Resources/new-icons/redo.png diff --git a/Ink Canvas/Resources/new-icons/rotate.png b/InkCanvasForClass/Resources/new-icons/rotate.png similarity index 100% rename from Ink Canvas/Resources/new-icons/rotate.png rename to InkCanvasForClass/Resources/new-icons/rotate.png diff --git a/Ink Canvas/Resources/new-icons/shapes.png b/InkCanvasForClass/Resources/new-icons/shapes.png similarity index 100% rename from Ink Canvas/Resources/new-icons/shapes.png rename to InkCanvasForClass/Resources/new-icons/shapes.png diff --git a/Ink Canvas/Resources/new-icons/trash.png b/InkCanvasForClass/Resources/new-icons/trash.png similarity index 100% rename from Ink Canvas/Resources/new-icons/trash.png rename to InkCanvasForClass/Resources/new-icons/trash.png diff --git a/Ink Canvas/Resources/new-icons/undo.png b/InkCanvasForClass/Resources/new-icons/undo.png similarity index 100% rename from Ink Canvas/Resources/new-icons/undo.png rename to InkCanvasForClass/Resources/new-icons/undo.png diff --git a/Ink Canvas/Resources/new-icons/unfold-chevron.png b/InkCanvasForClass/Resources/new-icons/unfold-chevron.png similarity index 100% rename from Ink Canvas/Resources/new-icons/unfold-chevron.png rename to InkCanvasForClass/Resources/new-icons/unfold-chevron.png diff --git a/Ink Canvas/Resources/new-icons/zoom.png b/InkCanvasForClass/Resources/new-icons/zoom.png similarity index 100% rename from Ink Canvas/Resources/new-icons/zoom.png rename to InkCanvasForClass/Resources/new-icons/zoom.png diff --git a/InkCanvasForClass/Resources/qrcodes.png b/InkCanvasForClass/Resources/qrcodes.png new file mode 100644 index 00000000..918dd1df Binary files /dev/null and b/InkCanvasForClass/Resources/qrcodes.png differ diff --git a/InkCanvasForClass/UIAccessDLL_x64.dll b/InkCanvasForClass/UIAccessDLL_x64.dll new file mode 100644 index 00000000..9edc84b8 Binary files /dev/null and b/InkCanvasForClass/UIAccessDLL_x64.dll differ diff --git a/InkCanvasForClass/UIAccessDLL_x86.dll b/InkCanvasForClass/UIAccessDLL_x86.dll new file mode 100644 index 00000000..48fe7e3e Binary files /dev/null and b/InkCanvasForClass/UIAccessDLL_x86.dll differ diff --git a/Ink Canvas/Windows/CountdownTimerWindow.xaml b/InkCanvasForClass/Windows/CountdownTimerWindow.xaml similarity index 100% rename from Ink Canvas/Windows/CountdownTimerWindow.xaml rename to InkCanvasForClass/Windows/CountdownTimerWindow.xaml diff --git a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs b/InkCanvasForClass/Windows/CountdownTimerWindow.xaml.cs similarity index 92% rename from Ink Canvas/Windows/CountdownTimerWindow.xaml.cs rename to InkCanvasForClass/Windows/CountdownTimerWindow.xaml.cs index c6528338..a51f2626 100644 --- a/Ink Canvas/Windows/CountdownTimerWindow.xaml.cs +++ b/InkCanvasForClass/Windows/CountdownTimerWindow.xaml.cs @@ -1,17 +1,12 @@ -using System; -using System.ComponentModel; +using Ink_Canvas.Helpers; +using System; using System.Media; +using System.Runtime.InteropServices; using System.Timers; using System.Windows; -using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; -using Application = System.Windows.Application; -using MouseEventArgs = System.Windows.Input.MouseEventArgs; -using Timer = System.Timers.Timer; namespace Ink_Canvas { @@ -57,7 +52,7 @@ namespace Ink_Canvas TextBlockSecond.Text = "00"; timer.Stop(); isTimerRunning = false; - SymbolIconStart.Symbol = Symbol.Play; + SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Play; BtnStartCover.Visibility = Visibility.Visible; TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F")); BorderStopTime.Visibility = Visibility.Collapsed; @@ -76,16 +71,16 @@ namespace Ink_Canvas SoundPlayer player = new SoundPlayer(); - int hour; + int hour = 0; int minute = 1; - int second; + int second = 0; int totalSeconds = 60; DateTime startTime = DateTime.Now; DateTime pauseTime = DateTime.Now; - bool isTimerRunning; - bool isPaused; + bool isTimerRunning = false; + bool isPaused = false; Timer timer = new Timer(); @@ -208,12 +203,12 @@ namespace Ink_Canvas if (WindowState == WindowState.Normal) { WindowState = WindowState.Maximized; - SymbolIconFullscreen.Symbol = Symbol.BackToWindow; + SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.BackToWindow; } else { WindowState = WindowState.Normal; - SymbolIconFullscreen.Symbol = Symbol.FullScreen; + SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.FullScreen; } } @@ -228,6 +223,7 @@ namespace Ink_Canvas BtnStartCover.Visibility = Visibility.Collapsed; BorderStopTime.Visibility = Visibility.Collapsed; TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F")); + return; } else if (isTimerRunning && isPaused) { @@ -238,7 +234,7 @@ namespace Ink_Canvas BtnStartCover.Visibility = Visibility.Collapsed; BorderStopTime.Visibility = Visibility.Collapsed; TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F")); - SymbolIconStart.Symbol = Symbol.Play; + SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Play; isTimerRunning = false; timer.Stop(); isPaused = false; @@ -288,7 +284,7 @@ namespace Ink_Canvas startTime += DateTime.Now - pauseTime; ProcessBarTime.IsPaused = false; TextBlockHour.Foreground = Brushes.Black; - SymbolIconStart.Symbol = Symbol.Pause; + SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Pause; isPaused = false; timer.Start(); UpdateStopTime(); @@ -300,7 +296,7 @@ namespace Ink_Canvas pauseTime = DateTime.Now; ProcessBarTime.IsPaused = true; TextBlockHour.Foreground = new SolidColorBrush(StringToColor("#FF5B5D5F")); - SymbolIconStart.Symbol = Symbol.Play; + SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Play; BorderStopTime.Visibility = Visibility.Collapsed; isPaused = true; timer.Stop(); @@ -312,7 +308,7 @@ namespace Ink_Canvas totalSeconds = ((hour * 60) + minute) * 60 + second; ProcessBarTime.IsPaused = false; TextBlockHour.Foreground = Brushes.Black; - SymbolIconStart.Symbol = Symbol.Pause; + SymbolIconStart.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.Pause; BtnResetCover.Visibility = Visibility.Collapsed; if (totalSeconds <= 10) @@ -340,7 +336,7 @@ namespace Ink_Canvas } } - private void Window_Closing(object sender, CancelEventArgs e) + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { isTimerRunning = false; } @@ -350,7 +346,7 @@ namespace Ink_Canvas Close(); } - private bool _isInCompact; + private bool _isInCompact = false; private void BtnMinimal_OnMouseUp(object sender, MouseButtonEventArgs e) { @@ -369,7 +365,7 @@ namespace Ink_Canvas dpiScaleY = source.CompositionTarget.TransformToDevice.M22; } IntPtr windowHandle = new WindowInteropHelper(this).Handle; - Screen screen = Screen.FromHandle(windowHandle); + System.Windows.Forms.Screen screen = System.Windows.Forms.Screen.FromHandle(windowHandle); double screenWidth = screen.Bounds.Width / dpiScaleX, screenHeight = screen.Bounds.Height / dpiScaleY; Left = (screenWidth / 2) - (Width / 2); Top = (screenHeight / 2) - (Height / 2); diff --git a/Ink Canvas/Windows/CycleProcessBar.xaml b/InkCanvasForClass/Windows/CycleProcessBar.xaml similarity index 100% rename from Ink Canvas/Windows/CycleProcessBar.xaml rename to InkCanvasForClass/Windows/CycleProcessBar.xaml diff --git a/Ink Canvas/Windows/CycleProcessBar.xaml.cs b/InkCanvasForClass/Windows/CycleProcessBar.xaml.cs similarity index 99% rename from Ink Canvas/Windows/CycleProcessBar.xaml.cs rename to InkCanvasForClass/Windows/CycleProcessBar.xaml.cs index 387c1bce..c8bf6a85 100644 --- a/Ink Canvas/Windows/CycleProcessBar.xaml.cs +++ b/InkCanvasForClass/Windows/CycleProcessBar.xaml.cs @@ -197,7 +197,7 @@ namespace Ink_Canvas.ProcessBars //达到100%则闭合整个 if (angel == 360) { - myCycleProcessBar.Data = Geometry.Parse(myCycleProcessBar.Data + " z"); + myCycleProcessBar.Data = Geometry.Parse(myCycleProcessBar.Data.ToString() + " z"); } } } diff --git a/Ink Canvas/Windows/NamesInputWindow.xaml b/InkCanvasForClass/Windows/NamesInputWindow.xaml similarity index 100% rename from Ink Canvas/Windows/NamesInputWindow.xaml rename to InkCanvasForClass/Windows/NamesInputWindow.xaml diff --git a/Ink Canvas/Windows/NamesInputWindow.xaml.cs b/InkCanvasForClass/Windows/NamesInputWindow.xaml.cs similarity index 90% rename from Ink Canvas/Windows/NamesInputWindow.xaml.cs rename to InkCanvasForClass/Windows/NamesInputWindow.xaml.cs index 9e9a637a..cd58264b 100644 --- a/Ink Canvas/Windows/NamesInputWindow.xaml.cs +++ b/InkCanvasForClass/Windows/NamesInputWindow.xaml.cs @@ -1,7 +1,6 @@ -using System.ComponentModel; +using Ink_Canvas.Helpers; using System.IO; using System.Windows; -using Ink_Canvas.Helpers; namespace Ink_Canvas { @@ -27,7 +26,7 @@ namespace Ink_Canvas } } - private void Window_Closing(object sender, CancelEventArgs e) + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (originText != TextBoxNames.Text) { diff --git a/Ink Canvas/Windows/OperatingGuideWindow.xaml b/InkCanvasForClass/Windows/OperatingGuideWindow.xaml similarity index 100% rename from Ink Canvas/Windows/OperatingGuideWindow.xaml rename to InkCanvasForClass/Windows/OperatingGuideWindow.xaml diff --git a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs b/InkCanvasForClass/Windows/OperatingGuideWindow.xaml.cs similarity index 81% rename from Ink Canvas/Windows/OperatingGuideWindow.xaml.cs rename to InkCanvasForClass/Windows/OperatingGuideWindow.xaml.cs index 3c429a4f..3d48d677 100644 --- a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs +++ b/InkCanvasForClass/Windows/OperatingGuideWindow.xaml.cs @@ -1,7 +1,6 @@ -using System.Windows; +using Ink_Canvas.Helpers; +using System.Windows; using System.Windows.Input; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; namespace Ink_Canvas { @@ -29,10 +28,10 @@ namespace Ink_Canvas private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e) { if (WindowState == WindowState.Normal) { WindowState = WindowState.Maximized; - SymbolIconFullscreen.Symbol = Symbol.BackToWindow; + SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.BackToWindow; } else { WindowState = WindowState.Normal; - SymbolIconFullscreen.Symbol = Symbol.FullScreen; + SymbolIconFullscreen.Symbol = iNKORE.UI.WPF.Modern.Controls.Symbol.FullScreen; } } diff --git a/Ink Canvas/Windows/RandWindow.xaml b/InkCanvasForClass/Windows/RandWindow.xaml similarity index 90% rename from Ink Canvas/Windows/RandWindow.xaml rename to InkCanvasForClass/Windows/RandWindow.xaml index 00ef6d31..8d428645 100644 --- a/Ink Canvas/Windows/RandWindow.xaml +++ b/InkCanvasForClass/Windows/RandWindow.xaml @@ -9,11 +9,9 @@ mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" Title="Ink Canvas 抽奖" Height="500" Width="900"> - - - - + + @@ -103,14 +101,6 @@ - - - - - - - - diff --git a/Ink Canvas/Windows/RandWindow.xaml.cs b/InkCanvasForClass/Windows/RandWindow.xaml.cs similarity index 67% rename from Ink Canvas/Windows/RandWindow.xaml.cs rename to InkCanvasForClass/Windows/RandWindow.xaml.cs index 35617167..766ebc19 100644 --- a/Ink Canvas/Windows/RandWindow.xaml.cs +++ b/InkCanvasForClass/Windows/RandWindow.xaml.cs @@ -1,17 +1,13 @@ -using System; +using Ink_Canvas.Helpers; +using Microsoft.VisualBasic; +using iNKORE.UI.WPF.Modern.Controls; +using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Windows; using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using Ink_Canvas.Helpers; -using iNKORE.UI.WPF.Modern.Controls; -using Microsoft.VisualBasic; -using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox; namespace Ink_Canvas { /// @@ -24,40 +20,6 @@ namespace Ink_Canvas { BorderBtnHelp.Visibility = settings.RandSettings.DisplayRandWindowNamesInputBtn == false ? Visibility.Collapsed : Visibility.Visible; RandMaxPeopleOneTime = settings.RandSettings.RandWindowOnceMaxStudents; RandDoneAutoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency*1000; - - // 加载背景 - LoadBackground(settings); - } - - private void LoadBackground(Settings settings) - { - try - { - int selectedIndex = settings.RandSettings.SelectedBackgroundIndex; - if (selectedIndex <= 0) - { - // 默认背景(无背景) - BackgroundImage.ImageSource = null; - MainBorder.Background = new SolidColorBrush(Color.FromRgb(240, 243, 249)); - } - else if (selectedIndex <= settings.RandSettings.CustomPickNameBackgrounds.Count) - { - // 自定义背景 - var customBackground = settings.RandSettings.CustomPickNameBackgrounds[selectedIndex - 1]; - if (File.Exists(customBackground.FilePath)) - { - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - bitmap.UriSource = new Uri(customBackground.FilePath); - bitmap.EndInit(); - BackgroundImage.ImageSource = bitmap; - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载点名背景出错: {ex.Message}", LogHelper.LogType.Error); - } } public RandWindow(Settings settings, bool IsAutoClose) { @@ -68,20 +30,17 @@ namespace Ink_Canvas { BorderBtnHelp.Visibility = settings.RandSettings.DisplayRandWindowNamesInputBtn == false ? Visibility.Collapsed : Visibility.Visible; RandMaxPeopleOneTime = settings.RandSettings.RandWindowOnceMaxStudents; RandDoneAutoCloseWaitTime = (int)settings.RandSettings.RandWindowOnceCloseLatency * 1000; - - // 加载背景 - LoadBackground(settings); - new Thread(() => { + new Thread(new ThreadStart(() => { Thread.Sleep(100); Application.Current.Dispatcher.Invoke(() => { BorderBtnRand_MouseUp(BorderBtnRand, null); }); - }).Start(); + })).Start(); } public static int randSeed = 0; - public bool isAutoClose; + public bool isAutoClose = false; public bool isNotRepeatName = false; public int TotalCount = 1; @@ -121,7 +80,7 @@ namespace Ink_Canvas { LabelOutput2.Visibility = Visibility.Collapsed; LabelOutput3.Visibility = Visibility.Collapsed; - new Thread(() => { + new Thread(new ThreadStart(() => { for (int i = 0; i < RandWaitingTimes; i++) { int rand = random.Next(1, PeopleCount + 1); while (rands.Contains(rand)) { @@ -155,55 +114,55 @@ namespace Ink_Canvas { outputString += Names[rand - 1] + Environment.NewLine; } else { outputs.Add(rand.ToString()); - outputString += rand + Environment.NewLine; + outputString += rand.ToString() + Environment.NewLine; } } if (TotalCount <= 5) { - LabelOutput.Content = outputString.Trim(); + LabelOutput.Content = outputString.ToString().Trim(); } else if (TotalCount <= 10) { LabelOutput2.Visibility = Visibility.Visible; outputString = ""; for (int i = 0; i < (outputs.Count + 1) / 2; i++) { - outputString += outputs[i] + Environment.NewLine; + outputString += outputs[i].ToString() + Environment.NewLine; } - LabelOutput.Content = outputString.Trim(); + LabelOutput.Content = outputString.ToString().Trim(); outputString = ""; for (int i = (outputs.Count + 1) / 2; i < outputs.Count; i++) { - outputString += outputs[i] + Environment.NewLine; + outputString += outputs[i].ToString() + Environment.NewLine; } - LabelOutput2.Content = outputString.Trim(); + LabelOutput2.Content = outputString.ToString().Trim(); } else { LabelOutput2.Visibility = Visibility.Visible; LabelOutput3.Visibility = Visibility.Visible; outputString = ""; for (int i = 0; i < (outputs.Count + 1) / 3; i++) { - outputString += outputs[i] + Environment.NewLine; + outputString += outputs[i].ToString() + Environment.NewLine; } - LabelOutput.Content = outputString.Trim(); + LabelOutput.Content = outputString.ToString().Trim(); outputString = ""; for (int i = (outputs.Count + 1) / 3; i < (outputs.Count + 1) * 2 / 3; i++) { - outputString += outputs[i] + Environment.NewLine; + outputString += outputs[i].ToString() + Environment.NewLine; } - LabelOutput2.Content = outputString.Trim(); + LabelOutput2.Content = outputString.ToString().Trim(); outputString = ""; for (int i = (outputs.Count + 1) * 2 / 3; i < outputs.Count; i++) { - outputString += outputs[i] + Environment.NewLine; + outputString += outputs[i].ToString() + Environment.NewLine; } - LabelOutput3.Content = outputString.Trim(); + LabelOutput3.Content = outputString.ToString().Trim(); } if (isAutoClose) { - new Thread(() => { + new Thread(new ThreadStart(() => { Thread.Sleep(RandDoneAutoCloseWaitTime); Application.Current.Dispatcher.Invoke(() => { PeopleControlPane.Opacity = 1; PeopleControlPane.IsHitTestVisible = true; Close(); }); - }).Start(); + })).Start(); } }); - }).Start(); + })).Start(); } private void Window_Loaded(object sender, RoutedEventArgs e) { @@ -246,34 +205,5 @@ namespace Ink_Canvas { private void BtnClose_MouseUp(object sender, MouseButtonEventArgs e) { Close(); } - - // 将 isIslandCallerFirstClick 设为静态字段,实现全局记录 - private static bool isIslandCallerFirstClick = true; - - private void BorderBtnIslandCaller_MouseUp(object sender, MouseButtonEventArgs e) - { - if (isIslandCallerFirstClick) - { - MessageBox.Show( - "首次使用ClassIsland点名功能,请确保已安装ClassIsland和Island caller插件。\n" + - "如未安装,请前往官网下载并安装后再使用。如果安装请再次点击此按钮。", - "提示", MessageBoxButton.OK, MessageBoxImage.Information); - isIslandCallerFirstClick = false; - return; - } - - try - { - Process.Start(new ProcessStartInfo - { - FileName = "classisland://plugins/IslandCaller/Run", - UseShellExecute = true - }); - } - catch (Exception ex) - { - MessageBox.Show("无法调用外部点名:" + ex.Message); - } - } } } diff --git a/InkCanvasForClass/Windows/SettingsViews/AboutPanel.xaml b/InkCanvasForClass/Windows/SettingsViews/AboutPanel.xaml new file mode 100644 index 00000000..a74ffefa --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/AboutPanel.xaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Windows/SettingsViews/AboutPanel.xaml.cs b/InkCanvasForClass/Windows/SettingsViews/AboutPanel.xaml.cs new file mode 100644 index 00000000..30dffb67 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/AboutPanel.xaml.cs @@ -0,0 +1,243 @@ +using OSVersionExtension; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using iNKORE.UI.WPF.Helpers; +using static Ink_Canvas.Windows.SettingsWindow; + +namespace Ink_Canvas.Windows.SettingsViews { + /// + /// AboutPanel.xaml 的交互逻辑 + /// + public partial class AboutPanel : UserControl { + public AboutPanel() { + InitializeComponent(); + + // 关于页面图片横幅 + if (File.Exists(App.RootPath + "icc-about-illustrations.png")) { + try { + CopyrightBannerImage.Visibility = Visibility.Visible; + CopyrightBannerImage.Source = + new BitmapImage(new Uri($"file://{App.RootPath + "icc-about-illustrations.png"}")); + } + catch { } + } else { + CopyrightBannerImage.Visibility = Visibility.Collapsed; + } + + // 关于页面构建时间 + var buildTime = FileBuildTimeHelper.GetBuildDateTime(System.Reflection.Assembly.GetExecutingAssembly()); + if (buildTime != null) { + var bt = ((DateTimeOffset)buildTime).LocalDateTime; + var m = bt.Month.ToString().PadLeft(2, '0'); + var d = bt.Day.ToString().PadLeft(2, '0'); + var h = bt.Hour.ToString().PadLeft(2, '0'); + var min = bt.Minute.ToString().PadLeft(2, '0'); + var s = bt.Second.ToString().PadLeft(2, '0'); + AboutBuildTime.Text = + $"build-{bt.Year}-{m}-{d}-{h}:{min}:{s}"; + } + + // 关于页面系统版本 + AboutSystemVersion.Text = $"{OSVersion.GetOperatingSystem()} {OSVersion.GetOSVersion().Version}"; + + // 关于页面触摸设备 + var _t_touch = new Thread(() => { + var touchcount = TouchTabletDetectHelper.GetTouchTabletDevices().Count; + var support = TouchTabletDetectHelper.IsTouchEnabled(); + Dispatcher.BeginInvoke(() => + AboutTouchTabletText.Text = $"{touchcount}个设备,{(support ? "支持触摸设备" : "无触摸支持")}"); + }); + _t_touch.Start(); + } + + public static class TouchTabletDetectHelper { + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern int GetSystemMetrics(int nIndex); + + public static bool IsTouchEnabled() + { + const int MAXTOUCHES_INDEX = 95; + int maxTouches = GetSystemMetrics(MAXTOUCHES_INDEX); + + return maxTouches > 0; + } + + public class USBDeviceInfo + { + public USBDeviceInfo(string deviceID, string pnpDeviceID, string description) + { + this.DeviceID = deviceID; + this.PnpDeviceID = pnpDeviceID; + this.Description = description; + } + public string DeviceID { get; private set; } + public string PnpDeviceID { get; private set; } + public string Description { get; private set; } + } + + public static List GetTouchTabletDevices() + { + List devices = new List(); + + ManagementObjectCollection collection; + using (var searcher = new ManagementObjectSearcher(@"Select * From Win32_PnPEntity")) + collection = searcher.Get(); + + foreach (var device in collection) { + var name = new StringBuilder((string)device.GetPropertyValue("Name")).ToString(); + if (!name.Contains("Pentablet")) continue; + devices.Add(new USBDeviceInfo( + (string)device.GetPropertyValue("DeviceID"), + (string)device.GetPropertyValue("PNPDeviceID"), + (string)device.GetPropertyValue("Description") + )); + } + + collection.Dispose(); + return devices; + } + } + + public static class FileBuildTimeHelper { + public struct _IMAGE_FILE_HEADER + { + public ushort Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + }; + + public static DateTimeOffset? GetBuildDateTime(Assembly assembly) + { + var path = assembly.Location; + if (File.Exists(path)) + { + var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)]; + using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + fileStream.Position = 0x3C; + fileStream.Read(buffer, 0, 4); + fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset + fileStream.Read(buffer, 0, 4); // "PE\0\0" + fileStream.Read(buffer, 0, buffer.Length); + } + var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER)); + return DateTimeOffset.FromUnixTimeSeconds(coffHeader.TimeDateStamp); + } + finally + { + pinnedBuffer.Free(); + } + } + else + { + return null; + } + } + } + + public event EventHandler IsTopBarNeedShadowEffect; + public event EventHandler 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()); + } + } + + private void ScrollBar_Scroll(object sender, RoutedEventArgs e) { + var scrollbar = (ScrollBar)sender; + var scrollviewer = scrollbar.FindAscendant(); + if (scrollviewer != null) scrollviewer.ScrollToVerticalOffset(scrollbar.Track.Value); + } + + private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e) { + var border = (Border)sender; + if (border.Child is Track track) { + track.Width = 16; + track.Margin = new Thickness(0, 0, -2, 0); + var scrollbar = track.FindAscendant(); + if (scrollbar != null) scrollbar.Width = 16; + var grid = track.FindAscendant(); + if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) { + backgroundBorder.Width = 8; + backgroundBorder.CornerRadius = new CornerRadius(4); + backgroundBorder.Opacity = 1; + } + var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ; + if (thumb != null) { + var _thumb = thumb as Border; + _thumb.CornerRadius = new CornerRadius(4); + _thumb.Width = 8; + _thumb.Margin = new Thickness(-0.75, 0, 1, 0); + _thumb.Background = new SolidColorBrush(Color.FromRgb(138, 138, 138)); + } + } + } + + private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e) { + var border = (Border)sender; + border.Background = new SolidColorBrush(Colors.Transparent); + border.CornerRadius = new CornerRadius(0); + if (border.Child is Track track) { + track.Width = 6; + track.Margin = new Thickness(0, 0, 0, 0); + var scrollbar = track.FindAscendant(); + if (scrollbar != null) scrollbar.Width = 6; + var grid = track.FindAscendant(); + if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) { + backgroundBorder.Width = 3; + backgroundBorder.CornerRadius = new CornerRadius(1.5); + backgroundBorder.Opacity = 0; + } + var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ; + if (thumb != null) { + var _thumb = thumb as Border; + _thumb.CornerRadius = new CornerRadius(1.5); + _thumb.Width = 3; + _thumb.Margin = new Thickness(0); + _thumb.Background = new SolidColorBrush(Color.FromRgb(195, 195, 195)); + } + } + } + + private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e) { + var thumb = (Thumb)sender; + var border = thumb.Template.FindName("ScrollbarThumbEx",thumb); + ((Border)border).Background = new SolidColorBrush(Color.FromRgb(95, 95, 95)); + } + + private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e) { + var thumb = (Thumb)sender; + var border = thumb.Template.FindName("ScrollbarThumbEx",thumb); + ((Border)border).Background = new SolidColorBrush(Color.FromRgb(138, 138, 138)); + } + } +} diff --git a/InkCanvasForClass/Windows/SettingsViews/AppearancePanel.xaml b/InkCanvasForClass/Windows/SettingsViews/AppearancePanel.xaml new file mode 100644 index 00000000..c1067873 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/AppearancePanel.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/InkCanvasForClass/Windows/SettingsViews/AppearancePanel.xaml.cs b/InkCanvasForClass/Windows/SettingsViews/AppearancePanel.xaml.cs new file mode 100644 index 00000000..0633ab59 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/AppearancePanel.xaml.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Windows.UI.ApplicationSettings; + +namespace Ink_Canvas.Windows.SettingsViews { + public partial class AppearancePanel : UserControl { + public AppearancePanel() { + InitializeComponent(); + BaseView.SettingsPanels.Add(new SettingsViewPanel() { + Title = "新版设置测试", + Items = new ObservableCollection(new SettingsItem[] { + new SettingsItem() { + Title = "默认ToggleSwitch", + Description = "这是测试文本,这是测试文本", + Type = SettingsItemType.SingleToggleSwtich, + IsSeparatorVisible = true + }, + new SettingsItem() { + Title = "默认开启的ToggleSwitch", + Description = "这是测试文本,这是测试文本324234324", + Type = SettingsItemType.SingleToggleSwtich, + IsSeparatorVisible = true, + ToggleSwitchToggled = true, + }, + new SettingsItem() { + Title = "默认关闭的ToggleSwitch", + Description = "这是测试文本,这是测试文本fsdsdffsd", + Type = SettingsItemType.SingleToggleSwtich, + IsSeparatorVisible = true, + ToggleSwitchToggled = false, + }, + new SettingsItem() { + Title = "绿色的ToggleSwitch", + Description = "这是测试文本,这是测试文本fs大风刮过4sd", + Type = SettingsItemType.SingleToggleSwtich, + IsSeparatorVisible = true, + ToggleSwitchToggled = true, + ToggleSwitchBackground = new SolidColorBrush(Color.FromRgb(51, 209, 122)), + }, + new SettingsItem() { + Title = "默认禁用的的ToggleSwitch", + Description = "这是测试文本", + Type = SettingsItemType.SingleToggleSwtich, + IsSeparatorVisible = true, + ToggleSwitchToggled = true, + ToggleSwitchEnabled = false, + ToggleSwitchBackground = new SolidColorBrush(Color.FromRgb(51, 209, 122)), + }, + new SettingsItem() { + Title = "控制上面的ToggleSwitch是否启用", + Description = "12423432452312322335", + Type = SettingsItemType.SingleToggleSwtich, + IsSeparatorVisible = true, + ToggleSwitchToggled = false, + }, + }) + }); + BaseView.SettingsPanels[0].Items[5].OnToggleSwitchToggled += (sender, args) => { + var item = (SettingsItem)sender; + BaseView.SettingsPanels[0].Items[4].ToggleSwitchEnabled = item.ToggleSwitchToggled; + }; + } + } +} diff --git a/InkCanvasForClass/Windows/SettingsViews/FloatingBarDnDSettingsPanel.xaml b/InkCanvasForClass/Windows/SettingsViews/FloatingBarDnDSettingsPanel.xaml new file mode 100644 index 00000000..164d44c4 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/FloatingBarDnDSettingsPanel.xaml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Windows/SettingsViews/FloatingBarDnDSettingsPanel.xaml.cs b/InkCanvasForClass/Windows/SettingsViews/FloatingBarDnDSettingsPanel.xaml.cs new file mode 100644 index 00000000..cecbadee --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/FloatingBarDnDSettingsPanel.xaml.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using GongSolutions.Wpf.DragDrop; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.Menu; + +namespace Ink_Canvas.Windows.SettingsViews { + + public partial class FloatingBarDnDSettingsPanel : UserControl { + + public class BarItemsDropTarget : IDropTarget { + public ObservableCollection BarItems { get; set; } = + new ObservableCollection(); + + void IDropTarget.DragOver(IDropInfo info) { + info.Effects = DragDropEffects.Move; + info.DropTargetAdorner = DropTargetAdorners.Insert; + } + + void IDropTarget.Drop(IDropInfo info) { + if (info.Data is FloatingBarItem draggedItem) { + var targetCollection = info.TargetCollection as ObservableCollection; + var sourceCollection = info.DragInfo.SourceCollection as ObservableCollection; + + Trace.WriteLine(info.InsertIndex); + + // 在同一个 ObservableCollection 中移动 + if (targetCollection.Equals(sourceCollection)) { + if (info.InsertIndex == 0) { + targetCollection.Move(targetCollection.IndexOf(info.Data as FloatingBarItem),0); + } else if (info.InsertIndex == targetCollection.Count) { + targetCollection.Remove(info.Data as FloatingBarItem); + targetCollection.Add(info.Data as FloatingBarItem); + } else if ((info.InsertIndex - targetCollection.IndexOf(info.Data as FloatingBarItem) == 1 && + info.InsertPosition == RelativeInsertPosition.AfterTargetItem) || + (info.InsertIndex - targetCollection.IndexOf(info.Data as FloatingBarItem) == 0 && + info.InsertPosition == RelativeInsertPosition.BeforeTargetItem)) { } else { + targetCollection.Move(targetCollection.IndexOf(info.Data as FloatingBarItem),info.InsertIndex - 1); + } + } else { // 跨 ObservableCollection 移动 + sourceCollection.Remove(info.Data as FloatingBarItem); + targetCollection.Insert(info.InsertIndex, info.Data as FloatingBarItem); + } + } + } + + void IDropTarget.DragEnter(IDropInfo info) { + + } + + void IDropTarget.DragLeave(IDropInfo info) { + + } + + } + + public class BarDrawerItemsDropTarget : IDropTarget { + public ObservableCollection BarDrawerItems { get; set; } = + new ObservableCollection(); + + void IDropTarget.DragOver(IDropInfo info) { + info.Effects = DragDropEffects.Move; + info.DropTargetAdorner = DropTargetAdorners.Insert; + } + + void IDropTarget.Drop(IDropInfo info) { + if (info.Data is FloatingBarItem draggedItem) { + var targetCollection = info.TargetCollection as ObservableCollection; + var sourceCollection = info.DragInfo.SourceCollection as ObservableCollection; + + // 在同一个 ObservableCollection 中移动 + if (targetCollection.Equals(sourceCollection)) { + targetCollection.Insert(info.InsertIndex, info.Data as FloatingBarItem); + } else { // 跨 ObservableCollection 移动 + sourceCollection.Remove(info.Data as FloatingBarItem); + targetCollection.Insert(info.InsertIndex, info.Data as FloatingBarItem); + } + } + } + + void IDropTarget.DragEnter(IDropInfo info) { + + } + + void IDropTarget.DragLeave(IDropInfo info) { + + } + + } + + public BarItemsDropTarget barItems { get; set; } = new BarItemsDropTarget(); + public BarDrawerItemsDropTarget barDrawerItems { get; set; } = new BarDrawerItemsDropTarget(); + + public FloatingBarDnDSettingsPanel() { + InitializeComponent(); + + ToolbarItemsControl.DataContext = barItems; + ToolbarDrawerItemsControl.DataContext = barDrawerItems; + + barItems.BarItems.Add(new FloatingBarItem() { + IconSource = FindResource("EraserIcon") as DrawingImage, + }); + barDrawerItems.BarDrawerItems.Add(new FloatingBarItem() { + IconSource = FindResource("CursorIcon") as DrawingImage, + }); + barDrawerItems.BarDrawerItems.Add(new FloatingBarItem() { + IconSource = FindResource("PenIcon") as DrawingImage, + }); + } + } +} diff --git a/InkCanvasForClass/Windows/SettingsViews/SettingsBaseView.xaml b/InkCanvasForClass/Windows/SettingsViews/SettingsBaseView.xaml new file mode 100644 index 00000000..4e41db91 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/SettingsBaseView.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Windows/SettingsViews/SettingsBaseView.xaml.cs b/InkCanvasForClass/Windows/SettingsViews/SettingsBaseView.xaml.cs new file mode 100644 index 00000000..9c012bc0 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsViews/SettingsBaseView.xaml.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Ink_Canvas.Components; +using iNKORE.UI.WPF.Helpers; + +namespace Ink_Canvas.Windows.SettingsViews { + + public class SettingsViewPanel { + public string Title { get; set; } + public Visibility _TitleVisibility => String.IsNullOrWhiteSpace(Title) ? Visibility.Collapsed : Visibility.Visible; + public Thickness _PanelMargin => + String.IsNullOrWhiteSpace(Title) ? new Thickness(0) : new Thickness(0, 12, 0, 0); + public ObservableCollection Items { get; set; } = new ObservableCollection() { }; + } + + public enum SettingsItemType { + Plain, // 只显示Title和Description + SingleToggleSwtich, + ToggleSwitchWithArrowButton, + SelectionButtons, + } + + public class SettingsItem : INotifyPropertyChanged { + public string Title { get; set; } + public string Description { get; set; } + public SettingsItemType Type { get; set; } = SettingsItemType.Plain; + public bool IsClickable { get; set; } = false; + public bool IsSeparatorVisible { get; set; } = true; + public Visibility _SeparatorVisibility => IsSeparatorVisible ? Visibility.Visible : Visibility.Collapsed; + public Visibility _ToggleSwitchVisibility => + Type == SettingsItemType.SingleToggleSwtich || Type == SettingsItemType.ToggleSwitchWithArrowButton ? Visibility.Visible : Visibility.Collapsed; + private bool _toggleSwitchToggled; + public bool ToggleSwitchToggled { + get => _toggleSwitchToggled; + set { + if (_toggleSwitchToggled != value) { + _toggleSwitchToggled = value; + OnPropertyChanged(nameof(ToggleSwitchToggled)); // 通知绑定控件属性变化 + OnToggleSwitchToggled?.Invoke(this, EventArgs.Empty); // 触发事件 + } + } + } + public event EventHandler OnToggleSwitchToggled; + public event PropertyChangedEventHandler PropertyChanged; + protected virtual void OnPropertyChanged(string propertyName) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private SolidColorBrush _toggleSwitchBackground = new SolidColorBrush(Color.FromRgb(53, 132, 228)); + public SolidColorBrush ToggleSwitchBackground { + get => _toggleSwitchBackground; + set { + if (_toggleSwitchBackground != value) { + _toggleSwitchBackground = value; + OnPropertyChanged(nameof(ToggleSwitchBackground)); // 通知绑定控件属性变化 + } + } + } + + private bool _toggleSwitchEnabled = true; + public bool ToggleSwitchEnabled { + get => _toggleSwitchEnabled; + set { + if (_toggleSwitchEnabled != value) { + _toggleSwitchEnabled = value; + OnPropertyChanged(nameof(ToggleSwitchEnabled)); // 通知绑定控件属性变化 + } + } + } + } + + public partial class SettingsBaseView : UserControl { + public SettingsBaseView() { + InitializeComponent(); + SettingsViewBaseItemsControl.ItemsSource = SettingsPanels; + } + + public ObservableCollection SettingsPanels { get; set; } = + new ObservableCollection() { }; + + public event EventHandler IsTopBarNeedShadowEffect; + public event EventHandler 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()); + } + } + + private void ScrollBar_Scroll(object sender, RoutedEventArgs e) { + var scrollbar = (ScrollBar)sender; + var scrollviewer = scrollbar.FindAscendant(); + if (scrollviewer != null) scrollviewer.ScrollToVerticalOffset(scrollbar.Track.Value); + } + + private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e) { + var border = (Border)sender; + if (border.Child is Track track) { + track.Width = 16; + track.Margin = new Thickness(0, 0, -2, 0); + var scrollbar = track.FindAscendant(); + if (scrollbar != null) scrollbar.Width = 16; + var grid = track.FindAscendant(); + if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) { + backgroundBorder.Width = 8; + backgroundBorder.CornerRadius = new CornerRadius(4); + backgroundBorder.Opacity = 1; + } + var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ; + if (thumb != null) { + var _thumb = thumb as Border; + _thumb.CornerRadius = new CornerRadius(4); + _thumb.Width = 8; + _thumb.Margin = new Thickness(-0.75, 0, 1, 0); + _thumb.Background = new SolidColorBrush(Color.FromRgb(138, 138, 138)); + } + } + } + + private void ToggleSwitch_OnToggled(object sender, RoutedEventArgs e) { + var toggleswitch = sender as ToggleSwitch; + var item = toggleswitch.Tag as SettingsItem; + item.ToggleSwitchToggled = toggleswitch.IsOn; + } + + private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e) { + var border = (Border)sender; + border.Background = new SolidColorBrush(Colors.Transparent); + border.CornerRadius = new CornerRadius(0); + if (border.Child is Track track) { + track.Width = 6; + track.Margin = new Thickness(0, 0, 0, 0); + var scrollbar = track.FindAscendant(); + if (scrollbar != null) scrollbar.Width = 6; + var grid = track.FindAscendant(); + if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) { + backgroundBorder.Width = 3; + backgroundBorder.CornerRadius = new CornerRadius(1.5); + backgroundBorder.Opacity = 0; + } + var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ; + if (thumb != null) { + var _thumb = thumb as Border; + _thumb.CornerRadius = new CornerRadius(1.5); + _thumb.Width = 3; + _thumb.Margin = new Thickness(0); + _thumb.Background = new SolidColorBrush(Color.FromRgb(195, 195, 195)); + } + } + } + + private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e) { + var thumb = (Thumb)sender; + var border = thumb.Template.FindName("ScrollbarThumbEx",thumb); + ((Border)border).Background = new SolidColorBrush(Color.FromRgb(95, 95, 95)); + } + + private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e) { + var thumb = (Thumb)sender; + var border = thumb.Template.FindName("ScrollbarThumbEx",thumb); + ((Border)border).Background = new SolidColorBrush(Color.FromRgb(138, 138, 138)); + } + } + +} diff --git a/InkCanvasForClass/Windows/SettingsWindow.xaml b/InkCanvasForClass/Windows/SettingsWindow.xaml new file mode 100644 index 00000000..aef30019 --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsWindow.xaml @@ -0,0 +1,3275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Windows/SettingsWindow.xaml.cs b/InkCanvasForClass/Windows/SettingsWindow.xaml.cs new file mode 100644 index 00000000..bb2c913c --- /dev/null +++ b/InkCanvasForClass/Windows/SettingsWindow.xaml.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Management; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Ink_Canvas.Popups; +using Ink_Canvas.Windows.SettingsViews; +using iNKORE.UI.WPF.Helpers; +using iNKORE.UI.WPF.Modern.Controls; +using OSVersionExtension; + +namespace Ink_Canvas.Windows { + public partial class SettingsWindow : Window { + + public SettingsWindow() { + InitializeComponent(); + + // 初始化侧边栏项目 + SidebarItemsControl.ItemsSource = SidebarItems; + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "启动时行为", + Name = "StartupItem", + IconSource = FindResource("StartupIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "画板和墨迹", + Name = "CanvasAndInkItem", + IconSource = FindResource("CanvasAndInkIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "手势操作", + Name = "GesturesItem", + IconSource = FindResource("GesturesIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Separator + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "个性化和外观", + Name = "AppearanceItem", + IconSource = FindResource("AppearanceIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "墨迹转形状", + Name = "InkRecognitionItem", + IconSource = FindResource("InkRecognitionIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "几何与形状绘制", + Name = "ShapeDrawingItem", + IconSource = FindResource("ShapeDrawingIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "自动化行为", + Name = "AutomationItem", + IconSource = FindResource("AutomationIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Separator + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "PowerPoint 支持", + Name = "PowerPointItem", + IconSource = FindResource("PowerPointIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "插件和脚本", + Name = "ExtensionsItem", + IconSource = FindResource("ExtensionsIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Separator + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "存储空间", + Name = "StorageItem", + IconSource = FindResource("StorageIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "截图和屏幕捕捉", + Name = "SnapshotItem", + IconSource = FindResource("SnapshotIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "点名器设置", + Name = "LuckyRandomItem", + IconSource = FindResource("LuckyRandomIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "高级选项", + Name = "AdvancedItem", + IconSource = FindResource("AdvancedIcon") as DrawingImage, + Selected = false, + }); + SidebarItems.Add(new SidebarItem() { + Type = SidebarItemType.Item, + Title = "关于 InkCanvasForClass", + Name = "AboutItem", + IconSource = FindResource("AboutIcon") as DrawingImage, + Selected = false, + }); + _selectedSidebarItemName = "AboutItem"; + UpdateSidebarItemsSelection(); + + SettingsPanes = new Grid[] { + AboutPane, + ExtensionsPane, + CanvasAndInkPane, + GesturesPane, + StartupPane, + AppearancePane, + InkRecognitionPane, + AutomationPane, + PowerPointPane + }; + + SettingsPaneScrollViewers = new ScrollViewer[] { + SettingsAboutPanel.AboutScrollViewerEx, + CanvasAndInkScrollViewerEx, + GesturesScrollViewerEx, + StartupScrollViewerEx, + (AppearancePane.Children[0] as AppearancePanel).BaseView.SettingsViewScrollViewer, + InkRecognitionScrollViewerEx, + AutomationScrollViewerEx, + PowerPointScrollViewerEx + }; + + SettingsAboutPanel.IsTopBarNeedShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0.25; + SettingsAboutPanel.IsTopBarNeedNoShadowEffect += (o, s) => DropShadowEffectTopBar.Opacity = 0; + } + + public Grid[] SettingsPanes = new Grid[] { }; + public ScrollViewer[] SettingsPaneScrollViewers = new ScrollViewer[] { }; + + public enum SidebarItemType { + Item, + Separator + } + + public class SidebarItem { + public SidebarItemType Type { get; set; } + public string Title { get; set; } + public string Name { get; set; } + public ImageSource IconSource { get; set; } + public bool Selected { get; set; } + public Visibility _spVisibility { + get => this.Type == SidebarItemType.Separator ? Visibility.Visible : Visibility.Collapsed; + } + public Visibility _siVisibility { + get => this.Type == SidebarItemType.Item ? Visibility.Visible : Visibility.Collapsed; + } + + public SolidColorBrush _siBackground { + get => this.Selected + ? new SolidColorBrush(Color.FromRgb(217, 217, 217)) + : new SolidColorBrush(Colors.Transparent); + } + } + + public string _selectedSidebarItemName = ""; + public ObservableCollection SidebarItems = new ObservableCollection(); + + public void UpdateSidebarItemsSelection() { + foreach (var si in SidebarItems) { + si.Selected = si.Name == _selectedSidebarItemName; + if (si.Selected) SettingsWindowTitle.Text = si.Title; + } + CollectionViewSource.GetDefaultView(SidebarItems).Refresh(); + + AboutPane.Visibility = _selectedSidebarItemName == "AboutItem" ? Visibility.Visible : Visibility.Collapsed; + ExtensionsPane.Visibility = _selectedSidebarItemName == "ExtensionsItem" ? Visibility.Visible : Visibility.Collapsed; + CanvasAndInkPane.Visibility = _selectedSidebarItemName == "CanvasAndInkItem" ? Visibility.Visible : Visibility.Collapsed; + GesturesPane.Visibility = _selectedSidebarItemName == "GesturesItem" ? Visibility.Visible : Visibility.Collapsed; + StartupPane.Visibility = _selectedSidebarItemName == "StartupItem" ? Visibility.Visible : Visibility.Collapsed; + AppearancePane.Visibility = _selectedSidebarItemName == "AppearanceItem" ? Visibility.Visible : Visibility.Collapsed; + InkRecognitionPane.Visibility = _selectedSidebarItemName == "InkRecognitionItem" ? Visibility.Visible : Visibility.Collapsed; + AutomationPane.Visibility = _selectedSidebarItemName == "AutomationItem" ? Visibility.Visible : Visibility.Collapsed; + PowerPointPane.Visibility = _selectedSidebarItemName == "PowerPointItem" ? Visibility.Visible : Visibility.Collapsed; + foreach (var sv in SettingsPaneScrollViewers) { + sv.ScrollToTop(); + } + } + + 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; + var scrollviewer = scrollbar.FindAscendant(); + if (scrollviewer != null) scrollviewer.ScrollToVerticalOffset(scrollbar.Track.Value); + } + + private void ScrollBarTrack_MouseEnter(object sender, MouseEventArgs e) { + var border = (Border)sender; + if (border.Child is Track track) { + track.Width = 16; + track.Margin = new Thickness(0, 0, -2, 0); + var scrollbar = track.FindAscendant(); + if (scrollbar != null) scrollbar.Width = 16; + var grid = track.FindAscendant(); + if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) { + backgroundBorder.Width = 8; + backgroundBorder.CornerRadius = new CornerRadius(4); + backgroundBorder.Opacity = 1; + } + var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ; + if (thumb != null) { + var _thumb = thumb as Border; + _thumb.CornerRadius = new CornerRadius(4); + _thumb.Width = 8; + _thumb.Margin = new Thickness(-0.75, 0, 1, 0); + _thumb.Background = new SolidColorBrush(Color.FromRgb(138, 138, 138)); + } + } + } + + private void ScrollBarTrack_MouseLeave(object sender, MouseEventArgs e) { + var border = (Border)sender; + border.Background = new SolidColorBrush(Colors.Transparent); + border.CornerRadius = new CornerRadius(0); + if (border.Child is Track track) { + track.Width = 6; + track.Margin = new Thickness(0, 0, 0, 0); + var scrollbar = track.FindAscendant(); + if (scrollbar != null) scrollbar.Width = 6; + var grid = track.FindAscendant(); + if (grid.FindDescendantByName("ScrollBarBorderTrackBackground") is Border backgroundBorder) { + backgroundBorder.Width = 3; + backgroundBorder.CornerRadius = new CornerRadius(1.5); + backgroundBorder.Opacity = 0; + } + var thumb = track.Thumb.Template.FindName("ScrollbarThumbEx", track.Thumb) ; + if (thumb != null) { + var _thumb = thumb as Border; + _thumb.CornerRadius = new CornerRadius(1.5); + _thumb.Width = 3; + _thumb.Margin = new Thickness(0); + _thumb.Background = new SolidColorBrush(Color.FromRgb(195, 195, 195)); + } + } + } + + private void ScrollbarThumb_MouseDown(object sender, MouseButtonEventArgs e) { + var thumb = (Thumb)sender; + var border = thumb.Template.FindName("ScrollbarThumbEx",thumb); + ((Border)border).Background = new SolidColorBrush(Color.FromRgb(95, 95, 95)); + } + + private void ScrollbarThumb_MouseUp(object sender, MouseButtonEventArgs e) { + var thumb = (Thumb)sender; + var border = thumb.Template.FindName("ScrollbarThumbEx",thumb); + ((Border)border).Background = new SolidColorBrush(Color.FromRgb(138, 138, 138)); + } + + private Border _sidebarItemMouseDownBorder = null; + + private void SidebarItem_MouseDown(object sender, MouseButtonEventArgs e) { + if (_sidebarItemMouseDownBorder != null || _sidebarItemMouseDownBorder == sender) return; + _sidebarItemMouseDownBorder = (Border)sender; + var bd = sender as Border; + if (bd.FindDescendantByName("MouseFeedbackBorder") is Border feedbackBd) feedbackBd.Opacity = 0.12; + } + + private void SidebarItem_MouseUp(object sender, MouseButtonEventArgs e) { + if (_sidebarItemMouseDownBorder == null || _sidebarItemMouseDownBorder != sender) return; + if (_sidebarItemMouseDownBorder.Tag is SidebarItem data) _selectedSidebarItemName = data.Name; + SidebarItem_MouseLeave(sender, null); + UpdateSidebarItemsSelection(); + } + + private void SidebarItem_MouseLeave(object sender, MouseEventArgs e) { + if (_sidebarItemMouseDownBorder == null || _sidebarItemMouseDownBorder != sender) return; + if (_sidebarItemMouseDownBorder.FindDescendantByName("MouseFeedbackBorder") is Border feedbackBd) feedbackBd.Opacity = 0; + _sidebarItemMouseDownBorder = null; + } + } +} diff --git a/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml b/InkCanvasForClass/Windows/YesOrNoNotificationWindow.xaml similarity index 100% rename from Ink Canvas/Windows/YesOrNoNotificationWindow.xaml rename to InkCanvasForClass/Windows/YesOrNoNotificationWindow.xaml diff --git a/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs b/InkCanvasForClass/Windows/YesOrNoNotificationWindow.xaml.cs similarity index 95% rename from Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs rename to InkCanvasForClass/Windows/YesOrNoNotificationWindow.xaml.cs index 6033ec13..23e76ed4 100644 --- a/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs +++ b/InkCanvasForClass/Windows/YesOrNoNotificationWindow.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Windows; +using System.Windows.Controls; namespace Ink_Canvas { @@ -47,7 +48,7 @@ namespace Ink_Canvas } private void Window_Closed(object sender, EventArgs e) { - _windowClose?.Invoke(); + _windowClose.Invoke(); } } } \ No newline at end of file diff --git a/Ink Canvas/app.manifest b/InkCanvasForClass/app.manifest similarity index 98% rename from Ink Canvas/app.manifest rename to InkCanvasForClass/app.manifest index 89e4303d..df1798c0 100644 --- a/Ink Canvas/app.manifest +++ b/InkCanvasForClass/app.manifest @@ -1,6 +1,6 @@  - + diff --git a/InkCanvasForClass/icc-about-illustrations.png b/InkCanvasForClass/icc-about-illustrations.png new file mode 100644 index 00000000..43151b07 Binary files /dev/null and b/InkCanvasForClass/icc-about-illustrations.png differ diff --git a/InkCanvasForClass/icc.png b/InkCanvasForClass/icc.png new file mode 100644 index 00000000..99017b97 Binary files /dev/null and b/InkCanvasForClass/icc.png differ diff --git a/InkCanvasForClass/icc.toml b/InkCanvasForClass/icc.toml new file mode 100644 index 00000000..880b6965 --- /dev/null +++ b/InkCanvasForClass/icc.toml @@ -0,0 +1,35 @@ +# InkCanvasForClass 配置文件 +# 版本:v1.0 2024-9-12 +Version = 1.0 +[FloatingBar] # 浮动工具栏 +SemiTransparent = false # 半透明工具栏 +# 开启半透明工具栏后,所有按钮都会呈现为半透明状态 +NearSnap = false # 靠近屏幕角落自动吸附 +InitialPosition = "Center" # 初始位置 +# 初始位置可以是 "TopLeft" "TopRight" "BottomLeft" "BottomRight" "BottomCenter" "TopCenter" +# 或者以数组形式传入浮动工具栏左上角定位的坐标 +ElementCornerRadius = "SuperEllipse" # 圆角类型 +# 圆角类型可以是 "SuperEllipse" "Circle" "None" +# 也可以是具体的数字大小,如 1.14 5.14 2 16 +# 最大圆角值为 24,相当于 "Circle","SuperEllipse" 是超椭圆 +ParallaxEffect = true # 视差效果 +MiniMode = true # 迷你模式 +MiniModeTrigger = false # 浮动工具栏迷你模式触发器,暂未实现 +# 可选 "HeadIconMouseButtonRight" "HeadIconMouseButtonCenter" "HeadIconMouseButtonLeftWithCtrlKey" +# 可使用 KeyStroke 来填入触发器 +ClearButtonColor = [224, 27, 36] # 清空按钮颜色 +ClearButtonPressColor = [254, 226, 226] # 清空按钮按下时颜色 +ToolButtonSelectedBgColor = [37, 99, 235] # 工具按钮选中颜色 +MovingLimitationNoSnap = 12 # 移动限制 无Snap,0 关闭 +MovingLimitationSnapped = 24 # 移动限制 Snaped,0 关闭 +[FloatingBar.NearSnapAreaSize] # 浮动工具栏角落吸附区域大小 +TopLeft = 24 # 左上角 +TopRight = 24 # 右上角 +BottomLeft = [24,24] # 左下角 +BottomRight = [24,24] # 右下角 +TopCenter = 24 # 顶部 +BottomCenter = 24 # 底部 +[FloatingBar.ToolBarItems] # 浮动工具栏工具按钮排序和显示 +CursorMode = [ "Cursor", "Pen", "Clear" , "Separator", "Whiteboard", "Gesture", "Menu", "Fold" ] +MiniMode = [ "Cursor", "Pen", "Clear" ] +AnnotationMode = [ "Cursor", "Pen", "Clear", "Separator", "Eraser", "ShapeDrawing", "Select", "Separator", "Undo", "Redo", "Separator", "Whiteboard", "Gesture", "Menu", "Fold" ] \ No newline at end of file diff --git a/InkCanvasForClassX/App.config b/InkCanvasForClassX/App.config new file mode 100644 index 00000000..56efbc7b --- /dev/null +++ b/InkCanvasForClassX/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/InkCanvasForClassX/App.xaml b/InkCanvasForClassX/App.xaml new file mode 100644 index 00000000..e938505d --- /dev/null +++ b/InkCanvasForClassX/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/InkCanvasForClassX/App.xaml.cs b/InkCanvasForClassX/App.xaml.cs new file mode 100644 index 00000000..5ba574cd --- /dev/null +++ b/InkCanvasForClassX/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace InkCanvasForClassX +{ + /// + /// App.xaml 的交互逻辑 + /// + public partial class App : Application + { + } +} diff --git a/InkCanvasForClassX/InkCanvasForClassX.csproj b/InkCanvasForClassX/InkCanvasForClassX.csproj new file mode 100644 index 00000000..77ad0014 --- /dev/null +++ b/InkCanvasForClassX/InkCanvasForClassX.csproj @@ -0,0 +1,18 @@ + + + net472 + WinExe + false + true + true + + + + + + + + + + + \ No newline at end of file diff --git a/InkCanvasForClassX/InkCanvasForClassX.csproj.user b/InkCanvasForClassX/InkCanvasForClassX.csproj.user new file mode 100644 index 00000000..4fdbee1f --- /dev/null +++ b/InkCanvasForClassX/InkCanvasForClassX.csproj.user @@ -0,0 +1,14 @@ + + + + + + Code + + + + + Designer + + + \ No newline at end of file diff --git a/InkCanvasForClassX/Libraries/InkCanvas.xaml b/InkCanvasForClassX/Libraries/InkCanvas.xaml new file mode 100644 index 00000000..7316ab7a --- /dev/null +++ b/InkCanvasForClassX/Libraries/InkCanvas.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/InkCanvasForClassX/Libraries/InkCanvas.xaml.cs b/InkCanvasForClassX/Libraries/InkCanvas.xaml.cs new file mode 100644 index 00000000..77ec5da4 --- /dev/null +++ b/InkCanvasForClassX/Libraries/InkCanvas.xaml.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace InkCanvasForClassX.Libraries +{ + public partial class InkCanvas : UserControl + { + + public static readonly DependencyProperty InkStrokesProperty = + DependencyProperty.Register( + name: "InkStrokes", + propertyType: typeof(StrokeCollection), + ownerType: typeof(InkCanvas), + typeMetadata: new FrameworkPropertyMetadata( + defaultValue: new StrokeCollection(), + propertyChangedCallback: new PropertyChangedCallback(OnInkStrokesChanged)) + ); + + public StrokeCollection InkStrokes { + get => (StrokeCollection)GetValue(InkStrokesProperty); + set { + Trace.WriteLine("Set InkStrokes"); + SetValue(InkStrokesProperty, value); + } + } + + private static void OnInkStrokesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Trace.WriteLine("Update"); + var control = (InkCanvas)d; + if (e.OldValue is StrokeCollection oldStrokes) { + oldStrokes.StrokesChanged -= control.OnStrokesChanged; + } + + if (e.NewValue is StrokeCollection newStrokes) { + newStrokes.StrokesChanged += control.OnStrokesChanged; + control.inkProjector.Strokes = newStrokes; + } + } + private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs e) + { + Trace.WriteLine("Strokes Collection Changed"); + // Ensure that the InkStrokes dependency property updates + SetValue(InkStrokesProperty, sender as StrokeCollection); + inkProjector.Strokes = sender as StrokeCollection; + } + + public InkCanvas() + { + InitializeComponent(); + InkStrokes.StrokesChanged += OnStrokesChanged; + } + } +} diff --git a/InkCanvasForClassX/Libraries/InkProjector.cs b/InkCanvasForClassX/Libraries/InkProjector.cs new file mode 100644 index 00000000..373ed5bb --- /dev/null +++ b/InkCanvasForClassX/Libraries/InkProjector.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Windows; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; + +namespace InkCanvasForClassX.Libraries +{ + /// + /// 該控件提供一個基於DrawingVisual的最小的StrokeCollection渲染控件,用以替代重量級的InkCanvas控件 + /// + public class InkProjector : FrameworkElement { + private VisualCollection _children; + private DrawingVisual _layer = new DrawingVisual(); + private StrokeCollection _strokes; + private PerfectFreehandJint _perfectFreehandJint = new PerfectFreehandJint(); + + public InkProjector() + { + _children = new VisualCollection(this) { + _layer // 初始化DrawingVisual + }; + } + + public StrokeCollection Strokes { + get => _strokes; + set { + _strokes = value; + DrawPerfectInk(); + } + } + + protected override int VisualChildrenCount => _children.Count; + + protected override Visual GetVisualChild(int index) { + if (index < 0 || index >= _children.Count) throw new ArgumentOutOfRangeException(); + return _children[index]; + } + + private void DrawInk() { + DrawingContext context = _layer.RenderOpen(); + _strokes.Draw(context); + context.Close(); + } + + private void DrawPerfectInk() { + DrawingContext context = _layer.RenderOpen(); + context.PushClip(new RectangleGeometry(new Rect(new Size(this.ActualWidth,this.ActualHeight)))); + foreach (var stroke in _strokes) { + var stylusPtsList = new List(); + foreach (var strokeStylusPoint in stroke.StylusPoints) + { + stylusPtsList.Add(new PerfectFreehandJint.StylusPointLite() + { + x = Math.Round(strokeStylusPoint.X, 2), + y = Math.Round(strokeStylusPoint.Y, 2), + pressure = strokeStylusPoint.PressureFactor, + }); + } + context.DrawGeometry(new SolidColorBrush(Colors.Black), (System.Windows.Media.Pen)null, _perfectFreehandJint.GetGeometryStroke(stylusPtsList.ToArray(), new PerfectFreehandJint.StrokeOptions() + { + size = 2, + thinning = 0.5, + smoothing = 0.5, + streamline = 0.2, + simulatePressure = true, + easing = (x) => 1 - (1 - x) * (1 - x), + last = true, + start = new PerfectFreehandJint.StrokeCapOptions() + { + cap = true, + taper = 0, + easing = (x) => 1 - (1 - x) * (1 - x), + }, + end = new PerfectFreehandJint.StrokeCapOptions() + { + cap = true, + taper = 0, + easing = (x) => 1 - (1 - x) * (1 - x), + }, + })); + } + context.Pop(); + context.Close(); + } + + protected override void OnMouseLeave(MouseEventArgs e) { + base.OnMouseLeave(e); + Trace.WriteLine("Mouse Move"); + } + } +} diff --git a/InkCanvasForClassX/Libraries/PerfectFreehand.cs b/InkCanvasForClassX/Libraries/PerfectFreehand.cs new file mode 100644 index 00000000..7c62479e --- /dev/null +++ b/InkCanvasForClassX/Libraries/PerfectFreehand.cs @@ -0,0 +1,640 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using InkCanvasForClassX.Libraries; +using InkCanvasForClassX.Libraries.Stroke; + +namespace InkCanvasForClassX.Libraries +{ + /// + /// 提供對JS庫steveruizok/perfect-freehand的C#包裝 + /// + public class PerfectFreehand { + + private static double Average(double a, double b) { + return (a + b) / 2; + } + + public static string ConvertVectorsToSVGPath(Vector[] points, bool closed = true) + { + int len = points.Length; + + if (len < 4) + { + return string.Empty; + } + + Vector a = points[0]; + Vector b = points[1]; + Vector c = points[2]; + + StringBuilder result = new StringBuilder(); + result.AppendFormat("M{0:F2},{1:F2} Q{2:F2},{3:F2} {4:F2},{5:F2} T", + a.X, a.Y, b.X, b.Y, Average(b.X, c.X), Average(b.Y, c.Y)); + + for (int i = 2, max = len - 1; i < max; i++) + { + a = points[i]; + b = points[i + 1]; + result.AppendFormat("{0:F2},{1:F2} ", Average(a.X, b.X), Average(a.Y, b.Y)); + } + + if (closed) + { + result.Append("Z"); + } + + return result.ToString(); + } + + /// + /// Get an array of points as objects with an adjusted point, pressure, vector, distance, and runningLength. + /// + /// 原注釋: An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional in both cases. 請使用StylusPointCollection + /// An object with options. + /// + public static StrokePoint[] GetStrokePoints(StylusPointCollection points, + StrokeOptions options) { + var streamline = options.Streamline ?? 0.5; + var size = options.Size ?? 16; + var isComplete = options.Last ?? false; + + // If we don't have any points, return an empty array. + if (points.Count == 0) return Array.Empty(); + + // Find the interpolation level between points. + double t = 0.15 + (1 - streamline) * 0.85; + + // Purify the StylusPointCollection + var pts = new StylusPointCollection(); + foreach (var stylusPoint in points) { + pts.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor)); + } + + // Add extra points between the two, to help avoid "dash" lines + // for strokes with tapered start and ends. Don't mutate the + // input array! + if (points.Count == 2) { + var last = pts[1]; + pts.RemoveAt(pts.Count - 1); + for (var i = 1; i < 5; i++) { + var _vec = Vector.InterpolateVectors( + new Vector(pts[0].X, pts[0].Y), + new Vector(last.X, last.Y), + i / 4 + ); + pts.Add(new StylusPoint(_vec.X, _vec.Y)); + } + } + + // If there's only one point, add another point at a 1pt offset. + // Don't mutate the input array! + if (pts.Count == 1) { + var onePt = new Vector(pts[0].X + 1, pts[0].Y + 1); + pts.Add(new StylusPoint(onePt.X, onePt.Y, pts[0].PressureFactor)); + } + + // The strokePoints array will hold the points for the stroke. + // Start it out with the first point, which needs no adjustment. + var strokePoints = new List() { + new StrokePoint() { + Point = new Vector(pts[0].X, pts[0].Y), + Pressure = pts[0].PressureFactor >= 0 ? pts[0].PressureFactor : 0.25, + Vector = new Vector(1, 1), + Distance = 0, + RunningLength = 0, + } + }; + + // A flag to see whether we've already reached out minimum length + var hasReachedMinimumLength = false; + + // We use the runningLength to keep track of the total distance + double runningLength = 0; + + // We're set this to the latest point, so we can use it to calculate + // the distance and vector of the next point. + var prev = strokePoints[0]; + + // const max = pts.length - 1 + var max = pts.Count - 1; + + // Iterate through all of the points, creating StrokePoints. + for (var i = 1; i < pts.Count; i++) { + var point = isComplete && i == max + ? // If we're at the last point, and `options.last` is true, + // then add the actual input point. + new Vector(pts[i].X, pts[i].Y) + : // Otherwise, using the t calculated from the streamline + // option, interpolate a new point between the previous + // point the current point. + Vector.InterpolateVectors(prev.Point, new Vector(pts[i].X, pts[i].Y), t); + + // If the new point is the same as the previous point, skip ahead. + if (prev.Point.IsEqual(point)) continue; + + // How far is the new point from the previous point? + var distance = Vector.DistLengthVectors(point, prev.Point); + + // Add this distance to the total "running length" of the line. + runningLength += distance; + + // At the start of the line, we wait until the new point is a + // certain distance away from the original point, to avoid noise + if (i < max && !hasReachedMinimumLength) { + if (runningLength < size) continue; + hasReachedMinimumLength = true; + // TODO: Backfill the missing points so that tapering works correctly. + } + + // Create a new strokepoint (it will be the new "previous" one). + prev = new StrokePoint() { + // The adjusted point + Point = point, + // The input pressure (or .5 if not specified) + Pressure = pts[i].PressureFactor >= 0 ? pts[i].PressureFactor : 0.5, + // The vector from the current point to the previous point + Vector = Vector.UnitVector(Vector.SubtractVectors(prev.Point, point)), + // The distance between the current point and the previous point + Distance = distance, + // The total distance so far + RunningLength = runningLength, + }; + + // Push it to the strokePoints array. + strokePoints.Add(prev); + } + + // Set the vector of the first point to be the same as the second point. + strokePoints[0].Vector = strokePoints[1]?.Vector ?? new Vector(0, 0); + + return strokePoints.ToArray(); + } + + /// + /// Compute a radius based on the pressure. + /// + public static double GetStrokeRadius( + double size, + double thinning, + double pressure, + Func easing = null) + { + if (easing == null) { + easing = t => t; // 默認的 easing 函數 + } + return size * easing(0.5 - thinning * (0.5 - pressure)); + } + + // This is the rate of change for simulated pressure. It could be an option. + private const double RATE_OF_PRESSURE_CHANGE = 0.275; + + private const double PI = Math.PI; + private const double FIXED_PI = PI + 0.0001; + + /// + /// Get an array of points (as `[x, y]`) representing the outline of a stroke. + /// + /// An array of StrokePoints as returned from `getStrokePoints`. + /// An object with options. + /// + public static Vector[] GetStrokeOutlinePointsVectors(StrokePoint[] points, StrokeOptions options) { + var strokeOptions_Size = options.Size ?? 16; + var strokeOptions_Thinning = options.Thinning ?? 0.5; + var strokeOptions_Smoothing = options.Smoothing ?? 0.5; + var strokeOptions_SimulatePressure = options.SimulatePressure ?? true; + Func strokeOptions_Easing = (t) => t; + var strokeOptions_Start = options.Start; + var strokeOptions_End = options.End; + var isComplete = options.Last ?? false; + + var capStart = strokeOptions_Start != null ? strokeOptions_Start.Cap : true; + Func taperStartEase = strokeOptions_Start != null + ? strokeOptions_Start.Easing + : (s) => s * (2 - s); + + var capEnd = strokeOptions_End != null ? strokeOptions_End.Cap : true; + Func taperEndEase = strokeOptions_End != null + ? strokeOptions_End.Easing + : (t) => --t * t * t + 1; + + // We can't do anything with an empty array or a stroke with negative size. + if (points.Length == 0 || strokeOptions_Size <= 0) { + return new Vector[] { }; + } + + // The total length of the line + var totalLength = points[points.Length - 1].RunningLength; + + var taperStart = strokeOptions_Start != null ? strokeOptions_Start.IsTaper == false ? 0 : + strokeOptions_Start.IsTaper ? Math.Max(strokeOptions_Size, totalLength) : + strokeOptions_Start.Taper != null ? (double)strokeOptions_Start.Taper : 0 : Double.NaN; + + var taperEnd = strokeOptions_End != null ? strokeOptions_End.IsTaper == false ? 0 : + strokeOptions_End.IsTaper ? Math.Max(strokeOptions_Size, totalLength) : + strokeOptions_End.Taper != null ? (double)strokeOptions_End.Taper : 0 : Double.NaN; + + // The minimum allowed distance between points (squared) + var minDistance = Math.Pow(strokeOptions_Size * strokeOptions_Smoothing, 2); + + // Our collected left and right points + var leftPts = new List(); + var rightPts = new List(); + + // Previous pressure (start with average of first five pressures, + // in order to prevent fat starts for every line. Drawn lines + // almost always start slow! + var _prevPressure_arrseg = new ArraySegment(points, 0, 10); + var prevPressure = _prevPressure_arrseg.Aggregate(points[0].Pressure, + (acc, curr) => { + var pressure = curr.Pressure; + + if (strokeOptions_SimulatePressure) { + // Speed of change - how fast should the the pressure changing? + var sp = Math.Min(1, curr.Distance / strokeOptions_Size); + // Rate of change - how much of a change is there? + var rp = Math.Min(1, 1 - sp); + pressure = Math.Min(1, acc + (rp - acc) * (sp * RATE_OF_PRESSURE_CHANGE)); + } + + return (acc + pressure) / 2; + }); + + // The current radius + var radius = GetStrokeRadius(strokeOptions_Size, strokeOptions_Thinning, points[points.Length - 1].Pressure, + strokeOptions_Easing); + + // The radius of the first saved point + double firstRadius = Double.NaN; + + // Previous vector + var prevVector = points[0].Vector; + + // Previous left and right points + var pl = points[0].Point; + var pr = pl; + + // Temporary left and right points + var tl = pl; + var tr = pr; + + // Keep track of whether the previous point is a sharp corner + // ... so that we don't detect the same corner twice + var isPrevPointSharpCorner = false; + + /* + Find the outline's left and right points + + Iterating through the points and populate the rightPts and leftPts arrays, + skipping the first and last pointsm, which will get caps later on. + */ + foreach (var _sp in points) { + var pressure = _sp.Pressure; + var point = _sp.Point; + var vector = _sp.Vector; + var distance = _sp.Distance; + var runningLength = _sp.RunningLength; + + var i = Array.IndexOf(points, _sp); + + // Removes noise from the end of the line + if (i < points.Length - 1 && totalLength - runningLength < 3) { + continue; + } + + /* + Calculate the radius + + If not thinning, the current point's radius will be half the size; or + otherwise, the size will be based on the current (real or simulated) + pressure. + */ + if (strokeOptions_Thinning == Double.NaN) { + if (strokeOptions_SimulatePressure) { + // If we're simulating pressure, then do so based on the distance + // between the current point and the previous point, and the size + // of the stroke. Otherwise, use the input pressure. + var sp = Math.Min(1, distance / strokeOptions_Size); + var rp = Math.Min(1, 1 - sp); + pressure = Math.Min(1, prevPressure + (rp - prevPressure) * (sp * RATE_OF_PRESSURE_CHANGE)); + } + + radius = GetStrokeRadius(strokeOptions_Size, strokeOptions_Thinning, pressure, + strokeOptions_Easing); + } else { + radius = strokeOptions_Size / 2; + } + + if (firstRadius == Double.NaN) firstRadius = radius; + + /* + Apply tapering + + If the current length is within the taper distance at either the + start or the end, calculate the taper strengths. Apply the smaller + of the two taper strengths to the radius. + */ + var ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1; + var te = runningLength < taperEnd ? taperEndEase(runningLength / taperEnd) : 1; + + radius = Math.Max(0.01, radius * Math.Min(ts, te)); + + /* Add points to left and right */ + + /* + Handle sharp corners + + Find the difference (dot product) between the current and next vector. + If the next vector is at more than a right angle to the current vector, + draw a cap at the current point. + */ + + var nextVector = (i < points.Length - 1 ? points[i + 1] : points[i]).Vector; + var nextDpr = i < points.Length - 1 ? Vector.DotVectors(vector, nextVector) : 1.0; + var prevDpr = Vector.DotVectors(vector, prevVector); + + var isPointSharpCorner = prevDpr < 0 && !isPrevPointSharpCorner; + var isNextPointSharpCorner = nextDpr < 0; + + if (isPointSharpCorner || isNextPointSharpCorner) { + // It's a sharp corner. Draw a rounded cap and move on to the next point + // Considering saving these and drawing them later? So that we can avoid + // crossing future points. + + var offset = Vector.MultiplyVector(Vector.PerpendicularRotationVector(prevVector), radius); + + double step = 1D / 13D; + double t = 0D; + for (; t <= 1D; t += step) { + tl = Vector.RotateVectors(Vector.SubtractVectors(point, offset), point, FIXED_PI * t); + leftPts.Add(tl); + + tr = Vector.RotateVectors(Vector.AddVectors(point, offset), point, FIXED_PI * -t); + rightPts.Add(tr); + } + + pl = tl; + pr = tr; + + if (isNextPointSharpCorner) { + isPrevPointSharpCorner = true; + } + continue; + } + + isPrevPointSharpCorner = false; + + // Handle the last point + if (i == points.Length - 1) { + var offset = Vector.MultiplyVector(Vector.PerpendicularRotationVector(vector), radius); + leftPts.Add(Vector.SubtractVectors(point, offset)); + rightPts.Add(Vector.AddVectors(point, offset)); + continue; + } + + /* + Add regular points + + Project points to either side of the current point, using the + calculated size as a distance. If a point's distance to the + previous point on that side greater than the minimum distance + (or if the corner is kinda sharp), add the points to the side's + points array. + */ + Vector _offset = + Vector.MultiplyVector( + Vector.PerpendicularRotationVector(Vector.InterpolateVectors(nextVector, vector, nextDpr)), + radius); + + tl = Vector.SubtractVectors(point, _offset); + + if (i <= 1 || Vector.DistLengthSquaredVectors(pl, tl) > minDistance) { + leftPts.Add(tl); + pl = tl; + } + + tr = Vector.AddVectors(point, _offset); + + if (i <= 1 || Vector.DistLengthSquaredVectors(pr, tr) > minDistance) { + rightPts.Add(tr); + pr = tr; + } + + // Set variables for next iteration + prevPressure = pressure; + prevVector = vector; + } + + /* + Drawing caps + + Now that we have our points on either side of the line, we need to + draw caps at the start and end. Tapered lines don't have caps, but + may have dots for very short lines. + */ + var firstPoint = points[0].Point; + + var lastPoint = points.Length > 1 + ? points[points.Length - 1].Point + : Vector.AddVectors(points[0].Point, new Vector(1, 1)); + + var startCap = new List(); + var endCap = new List(); + + /* + Draw a dot for very short or completed strokes + + If the line is too short to gather left or right points and if the line is + not tapered on either side, draw a dot. If the line is tapered, then only + draw a dot if the line is both very short and complete. If we draw a dot, + we can just return those points. + */ + if (points.Length == 1) { + if (!((strokeOptions_Start != null && (taperStart != 0 && strokeOptions_Start.IsTaper)) || (strokeOptions_End != null && + (taperEnd != 0 && strokeOptions_End.IsTaper))) || isComplete) { + var start = Vector.ProjectVectors(firstPoint, + Vector.UnitVector( + Vector.PerpendicularRotationVector(Vector.SubtractVectors(firstPoint, lastPoint))), !double.IsNaN(firstRadius) ? -firstRadius : -radius); + var dotPts = new List(); + double step = 1D / 13D; + double t = step; + for (; t <= 1D; t += step) { + dotPts.Add(Vector.RotateVectors(start, firstPoint, FIXED_PI * 2 * t)); + } + + return dotPts.ToArray(); + } + } else { + /* + Draw a start cap + + Unless the line has a tapered start, or unless the line has a tapered end + and the line is very short, draw a start cap around the first point. Use + the distance between the second left and right point for the cap's radius. + Finally remove the first left and right points. :psyduck: + */ + + if ((strokeOptions_Start != null && (taperStart != 0 && strokeOptions_Start.IsTaper)) || ((strokeOptions_End != null && + (taperEnd != 0 && strokeOptions_End.IsTaper)) && points.Length == 1)) { + // The start point is tapered, noop + } else if (capStart) { + // Draw the round cap - add thirteen points rotating the right point around the start point to the left point + double step = 1D / 13D; + double t = step; + for (; t <= 1D; t += step) { + var pt = Vector.RotateVectors(rightPts[0], firstPoint, FIXED_PI * t); + startCap.Add(pt); + } + } else { + // Draw the flat cap - add a point to the left and right of the start point + var cornersVector = Vector.SubtractVectors(leftPts[0], rightPts[0]); + var offsetA = Vector.MultiplyVector(cornersVector, 0.5); + var offsetB = Vector.MultiplyVector(cornersVector, 0.51); + + startCap.Add(Vector.SubtractVectors(firstPoint, offsetA)); + startCap.Add(Vector.SubtractVectors(firstPoint, offsetB)); + startCap.Add(Vector.AddVectors(firstPoint, offsetA)); + startCap.Add(Vector.AddVectors(firstPoint, offsetB)); + } + + /* + Draw an end cap + + If the line does not have a tapered end, and unless the line has a tapered + start and the line is very short, draw a cap around the last point. Finally, + remove the last left and right points. Otherwise, add the last point. Note + that This cap is a full-turn-and-a-half: this prevents incorrect caps on + sharp end turns. + */ + var direction = + Vector.PerpendicularRotationVector(Vector.NegateVector(points[points.Length - 1].Vector)); + + if ((strokeOptions_End != null && + (taperEnd != 0 && strokeOptions_End.IsTaper)) || + ((strokeOptions_Start != null && (taperStart != 0 && strokeOptions_Start.IsTaper)) && points.Length == 1)) { + // Tapered end - push the last point to the line + endCap.Add(lastPoint); + } else if (capEnd) { + // Draw the round end cap + var start = Vector.ProjectVectors(lastPoint, direction, radius); + double step = 1D / 29D; + double t = step; + for (; t < 1D; t += step) { + endCap.Add(Libraries.Vector.RotateVectors(start, lastPoint, FIXED_PI * 3 * t)); + } + } else { + // Draw the flat end cap + endCap.Add(Vector.AddVectors(lastPoint, Vector.MultiplyVector(direction, radius))); + endCap.Add(Vector.AddVectors(lastPoint, Vector.MultiplyVector(direction, radius * 0.99))); + endCap.Add(Vector.SubtractVectors(lastPoint, Vector.MultiplyVector(direction, radius))); + endCap.Add(Vector.SubtractVectors(lastPoint, Vector.MultiplyVector(direction, radius * 0.99))); + } + } + + /* + Return the points in the correct winding order: begin on the left side, then + continue around the end cap, then come back along the right side, and finally + complete the start cap. + */ + rightPts.Reverse(); + return leftPts.Concat(endCap).Concat(rightPts).Concat(startCap).ToArray(); + } + } + + namespace Stroke { + public class StrokeOptions + { + /// + /// The base size (diameter) of the stroke. + /// + public double? Size { get; set; } + + /// + /// The effect of pressure on the stroke's size. + /// + public double? Thinning { get; set; } + + /// + /// How much to soften the stroke's edges. + /// + public double? Smoothing { get; set; } + public double? Streamline { get; set; } + + /// + /// An easing function to apply to each point's pressure. + /// + public Func Easing { get; set; } + + /// + /// Whether to simulate pressure based on velocity. + /// + public bool? SimulatePressure { get; set; } + + /// + /// Cap, taper and easing for the start of the line. + /// + public StrokeCapOptions Start { get; set; } + + /// + /// Cap, taper and easing for the end of the line. + /// + public StrokeCapOptions End { get; set; } + + /// + /// Whether to handle the points as a completed stroke. + /// + public bool? Last { get; set; } + } + + public class StrokeCapOptions + { + /// + /// Whether to apply a cap at the start/end of the line. + /// + public bool Cap { get; set; } + + /// + /// The taper value at the start/end of the line. + /// + public double Taper { get; set; } + + public bool IsTaper { get; set; } + + /// + /// An easing function to apply to the taper. + /// + public Func Easing { get; set; } + } + + public class StrokePoint + { + /// + /// The point coordinates as [x, y]. + /// + public Vector Point { get; set; } + + /// + /// The pressure at the point. + /// + public double Pressure { get; set; } + + /// + /// The distance from the previous point. + /// + public double Distance { get; set; } + + /// + /// The vector at the point. + /// + public Vector Vector { get; set; } + + /// + /// The running length of the stroke. + /// + public double RunningLength { get; set; } + } + } +} diff --git a/InkCanvasForClassX/Libraries/PerfectFreehandJint.cs b/InkCanvasForClassX/Libraries/PerfectFreehandJint.cs new file mode 100644 index 00000000..c5915af8 --- /dev/null +++ b/InkCanvasForClassX/Libraries/PerfectFreehandJint.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using InkCanvasForClassX.Libraries.Stroke; +using Jint; +using Jint.Native; + +namespace InkCanvasForClassX.Libraries +{ + public class PerfectFreehandJint { + public class StylusPointLite + { + public double x; + public double y; + public double pressure; + } + + public class StrokeOptions + { + public double? size { get; set; } + public double? thinning { get; set; } + public double? smoothing { get; set; } + public double? streamline { get; set; } + public Func easing { get; set; } + public bool? simulatePressure { get; set; } + public StrokeCapOptions start { get; set; } + public StrokeCapOptions end { get; set; } + public bool? last { get; set; } + } + + public class StrokeCapOptions + { + public bool? cap { get; set; } + public double? taper { get; set; } + public Func easing { get; set; } + } + + public class StrokePoint + { + public Array point { get; set; } + public double pressure { get; set; } + public double distance { get; set; } + public Array vector { get; set; } + public double runningLength { get; set; } + } + + public readonly Engine JintEngine = new Engine(); + + public PerfectFreehandJint() { + // perfect-freehand + JintEngine.Execute("\"use strict\";function neg(t){return[-t[0],-t[1]]}function add(t,n){return[t[0]+n[0],t[1]+n[1]]}function sub(t,n){return[t[0]-n[0],t[1]-n[1]]}function mul(t,n){return[t[0]*n,t[1]*n]}function div(t,n){return[t[0]/n,t[1]/n]}function per(t){return[t[1],-t[0]]}function dpr(t,n){return t[0]*n[0]+t[1]*n[1]}function isEqual(t,n){return t[0]===n[0]&&t[1]===n[1]}function len(t){return Math.hypot(t[0],t[1])}function len2(t){return t[0]*t[0]+t[1]*t[1]}function dist2(t,n){return len2(sub(t,n))}function uni(t){return div(t,len(t))}function dist(t,n){return Math.hypot(t[1]-n[1],t[0]-n[0])}function med(t,n){return mul(add(t,n),.5)}function rotAround(t,n,e){const r=Math.sin(e),u=Math.cos(e),i=t[0]-n[0],s=t[1]-n[1],o=i*r+s*u;return[i*u-s*r+n[0],o+n[1]]}function lrp(t,n,e){return add(t,mul(sub(n,t),e))}function prj(t,n,e){return add(t,mul(n,e))}function getStrokeRadius(t,n,e,r=(t=>t)){return t*r(.5-n*(.5-e))}function getStrokePoints(t,n={}){var e;const{streamline:r=.5,size:u=16,last:i=!1}=n;if(0===t.length)return[];const s=.15+.85*(1-r);let o=Array.isArray(t[0])?t:t.map((({x:t,y:n,pressure:e=.5})=>[t,n,e]));if(2===o.length){const t=o[1];o=o.slice(0,-1);for(let n=1;n<5;n++)o.push(lrp(o[0],t,n/4))}1===o.length&&(o=[...o,[...add(o[0],[1,1]),...o[0].slice(2)]]);const c=[{point:[o[0][0],o[0][1]],pressure:o[0][2]>=0?o[0][2]:.25,vector:[1,1],distance:0,runningLength:0}];let l=!1,p=0,a=c[0];const d=o.length-1;for(let t=1;t=0?o[t][2]:.5,vector:uni(sub(a.point,n)),distance:e,runningLength:p},c.push(a)}return c[0].vector=(null===(e=c[1])||void 0===e?void 0:e.vector)||[0,0],c}const{min:min,PI:PI}=Math,RATE_OF_PRESSURE_CHANGE=.275,FIXED_PI=PI+1e-4;function getStrokeOutlinePoints(t,n={}){const{size:e=16,smoothing:r=.5,thinning:u=.5,simulatePressure:i=!0,easing:s=(t=>t),start:o={},end:c={},last:l=!1}=n,{cap:p=!0,easing:a=(t=>t*(2-t))}=o,{cap:d=!0,easing:h=(t=>--t*t*t+1)}=c;if(0===t.length||e<=0)return[];const f=t[t.length-1].runningLength,g=!1===o.taper?0:!0===o.taper?Math.max(e,f):o.taper,m=!1===c.taper?0:!0===c.taper?Math.max(e,f):c.taper,E=Math.pow(e*r,2),P=[],v=[];let I,_=t.slice(0,10).reduce(((t,n)=>{let r=n.pressure;if(i){const u=min(1,n.distance/e),i=min(1,1-u);r=min(1,t+u*RATE_OF_PRESSURE_CHANGE*(i-t))}return(t+r)/2}),t[0].pressure),A=getStrokeRadius(e,u,t[t.length-1].pressure,s),S=t[0].vector,b=t[0].point,R=b,M=b,F=R,k=!1;for(let n=0;nE)&&(P.push(M),b=M),F=add(o,x),(n<=1||dist2(R,F)>E)&&(v.push(F),R=F),_=r,S=c}const D=t[0].point.slice(0,2),X=t.length>1?t[t.length-1].point.slice(0,2):add(t[0].point,[1,1]),y=[],O=[];if(1===t.length){if(!g&&!m||l){const t=prj(D,uni(per(sub(D,X))),-(I||A)),n=[];for(let e=1/13,r=e;r<=1;r+=e)n.push(rotAround(t,D,2*FIXED_PI*r));return n}}else{if(g||m&&1===t.length);else if(p)for(let t=1/13,n=t;n<=1;n+=t){const t=rotAround(v[0],D,FIXED_PI*n);y.push(t)}else{const t=sub(P[0],v[0]),n=mul(t,.5),e=mul(t,.51);y.push(sub(D,n),sub(D,e),add(D,e),add(D,n))}const n=per(neg(t[t.length-1].vector));if(m||g&&1===t.length)O.push(X);else if(d){const t=prj(X,n,A);for(let n=1/29,e=n;e<1;e+=n)O.push(rotAround(t,X,3*FIXED_PI*e))}else O.push(add(X,mul(n,A)),add(X,mul(n,.99*A)),sub(X,mul(n,.99*A)),sub(X,mul(n,A)))}return P.concat(O,v.reverse(),y)}function getStroke(t,n={}){return getStrokeOutlinePoints(getStrokePoints(t,n),n)}"); + // getSvgPathFromStroke + JintEngine.Execute( + "\"use strict\";const average=(e,t)=>(e+t)/2;function getSvgPathFromStroke(e,t=!0){const o=e.length;if(o<4)return\"\";let r=e[0],a=e[1];const i=e[2];let F=`M${r[0].toFixed(2)},${r[1].toFixed(2)} Q${a[0].toFixed(2)},${a[1].toFixed(2)} ${average(a[0],i[0]).toFixed(2)},${average(a[1],i[1]).toFixed(2)} T`;for(let t=2,i=o-1;t + /// 提供了對平面向量的坐標運算 + /// + public class Vector { + + private double _x = 0; + private double _y = 0; + + public Vector(double x = 0, double y = 0) { + _x = x; + _y = y; + } + + public double X { + get => _x; + set => _x = value; + } + + public double Y { + get => _y; + set => _y = value; + } + + /// + /// 將該向量改變為其相反向量 + /// + public Vector Negate() { + _x = -_x; + _y = -_y; + return this; + } + + /// + /// 提供一個Vector,返回該Vector的相反向量 + /// + public static Vector NegateVector(Vector vec) { + return new Vector(-vec.X, -vec.Y); + } + + /// + /// Csharp中的Math.Hypot實現 + /// + private static double Hypot(params double[] values) + { + double sum = 0; + foreach (var value in values) { + sum += Math.Pow(value, 2); + } + return Math.Sqrt(sum); + } + + /// + /// 獲取該向量的長度 + /// + public double Length => Vector.Hypot(_x,_y); + + /// + /// 獲取該向量的未開平方的長度 + /// + public double LengthSquared => _x * _x + _y * _y; + + /// + /// 將該向量和另一個向量相加 + /// + public Vector Add(Vector vec) + { + _x = _x + vec.X; + _y = _y + vec.Y; + return this; + } + + /// + /// 提供兩個Vector,返回相加後的Vector + /// + public static Vector AddVectors(Vector vec1, Vector vec2) + { + return new Vector(vec1.X + vec2.X, vec1.Y + vec2.Y); + } + + /// + /// 將該向量減去另一個向量 + /// + public Vector Subtract(Vector vec) + { + _x = _x - vec.X; + _y = _y - vec.Y; + return this; + } + + /// + /// 提供兩個Vector,返回-後的Vector + /// + public static Vector SubtractVectors(Vector vec1, Vector vec2) + { + return new Vector(vec1.X - vec2.X, vec1.Y - vec2.Y); + } + + /// + /// 將該向量除以一個數值 + /// + public Vector DivideBy(double n) + { + _x = _x / n; + _y = _y / n; + return this; + } + + /// + /// 提供兩個Vector,返回/後的Vector + /// + public static Vector DivideByVector(Vector vec, double n) + { + return new Vector(vec.X / n, vec.Y / n); + } + + /// + /// 將該向量乘以一個數值 + /// + public Vector Multiply(double n) + { + _x = _x * n; + _y = _y * n; + return this; + } + + /// + /// 提供兩個Vector,返回*後的Vector + /// + public static Vector MultiplyVector(Vector vec, double n) + { + return new Vector(vec.X * n, vec.Y * n); + } + + /// + /// 將該向量垂直旋轉 + /// + public Vector PerpendicularRotation() { + var _t = _x; + _x = _y; + _y = - _t; + return this; + } + + /// + /// 提供Vector,返回垂直旋轉後的Vector + /// + public static Vector PerpendicularRotationVector(Vector vec) + { + return new Vector(vec.Y, - vec.X); + } + + /// + /// 提供兩個Vector,返回兩個Vector點乘後的数值 + /// + public static double DotVectors(Vector vec1, Vector vec2) + { + return vec1.X * vec2.X + vec1.Y * vec2.Y; + } + + /// + /// 判斷該向量和另外一個向量是否相等 + /// + public bool IsEqual(Vector vec) + { + return Math.Abs(_x - vec.X) < 0.01 && Math.Abs(_y - vec.Y) < 0.01; + } + + /// + /// 獲取該向量和另一個向量的平方距離 + /// + public double DistLengthSquared(Vector vec) { + var subVec = Vector.SubtractVectors(this, vec); + return subVec.LengthSquared; + } + + /// + /// 獲取一個向量和另一個向量的平方距離 + /// + public static double DistLengthSquaredVectors(Vector vec1, Vector vec2) + { + var subVec = Vector.SubtractVectors(vec1, vec2); + return subVec.LengthSquared; + } + + /// + /// 獲取該向量和另一個向量的距離 + /// + public double DistLength(Vector vec) { + return Vector.Hypot(this._y - vec.Y, this._x - vec.X); + } + + /// + /// 獲取一個向量和另一個向量的距離 + /// + public static double DistLengthVectors(Vector vec1, Vector vec2) + { + return Vector.Hypot(vec1.Y - vec2.Y, vec1.X - vec2.X); + } + + /// + /// 將該向量修改為其中間向量 + /// + public Vector Med(Vector vec) { + var addVec = new Vector(_x, _y).Add(vec); + addVec.Multiply(0.5); + _x = addVec.X; + _y = addVec.Y; + return this; + } + + /// + /// 獲取一個向量和另一個向量的中間向量 + /// + public static Vector MedVectors(Vector vec1, Vector vec2) { + var addVec = Vector.AddVectors(vec1, vec2); + return addVec.Multiply(0.5); + } + + /// + /// 將該向量圍繞另一個向量旋轉r弧度 + /// + public Vector Rotate(Vector vec, double r) { + var sin = Math.Sin(r); + var cos = Math.Cos(r); + + var px = _x - vec.X; + var py = _y - vec.Y; + + var nx = px * cos - py * sin; + var ny = px * sin + py * cos; + + _x = nx + vec.X; + _y = ny + vec.Y; + return this; + } + + /// + /// 將一個向量圍繞另一個向量旋轉r弧度 + /// + public static Vector RotateVectors(Vector vec1, Vector vec2, double r) + { + var sin = Math.Sin(r); + var cos = Math.Cos(r); + + var px = vec1.X - vec2.X; + var py = vec1.Y - vec2.Y; + + var nx = px * cos - py * sin; + var ny = px * sin + py * cos; + + return new Vector(nx + vec2.X, ny + vec2.Y); + } + + /// + /// 將該向量與另一個向量插值 + /// + public Vector Interpolate(Vector vec, double t) { + Add(SubtractVectors(vec, this).Multiply(t)); + return this; + } + + /// + /// 將一個向量與另一個向量插值 + /// + public static Vector InterpolateVectors(Vector vec1, Vector vec2, double t) { + return AddVectors(vec1, SubtractVectors(vec2, vec1).Multiply(t)); + } + + /// + /// 將該向量投影在向量的方向上,並附加距離 + /// + public Vector Project(Vector vec, double c) { + Add(vec.Multiply(c)); + return this; + } + + /// + /// 將投影在向量的方向上,並附加距離 + /// + public static Vector ProjectVectors(Vector vec1, Vector vec2, double c) { + return AddVectors(vec1, MultiplyVector(vec2,c)); + } + + /// + /// 取得單位向量 + /// + public Vector Unit() + { + return DivideByVector(this, Length); + } + + /// + /// 取得單位向量 + /// + public static Vector UnitVector(Vector vec) + { + return DivideByVector(vec, vec.Length); + } + } +} diff --git a/InkCanvasForClassX/MainWindow.xaml b/InkCanvasForClassX/MainWindow.xaml new file mode 100644 index 00000000..46c516b9 --- /dev/null +++ b/InkCanvasForClassX/MainWindow.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/InkCanvasForClassX/MainWindow.xaml.cs b/InkCanvasForClassX/MainWindow.xaml.cs new file mode 100644 index 00000000..3c1f2874 --- /dev/null +++ b/InkCanvasForClassX/MainWindow.xaml.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using InkCanvasForClassX.Libraries; +using InkCanvasForClassX.Libraries.Stroke; + +namespace InkCanvasForClassX +{ + /// + /// MainWindow.xaml 的交互逻辑 + /// + public partial class MainWindow : Window + { + + + + public static StylusPointCollection GenerateStylusPoints(int numberOfPoints, int maxX, int maxY) + { + Random random = new Random(); + StylusPointCollection points = new StylusPointCollection(); + + for (int i = 0; i < numberOfPoints; i++) + { + double x = random.NextDouble() * maxX; + double y = random.NextDouble() * maxY; + float pressureFactor = 0.5F; + + StylusPoint point = new StylusPoint(x, y, pressureFactor); + points.Add(point); + } + + return points; + } + + private bool isDragging = false; + private Point startPoint; + public MainWindow() + { + InitializeComponent(); + InkC.StrokeCollected += (object sender, InkCanvasStrokeCollectedEventArgs e) => { + inkCanvas.InkStrokes = InkC.Strokes; + //var stylusPtsList = new List(); + //foreach (var strokeStylusPoint in e.Stroke.StylusPoints) { + // stylusPtsList.Add(new PerfectFreehandJint.StylusPointLite() + // { + // x = Math.Round(strokeStylusPoint.X,2) , + // y = Math.Round(strokeStylusPoint.Y,2), + // pressure = strokeStylusPoint.PressureFactor, + // }); + //} + //var aaa = new PerfectFreehandJint(); + //var ccc = aaa.GetSVGPathStroke(stylusPtsList.ToArray(), new PerfectFreehandJint.StrokeOptions() { + // size = 16, + // thinning = 0.5, + // smoothing = 0.5, + // streamline = 0.5, + // simulatePressure = true, + // easing = (t)=>t, + // last = true, + // start = new PerfectFreehandJint.StrokeCapOptions() { + // cap = true, + // taper = 0, + // easing = (t)=>t, + // }, + // end = new PerfectFreehandJint.StrokeCapOptions() + // { + // cap = true, + // taper = 0, + // easing = (t) => t, + // }, + //}); + //Trace.WriteLine(ccc); + }; + + InkC.MouseRightButtonDown += Inkcanv_MouseRightButtonDown; + InkC.MouseRightButtonUp += Inkcanv_MouseRightButtonUp; + InkC.MouseMove += Inkcanv_MouseMove; + var a = GenerateStylusPoints(10, 1920, 1080); + foreach (var strokePoint in a) + { + Trace.WriteLine($"point x:{strokePoint.X} point y:{strokePoint.Y}"); + } + var c = PerfectFreehand.GetStrokePoints(a, new StrokeOptions() { + Size = 16, + Thinning = 0.5, + Smoothing = 0.5, + SimulatePressure = true, + }); + var s = PerfectFreehand.GetStrokeOutlinePointsVectors(c, new StrokeOptions() { + Size = 16, + Thinning = 0.5, + Smoothing = 0.5, + SimulatePressure = true, + }); + foreach (var strokePoint in c) { + Trace.WriteLine($"point x:{strokePoint.Vector.X.ToString()} point y:{strokePoint.Vector.Y.ToString()}"); + } + Trace.WriteLine(PerfectFreehand.ConvertVectorsToSVGPath(s)); + } + + private void Inkcanv_MouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + isDragging = true; + startPoint = e.GetPosition(InkC); + InkC.CaptureMouse(); + } + + private void Inkcanv_MouseRightButtonUp(object sender, MouseButtonEventArgs e) + { + isDragging = false; + InkC.ReleaseMouseCapture(); + } + + private void Inkcanv_MouseMove(object sender, MouseEventArgs e) + { + if (isDragging && e.RightButton == MouseButtonState.Pressed) + { + Point currentPoint = e.GetPosition(InkC); + System.Windows.Vector delta = currentPoint - startPoint; + + foreach (Stroke stroke in InkC.Strokes) + { + stroke.Transform(new Matrix(1, 0, 0, 1, delta.X, delta.Y), false); + } + inkCanvas.InkStrokes = InkC.Strokes; + + startPoint = currentPoint; + } + } + + private void ButtonBase1_OnClick(object sender, RoutedEventArgs e) { + InkC.EditingMode = InkCanvasEditingMode.None; + } + + private void ButtonBase2_OnClick(object sender, RoutedEventArgs e) { + InkC.EditingMode = InkCanvasEditingMode.Ink; + } + } +} diff --git a/InkCanvasForClassX/Properties/AssemblyInfo.cs b/InkCanvasForClassX/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..84e25cae --- /dev/null +++ b/InkCanvasForClassX/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("InkCanvasForClassX")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("InkCanvasForClassX")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +//若要开始生成可本地化的应用程序,请设置 +//.csproj 文件中的 CultureYouAreCodingWith +//在 中。例如,如果你使用的是美国英语。 +//使用的是美国英语,请将 设置为 en-US。 然后取消 +//对以下 NeutralResourceLanguage 特性的注释。 更新 +//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。 + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //主题特定资源词典所处位置 + //(未在页面中找到资源时使用, + //或应用程序资源字典中找到时使用) + ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 + //(未在页面中找到资源时使用, + //、应用程序或任何主题专用资源字典中找到时使用) +)] + + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/InkCanvasForClassX/Properties/Resources.Designer.cs b/InkCanvasForClassX/Properties/Resources.Designer.cs new file mode 100644 index 00000000..d9fcb04c --- /dev/null +++ b/InkCanvasForClassX/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本: 4.0.30319.42000 +// +// 对此文件的更改可能导致不正确的行为,如果 +// 重新生成代码,则所做更改将丢失。 +// +//------------------------------------------------------------------------------ + +namespace InkCanvasForClassX.Properties +{ + + + /// + /// 强类型资源类,用于查找本地化字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// 返回此类使用的缓存 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("InkCanvasForClassX.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/InkCanvasForClassX/Properties/Resources.resx b/InkCanvasForClassX/Properties/Resources.resx new file mode 100644 index 00000000..af7dbebb --- /dev/null +++ b/InkCanvasForClassX/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/InkCanvasForClassX/Properties/Settings.Designer.cs b/InkCanvasForClassX/Properties/Settings.Designer.cs new file mode 100644 index 00000000..a2420cbf --- /dev/null +++ b/InkCanvasForClassX/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace InkCanvasForClassX.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/InkCanvasForClassX/Properties/Settings.settings b/InkCanvasForClassX/Properties/Settings.settings new file mode 100644 index 00000000..033d7a5e --- /dev/null +++ b/InkCanvasForClassX/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Manual.md b/Manual.md deleted file mode 100644 index 10006044..00000000 --- a/Manual.md +++ /dev/null @@ -1,102 +0,0 @@ -# Ink Canvas 使用说明 - -## 目录 -[一、介绍](#intro) -[二、用法](#usage) -[三、技巧](#skill) -[四、感谢](#thank) -[五、图片](#pictu) -## Ink Canvas 是什么? -Ink Canvas 画板是一款轻量级画板软件,其针对希沃白板设备进行了特别优化,与预装的“希沃白板 5”软件相比,启动速度大幅度提升(提升5-10 倍),系统资源占用更小,使用体验更佳。 - -可以双指缩放拖动甚至旋转,并针对Microsoft PowerPoint 进行优化,可以在放映幻灯片的时候打开白板或黑板进行板书,提高课堂效率。拥有智能墨迹识别技术,可识别圆形等图形,并自动转换。 - -统一幻灯片放映时和白板/黑板模式下的墨迹书写体验,形成统一操作习惯。 - -### Ink Canvas 模式 - -* **幻灯片模式** - * 在幻灯片模式下支持画笔、橡皮擦、图形工具、快捷翻页按键、换页自动换笔迹等功能 - * 自动保存幻灯片下的墨迹,下次打开时墨迹将自动恢复,并且随时可修改。 - * 如果有隐藏的幻灯片页面,将询问是否取消隐藏 -* **画板模式(黑/白板模式)** - * 在画板模式下有着一整个类似希沃白板一样的画板 - * 支持添加新页面和页面切换 - * 支持多指书写:黑板模式界面左下角人像图标为切换按钮 - * 默认是黑板,可以在设置中调成白板 -* **屏幕画笔模式** - * 在屏幕画笔模式下可以显示原屏幕内容的同时将鼠标调为画笔书写授课笔迹 - -### Ink Canvas 特性 -* 笔细的一头可以在屏幕上留下墨迹,而粗的一头可以当作橡皮擦(按笔画擦除),不必手动切换模式 -* 如果觉得翻转笔比较麻烦,可以直接用手指或倾斜笔尖精确擦除小范围墨迹 -* 手掌手指并拢,直接放在屏幕上,则是大橡皮(按区域擦除),此动作的识别速度和精度比“希沃白板 5”要高,即碰即擦,无需等待 -* 启动速度快 -* 更换画笔颜色更方便,不必先点击笔,再切换,清屏也更方便 -* 自动查杀希沃部分软件 -* 单条或多条墨迹选中后缩放、旋转、移动、克隆 -* 全屏幕笔迹双指手势缩放(旋转和拖动也是双指手势) -* 图形绘制(支持长按一直选中) - 直线、虚线、带箭头直线 - 多条平行线,带焦点和不带焦点的椭圆、双曲线、抛物线。 - 正圆、虚线圆,圆柱、圆锥、长方体 - 坐标系(平面直角坐标系,空间直角坐标系) -* 墨迹转图形,目前可实现智能识别圆、三角形、特殊四边形 - 自动转换为规范图形。可自动识别同心圆,相切圆,可自动识别球的截面圆 - -### Ink Canvas 功能 -* **倒计时**:美观的 UI,并可以以接近全屏的大小显示 -* **抽奖**:可导入名单(建议搭配 Excel 使用),可设置抽取人数 -* **保存墨迹**:默认保存至 `文档\Ink Canvas Strokes` -* **截图**:任意模式模式下(包括鼠标)下点击相机图标截图并自动保存至 `图片\Ink Canvas Screenshots`,可在设置中开启“截图时自动保存墨迹” -* **幻灯片自动保存墨迹**:默认保存至 `文档\Ink Canvas Strokes` -* **墨迹回放**:从头自动书写一遍屏中墨迹 - - -## 用法 -### 画笔设置 -* **颜色**:在“画笔模式”下直接点击主界面的颜色球即可 -* **粗细**:点击“设置”齿轮,在“画板”——“画笔粗细”中调整,默认5 -* **笔锋**:在“设置”齿轮中,有根据“速度”和“尾迹”绘制笔锋,让笔迹更美观 -### 橡皮擦使用 -* **笔迹擦除**:单击一次使图标变为灰色可按笔迹擦除内容 -* **范围擦除**:单击多一次使图标变蓝色可按提示图案范围擦除 -* **手掌擦除**:在画笔模式下按压手掌即可起到橡皮擦擦除功能(部分屏幕不支持) -### 绘制预设图形 -* **正圆**:确定圆心时可选用预设在“原点”位置点击,再拖动鼠标完成正圆绘制 -* **抛物线**:可以在“原点”位置点击,再拖动鼠标 -* **双曲线**:在“原点”位置点击拖动鼠标,先确定渐近线,然后确定实轴和虚轴 -* **椭圆**:在“原点”位置点击拖动鼠标,然后确定长轴和短轴(椭圆和双曲线都可以选择有无显示焦点的预设) -* **长方体**:先确定正视图,然后再次点击,确定侧视宽 -* **圆锥、圆柱**:一气呵成 -* **平面直角坐标系**:有多种预设,图标中两线交点上下左右的长边为实际绘制边。 -* **空间直角坐标系**:三轴同时绘制 -* **平行线**:从一端到另一端,适合绘制匀强电场的电场线,并针对特殊角度优化 -### ppt放映模式 -* **翻页**:屏幕中无笔迹时可以多指并排上下滑动翻页,以及控件和键盘方向键翻页 -* **保存**:ppt中的笔迹自动保存,还可以移动笔迹文件夹到其他电脑正常使用原笔迹 -ppt模式下打开的内置画板和正常打开的画板一样的,可能需要用户自行关注保存问题 -### 自动墨迹识别(Ink To Shape) -1. 尽可能画得标准和规范一些 -2. 正圆内画椭圆,可完成截面圆、同心椭圆(适用于天体轨迹)的识别 -3. 可自动完成同心圆、相切圆的识别(内切、外切) - -### 预览界面 - -## 技巧 -1. 双击“清屏”按钮即可在清屏的同时隐藏画板,相当于点击“清屏”和“隐藏画板”两个按钮。 -2. 一不小心擦错了,点击“撤销”按钮即可撤销,再次点击即可恢复。 -3. 如果需要关闭Ink Canvas画板,可在设置中找到关闭按钮。 -4. 在任意时刻,点击“黑板”按钮即可进入黑板。 -5. 空间不够写时,可以双指向上滑动来挪动原有墨迹,也可以双指缩放以腾出空间。 -6. 找不到“橡皮”按钮?笔反过来就是橡皮了!如果觉得翻转笔比较麻烦,可以直接用手指或倾斜笔尖精确擦除小范围墨迹。大范围擦除可以通过并拢手指使用手掌或手背,甚至黑板擦、抹布来完成。建议使用手背而非手掌,因为手掌上汗较多,在屏幕上滑动时摩擦较大,且较大的接触面积可能导致误识别为多指(多指缩放)的问题。 - -## 感谢 -特别感谢 [yuwenhui2020](https://github.com/yuwenhui2020) 为 ` Ink Canvas 使用说明` 做出的贡献! - -## 图片版说明 - -此为带图标的简易说明 -![image](image1.png) -图片预览https://yuwenhui2020.github.io/ink/BHH~7AJ6N7OMLT095LO(LQJ.png - diff --git a/README.md b/README.md index 5887da59..268a39fa 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,22 @@
- + -# InkCanvasForClass
Community Edition +# icc!future -最后一次基于 `InkCanvas` 控件的倔强... +![GitLab Contributors](https://img.shields.io/gitlab/contributors/62768293?style=flat-square) +![GitLab Last Commit](https://img.shields.io/gitlab/last-commit/62768293?style=flat-square) -![GitHub License](https://img.shields.io/github/license/InkCanvasForClass/community) -![GitHub top language](https://img.shields.io/github/languages/top/InkCanvasForClass/community) -![GitHub Repo stars](https://img.shields.io/github/stars/InkCanvasForClass/community) -![GitHub forks](https://img.shields.io/github/forks/InkCanvasForClass/community) -[![All Contributors](https://img.shields.io/github/all-contributors/InkCanvasForClass/community?color=ee8449)](#贡献者) +原名:`InkCanvasForClass`
+可能是最好用的教学批注软件,欲火重生
-[![Discord](https://img.shields.io/discord/1383039050184917053?label=Discord&logo=discord)](https://discord.gg/ahj7eJWhEG) -[![QQ](https://img.shields.io/badge/-1054377349-white?logo=qq&label=QQ)](https://qm.qq.com/q/qo32AclNh6) -[![STCN](https://img.shields.io/badge/icc--ce-8a2be2?label=%E6%99%BA%E6%95%99%E8%AE%BA%E5%9D%9B&link=https%3A%2F%2Fforum.smart-teach.cn%2Ft%2Ficc-ce)](https://forum.smart-teach.cn/t/icc-ce) - - +**基于 `InkCanvas` 控件的又一次倔强**
-## 🤔 发生了什么? +> [!IMPORTANT] +> 虽然 InkCanvasForClass 本体不再维护,但 InkCanvasForClass.PowerPoint.InteropHelper、InkCanvasForClass.PowerPoint.VstoPlugin、InkCanvasForClass.IACoreHelper等附属项目还会继续开发。InkCanvasForClass.IccInkCanvas 会继续维护并添加新代码。(高实验性) - -由于众所周知的原因,[DotteringDoge471](https://github.com/DotteringDoge471) 不再积极负责 InkCanvasForClass 旧时代版本的开发与维护工作,而刚好 [CJKmkp](https://github,com/CJK-mkp) 又维护了这个社区版本的 icc,经过沟通后就顺理成章地成为了 icc 的官方版本。该分支版本 **目前还在开发之中** ,可能会有潜在的问题/ Bug 出现,请在出现 Bug 后与开发者或与 [DotteringDoge471](https://github.com/DotteringDoge471) 上报,方便我们迅速诊断并解决问题。 - -> ⚠️ 请注意:[DotteringDoge471](https://github.com/DotteringDoge471) 不积极负责 **本社区版本** 的开发与维护工作,仅会在有空的时候对本项目开发新功能或修复 Bug。因此,任何问题反馈/Bug反馈/建议等,请优先找本项目主要维护者 [CJKmkp](https://github,com/CJK-mkp) 反馈或在 GitHub 仓库内提出 Issue! - -使用该版本 InkCanvasForClass,意味着您同意自行承担任何可能存在的问题与风险。建议不要在公众场合(例如公开课,录播课,线上直播课,大型会议)使用未经广泛测试和优化的 Beta 版本,对使用 Beta 版本而带来的任何问题和风险(例如:被班主任批斗,被校长处罚,崩溃而导致的场面混乱,全球海平面上升等),**将由使用者自行承担**,[CJKmkp](https://github,com/CJK-mkp) 和 [DotteringDoge471](https://github.com/DotteringDoge471) 及其项目的所有维护者不提供任何担保。 - -♥️ **本项目版权归 [CJKmkp](https://github,com/CJK-mkp) 所有。[CJKmkp](https://github,com/CJK-mkp) 拥有最终解释权。** - -**智教联盟 InkCanvasForClass Community Edition 板块:** [forum.smart-teach.cn/t/icc-ce](https://forum.smart-teach.cn/t/icc-ce) ,我们会在此处发布版本更新日志,同时,您也可以在遵守论坛对应管理规则与InkCanvasForClass Community Edition 板块管理条约的情况下,在该板块内提问或发表自己的使用体验。 - -## ⚠️ 使用须知 -在使用和分发本软件前,请务必了解相关开源协议。本软件基于 https://github.com/Awesome-Iwb/icc-0610fix 修改而来,而 icc-0610fix 基于 https://github.com/ChangSakura/Ink-Canvas 修改,ica 则基于 https://github.com/WXRIW/Ink-Canvas 修改,增加了包括但不限于隐藏到侧边栏等功能,更改了相关UI和软件操作逻辑。对于墨迹书写功能以及 ica 独有功能的相关问题反馈,建议优先查阅 https://github.com/WXRIW/Ink-Canvas/issues 。**使用前建议戴上大脑使用。** - -# 💬 提示 -- 对于新功能的有效意见和合理建议,开发者会适时回复并进行开发。本软件并非商业性质软件或由营利性机构驱动,请不要催促开发者,耐心等待能让功能少些Bug,更加稳定。 -- 此软件仅用于个人使用,请勿商用。更新速度不会很快,如果有能力请通过PR贡献代码,而不是在 Issue 里无能狂怒。 -- 欢迎尝试 InkCanvas 家族的其他成员,包括 [Ink Canvas Plus](https://khyan.top/ic+) 和 [Ink Canvas Artistry](https://github.com/InkCanvas/Ink-Canvas-Artistry) 。您的大力宣传能让更多用户发现我们的软件。 -- **强烈建议使用 Microsoft Office 365 的 PowerPoint 搭配 InkCanvasForClass 使用,效果更好!!!** - -## 📗 FAQ -### 在 Windows 10 以下版本系统中,部分图标显示为 “□” 怎么办? -[点击下载](https://aka.ms/SegoeFonts "SegoeFonts") SegoeFonts 文件,安装压缩包中 `SegMDL2.ttf` 字体后重启即可解决。 - -### 点击放映后一翻页就闪退 -请[激活 Microsoft Office](https://www.coolhub.top/archives/14)。 - -### 放映后画板程序不会切换到 PPT 模式 -1. PowerPoint 处在保护模式下(只读),请退出保护模式,方法如下: - 1. 打开 PowerPoint,点击左上角的“文件”选项; - 2. 在“信息”标签内,点击右侧的“启用编辑”按钮。 -2. 曾经安装过 WPS Office 办公软件,导致 COM 组件被破坏,解决方法为完全卸载 WPS Office 后重新安装 Microsoft Office Mondo 2016 即可解决。 -3. 请确保 PowerPoint 和本应用运行在同一权限下,如果 PowerPoint 以管理员身份运行而本应用以普通用户身份运行,也会出现无法切换到 PPT 模式的现象,您可以通过检查 PowerPoint 的兼容性设置或提权本应用运行来解决该问题。 -4. 如果上述方法不能解决你的问题,请参考这个链接[【点击此处以跳转】](https://www.inkeys.top/tutorial/ppt-com.html) - -### 程序无法正常启动 -请检查你的电脑上是否安装了 `.Net Framework 4.7.2` 或更高版本。若没有,请[前往官网](https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net472-offline-installer "下载 .Net Framework 4.7.2")下载安装。 - -如果仍无法运行,请[安装 `Microsoft Office`](https://www.coolhub.top/archives/11)。 - -### 程序能在 Wine 环境中运行吗? -不能,但是你可以期待 icc-gtk4,是正在开发的仅支持 Linux 平台的 icc 移植版本。 - -## ✏️ 贡献指南 - -请前往 InkCanvasForClass/dubious-notes - -**请注意,在贡献代码时,_务必_ 将所有代码提交到 _beta_ 分支,以保证beta版本总是新于main版本。** - -## TODO LIST -1. 预备2.0版本开发 -2. Ci联动插件 - -## 贡献者 -> [!NOTE] -> 此列表通过[All Contributers](https://allcontributors.org/)实现。 - - - - - - - - - - - - - - - - - -
CJK_mkp
CJK_mkp

🚧 📖 💻
CreeperAWA
CreeperAWA

💻
2,2,3-三甲基戊烷
2,2,3-三甲基戊烷

📝 📖 🎨
Alan-CRL
Alan-CRL

💻 🚇 📖 💵
MKStoler1024
MKStoler1024

📖 💻 🎨
Awesome Iwb
Awesome Iwb

📖
PrefacedCorg
PrefacedCorg

💻
- - - - - - -## 🤝 感谢 -感谢 [DotteringDoge471](https://github.com/DotteringDoge471) 创造了 `InkCanvasForClass`! -感谢 [yuwenhui2020](https://github.com/yuwenhui2020) 为 `Ink Canvas 使用说明` 做出的贡献! -感谢 [CN-Ironegg](https://github.com/CN-Ironegg)、[jiajiaxd](https://github.com/jiajiaxd)、[Kengwang](https://github.com/kengwang)、[Raspberry Kan](https://github.com/Raspberry-Monster)、[clover-yan](https://github.com/clover-yan)、[STBBRD](https://github.com/STBBRD)、[ChangSakura](https://github.com/WuChanging)、[DotteringDoge471](https://github.com/DotteringDoge471) 为本项目贡献代码! - -## License -GPLv3 +- **开源许可**: GNU General Public License Version 3 +- **开发者**: dubi906w +- **耻辱柱**: **`幻想熵K2ro`** \ No newline at end of file diff --git a/UpdateLog.md b/UpdateLog.md deleted file mode 100644 index 0f48f4f8..00000000 --- a/UpdateLog.md +++ /dev/null @@ -1,35 +0,0 @@ -1. 改进端点吸附 -2. 新增自定义浮动栏图标 -3. 新增自定义点名背景 -4. 新增退出收纳模式自动进入批注选项 -5. 改进自动更新 -6. 改进进程检测 -7. 改进设置侧边栏 -8. 修复PPT联动模块 -9. 修复使用正方形预设时多出来一条直线 -10. 新增白板自定义调色盘 -11. 改进手掌擦逻辑 -12. 新增并改进了插件功能 -13. 修复大量触摸问题 -14. 改进橡皮 -15. 新增设置配置备份功能 -16. 新增墨迹全屏保存功能 -17. 修复win7下的自动更新不可用的问题 -18. 改进墨迹打开功能 -19. 改进插件功能及启动台插件 -20. 改进墨迹平滑方案 -21. 改进窗口无焦点 -22. 修复白板页面预览不可触摸的问题 -23. 新增插入图片功能 -24. 新增侧边栏退出放映按钮 -25. 新增退出PPT自动恢复收纳模式 -26. 新增PPT自动回到首页 -27. 新增查杀鸿合屏幕书写后进入批注 -28. 修复浮动栏高度计算 -29. 修复墨迹错页 -30. 改进直线拉直 -31. 改进白板时间显示 -32. 修复快速面板中的退出放映按钮 -33. 优化多墨迹卡顿问题 -34. 优化至单文件版本 -35. 优化图片插入功能 diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 00000000..96504219 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,62 @@ +# FAQ + +## 1. ICC 对 PPT 的相容性如何呢? + +ICC 可以支持 WPS,但目前无法同时支持 MSOffice 和 WPS。若要启用 WPS 支持,请确保 WPS 是否在 “配置工具” 中开启了 “WPS Office 相容第三方系统和软件” 选项,该项目勾选并应用后,将无法检测到 MS Office 的COM接口。 + +如果您安装了“赣教通”、“畅言智慧课堂”等应用,可能会安装“畅言备课精灵”,因此会导致遗失64位的 Office COM 组件的注册且目前似乎无法修复(可以切换到新用户正常使用)。但 WPS Office 可以正常使用。 + +若要将 ICC 配合 WPS 使用,可打开“WPS 演示”后,前往“文件” - “选项” ,取消勾选“单萤幕幻灯片放映时,显示放映工具栏”该项,获得更好的体验。若要将 ICC 配合 MS Office 使用,可以打开 Powerpoint,前往“选项” ,“高级”,取消勾选“显示快捷工具栏”,获得更好的体验。 + +> 不建议使用 WPS,因为 WPS 会污染系统的 COM 接口环境,而且 WPS 简直就是像素级抄袭 Office,用了会让你痛恨一辈子。 + +## 2. **安装后**程序无法正常启动? + +请检查你的电脑上是否安装了 `.Net Framework 4.7.2` 或更高版本。若没有,请前往官网下载。 + +如果程序在启动后黑屏闪退,请打开 “事件查看器” 搜索有关 InkCanvasForClass 的错误信息并上报给开发者(可以在 GitHub 上提交 Issue,或者和开发者单独沟通) + +Windows 7 和 8/8.1 系统可能会出现无法启动的问题,属于正常情况(因为开发者还没有对旧版本系统进行优化,会因为部分依赖 Windows 10+ 系统的 API 导致 ICC 无法启动! + +> 遇到各种奇葩逗比问题请重启应用程式,如果不行请反馈给Dev解决! + +## 3. ICC 什么时候发正式版 + +取决于什么时候 ICC 能够达到我心中期待的样子 + +## 4. 我为什么要用 ICC ? + +ICC 的开发意图,是为了把 IC 和 ICA 的优点融合起来,修复他们的缺点,并在 ICA 的代码基础上进行改善和重写(其实现在很多 ICC 的代码都已经被逗比重写了,已经逐渐和 ICA 脱离干系了),使其功能更加丰富强大,接近于一个商业产品的完善度,如果你觉得 ICC 不适合你,那欢迎你使用同类产品。 + +## 5. ICC 目前只能用 Visual Studio 编译使用吗? + +对,我主观意识上并不想提供非正式版的 build,即使前面一段时间有机器人自动构建(虽然自动构建使用的是能工智人,~~`而且还是司马的幻想熵`~~,但毕竟不是我自己来构建的),但我并不想让大家使用一个还在开发的不完整的有缺陷的软件。具体请参考 Salt Player for Windows 的开发者阐述的观点,我和他的比较类似。 + +## 6. 用管理员权限运行 ICC 后,PPT 批注功能失效了 + +已知问题,目前正在重写 PPT 模块实现自动降权来解决。 + +## 附:ICC 画的所有饼 + +- 浮动工具栏 UI 改进(正在填屎坑) +- 白板模式 UI 改进(还有背景色和稿纸模式) +- **鼠标手势**,可以让画布移动缩放,甚至是旋转,都变得更加轻松(正在开发) +- **全新的设置 UI**,正在逐步清理原项目的屎山 +- **强制置顶**,基于 UIAccess + 输入线程抢夺的方法,保证 ICC 永远显示在所有软件的最顶层 +- **分辨率和DPI变化监听**,保证 ICC 界面正常显示 +- **高性能透明**,绘制使用多线程 UI + WindowChrome,摆脱低性能体验 +- **画面冻结**,使当前画面定格 +- **重写形状绘制**,让绘图体验更舒适方便,还会支持函数绘制和物理图绘制 +- **PPT COM+VSTO双接口融合**,缓解 Office 和 WPS 共存导致的 COM 接口被占用的问题 +- **PPT 系统优化**,高效 PPT 放映状态检测,不丢页不跳页 +- **禁用边缘手势**,禁用烦人的 Windows10/11 的边缘触摸手势 +- **点名器优化**,将会支持历史记录和多名单抽选,同时支持特殊配置 +- **内置小工具**,内置计算器,英汉词典,倒计时,秒表,放大镜,截图等实用小工具 +- **QuickPanel**,方便快捷的从 ICC 打开所有应用和调整系统设置 +- **情境化配置**,每位老师都有专属的配置文件 +- **插件和脚本系统**,支持使用 dotNet Framework 开发原生扩展或使用 Javascript 开发脚本来实现自动化操作或其他扩充功能 +- **形状识别**,基于微软清朝老库的手绘形状自动识别,并提供形状绘制纠正功能 +- **板书库**,高效管理所有板书,课程自动分类,提供云端同步 +- **自动收纳**,检测到教学软件自动开启时会自动隐藏 ICC 界面到屏幕侧边 +- **自动查杀**,检测到指定软件可以让 ICC 大打出手直接查杀并自动使用 ICC 进行替代 +- **桌面悬浮窗屏蔽**,隐藏画板悬浮窗,还您一个干净的电脑桌面 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..0471c297 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,42 @@ +# ICC 文档 + +## 开发 + +本项目目前开发状态: + +- 正在 [`master`](https://github.com/InkCanvasForClass/InkCanvasForClass/tree/master) 分支上开发第一个正式版本 **v6.0.0**。 + +要在本地编译应用,您需要安装以下负载和工具: +1. **[.NET Framework 4.7.2](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net472)** +2. [Visual Studio](https://visualstudio.microsoft.com/) + +对于 Visual Studio,您需要在安装时勾选以下工作负载: + +- .NET 桌面开发 + +## 子项目 + +**`InkCanvasForClass.IACoreHelper`**
+该项目实现了基于 .NET Framework 4.7.2 和 x86 运行环境的墨迹识别库 IACore 的封装。 + +**`InkCanvasForClass.PowerPoint.InteropHelper`**
+该项目将 ICC 的 PPT 适配功能给单独提取了出来,并下放 ZPH 的部分功能到本项目上面。 + +**`InkCanvasForClass.PowerPoint.Vsto`**
+该项目实现了 ICC 对 PPT 的 VSTO 插件支持,可以缓解 Office 和 WPS 共存导致的 COM 接口被占用的问题(或者其他任何有关 COM 接口的疑难杂症,只要软件正常,文档不是被保护文档或兼容模式或只读文档,VSTO都能行)。 + +**`InkCanvasForClassX`**
+该项目已废弃,皆在重写 ICC(💀bro以为自己能够摆脱 IC 的💩山)。 + +**`InkCanvasForClass.IccInkCanvas`**
+该项目将 ICC 魔改的 InkCanvas 控件给提取了出来,方便控件重用,减少 ICC 主程序代码量。封装选择V2、橡皮V2、实时笔锋、白板多页面管理、漫游管理、快捷键重写以及其他魔改。 + +**`InkCanvasForClass.IccInkCanvas.Demo`**
+IccInkCanvas 的 Demo 测试。 + +## 组件库 + +ICC 自己造了一套风格类似于 Gnome Gtk4 的 WPF 组件库,下面有具体每个控件的文档: + +1. [`ToggleSwitch`](./components/ToggleSwitch.md) Gtk.Switch 青春版,切换开关状态的按钮控件 +2. [`SegmentedButtons`]() 类似 Gtk.StackSwitcher 的分段单选按钮 \ No newline at end of file diff --git a/docs/components/SegmentedButtons.md b/docs/components/SegmentedButtons.md new file mode 100644 index 00000000..f71d8129 --- /dev/null +++ b/docs/components/SegmentedButtons.md @@ -0,0 +1,7 @@ +# SegmentedButtons + +## 定义 + +命名空间:`Ink_Canvas.Components` + +SegmentedButtons 提供了一组无单选框的按钮可供用户单选,并在选中时触发事件。 \ No newline at end of file diff --git a/docs/components/ToggleSwitch.md b/docs/components/ToggleSwitch.md new file mode 100644 index 00000000..c55b394c --- /dev/null +++ b/docs/components/ToggleSwitch.md @@ -0,0 +1,31 @@ +# ToggleSwitch + +## 定义 + +命名空间:`Ink_Canvas.Components` + +ToggleSwitch 开关按钮,只有开和关两种状态,可通过点击来切换状态。 + +## 属性 + +| Name | Description | +|------|-------------| +| `IsOn` | 指示是否为开启状态 | +| `IsEnabled` | 指示是否可用,不可用透明度减半且无HitTest,无TabStop | +| `IsDisplayTextIndicator` | 指示是否显示文字提示,I 和 O (WIP) | +| `OnContent` | 指示开启时的文字,为空或不指定则不显示 (WIP) | +| `OffContent` | 指示关闭时的文字,为空或不指定则不显示 (WIP) | +| `SwitchBackground` | 指示切换按钮的背景色,不设置则采用默认颜色 | +| `ThumbForeground` | 指示切换按钮Thumb的颜色,不设置则采用默认颜色 (WIP) | +| `IsEnableClickFeedback` | 指示是否启用点击时的变暗反馈 (WIP) | +| `IsReduceAnimations` | 指示是否减弱动画效果 (WIP) | +| `SwitchSize` | 指示ToggleSwitch的大小 (WIP) | + +## 事件 + +| Name | Description | +|------|-------------| +| `OnToggled` | 当切换按钮的开关状态被修改时触发 | +| `IsEnableClickFeedbackChanged` | 当 `IsEnableClickFeedback` 被修改时触发 (WIP) | +| `IsReduceAnimationsChanged` | 当 `IsReduceAnimations` 被修改时触发 (WIP) | +| `OnSwitchsizeChanged` | 当 `SwitchSize` 变化时触发 (WIP) | \ No newline at end of file diff --git a/icc-new-icon-beta.png b/icc-new-icon-beta.png new file mode 100644 index 00000000..db53b8f2 Binary files /dev/null and b/icc-new-icon-beta.png differ diff --git a/icc-new-icon.png b/icc-new-icon.png new file mode 100644 index 00000000..cb73c39d Binary files /dev/null and b/icc-new-icon.png differ diff --git a/icc.png b/icc.png deleted file mode 100644 index cf1979ad..00000000 Binary files a/icc.png and /dev/null differ diff --git a/privacy.txt b/privacy.txt deleted file mode 100644 index 2c50a9ce..00000000 --- a/privacy.txt +++ /dev/null @@ -1,30 +0,0 @@ -隐私政策 - 本软件指 Ink Canvas 画板软件(以下称本软件)。 - 本软件重视用户隐私,本软件尊重并保护所有使用服务用户的个人隐私权。为了给您提供更准确、更有个性化的服务,本软件会按照本隐私权政策的规定使用和披露您的个人信息。 - 除本隐私权政策另有规定外,在未征得您事先许可的情况下,本软件不会将这些信息对外披露或向第三方提供。本软件会不时更新本隐私权政策。您在同意本软件服务使用协议之时,即视为您已经同意本隐私权政策全部内容。本隐私权政策属于本软件服务使用协议不可分割的一部分。 - 1. 适用范围 - 1)本软件收集到的您在本软件发布的有关信息数据,包括但不限于参与活动、成交信息及评价详情; - 2)违反法律规定或违反本软件规则行为及本软件已对您采取的措施。 - 2.信息使用 - a)本软件不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可,或该第三方和本软件(含本软件关联公司)单独或共同为您提供服务,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些资料。 - b)本软件亦不允许任何第三方以任何手段收集、编辑、出售或者无偿传播您的个人信息。任何本软件平台用户如从事上述活动,一经发现,本软件有权立即终止与该用户的服务协议。 - c)为服务用户的目的,本软件可能通过使用您的个人信息,向您提供您感兴趣的信息,包括但不限于向您发出产品和服务信息,或者与本软件合作伙伴共享信息以便他们向您发送有关其产品和服务的信息(后者需要您的事先同意)。 - 3.信息披露 - 在如下情况下,本软件将依据您的个人意愿或法律的规定全部或部分的披露您的个人信息: - 1)经您事先同意,向第三方披露; - 2)为提供您所要求的产品和服务,而必须和第三方分享您的个人信息; - 3)根据法律的有关规定,或者行政或司法机构的要求,向第三方或者行政、司法机构披露; - 4)如您出现违反中国有关法律、法规或者本软件服务协议或相关规则的情况,需要向第三方披露; - 5)如您是适格的知识产权投诉人并已提起投诉,应被投诉人要求,向被投诉人披露,以便双方处理可能的权利纠纷; - 6)在本软件平台上创建的某一交易中,如交易任何一方履行或部分履行了交易义务并提出信息披露请求的,本软件有权决定向该用户提供其交易对方的联络方式等必要信息,以促成交易的完成或纠纷的解决。 - 7)其它本软件根据法律、法规或者网站政策认为合适的披露。 - 4. 信息存储和交换 - 本软件收集的有关您的信息和资料将保存在本软件及(或)其关联公司的服务器上,这些信息和资料可能传送至您所在国家、地区或本软件收集信息和资料所在地的境外并在境外被访问、存储和展示。 - 5. Cookie的使用 - a)在您未拒绝接受Cookies的情况下,本软件会在您的计算机上设定或取用Cookies - b)您有权选择接受或拒绝接受Cookies。您可以通过修改浏览器设置的方式拒绝接受Cookies。但如果您选择拒绝接受Cookies,则您可能无法登录或使用依赖于Cookies的本软件网络服务或功能。 - c)通过本软件所设Cookies所取得的有关信息,将适用本政策。 - 6. 信息安全 - a) 本软件帐号均有安全保护功能,请妥善保管您的用户名及密码信息。本软件将通过对用户密码进行加密等安全措施确保您的信息不丢失,不被滥用和变造。尽管有前述安全措施,但同时也请您注意在信息网络上不存在“完善的安全措施”。 - b) 在使用本软件网络服务进行网上交易时,您不可避免的要向交易对方或潜在的交易对方披露自己的个人信息,如联络方式或者邮政地址。请您妥善保护自己的个人信息,仅在必要的情形下向他人提供。如您发现自己的个人信息泄密,尤其是本软件用户名及密码发生泄露,请您立即联络本软件客服,以便本软件采取相应措施。 -